Jetpack Compose के लिए Kotlin

Jetpack Compose, Kotlin के आधार पर बनाई गई है. कुछ मामलों में, Kotlin खास पेज मुहावरे, जिनकी मदद से बेहतर तरीके से लिखने के लिए कोड लिखना आसान हो जाता है. अगर आपको लगता है कि किसी अन्य और मैन्युअल रूप से उस भाषा का Kotlin में अनुवाद करें, तो ऐसा हो सकता है कि वे कंपोज़ की सुविधाओं का इस्तेमाल न कर पाएं. ऐसे में, आपको मुहावरेदार ढंग से लिखे गए Kotlin कोड को समझना मुश्किल है. ज़्यादा रेवेन्यू पाना Kotlin की स्टाइल के बारे में जान लेने से, आपको इस तरह की गलतियों से बचने में मदद मिल सकती है.

डिफ़ॉल्ट आर्ग्युमेंट

Kotlin फ़ंक्शन लिखते समय, फ़ंक्शन के लिए डिफ़ॉल्ट वैल्यू तय की जा सकती है आर्ग्युमेंट, का उपयोग तब किया जाता है, जब कॉलर स्पष्ट रूप से उन मानों को पास नहीं करता है. यह सुविधा ज़रूरत से ज़्यादा फ़ंक्शन होने चाहिए.

उदाहरण के लिए, मान लीजिए कि आपको एक ऐसा फ़ंक्शन लिखना है जो एक स्क्वेयर बनाता है. वह फ़ंक्शन में एक ज़रूरी पैरामीटर हो सकता है, sideLength. इससे फ़ंक्शन की लंबाई बताई जा सकती है करते हैं. इसमें कई वैकल्पिक पैरामीटर हो सकते हैं, जैसे कि मोटाई, EdgeColor वगैरह; अगर कॉलर उन्हें नहीं बताता है, तो फ़ंक्शन डिफ़ॉल्ट वैल्यू का इस्तेमाल करता है. दूसरी भाषाओं में, आपको कई फ़ंक्शन हैं:

// We don't need to do this in Kotlin!
void drawSquare(int sideLength) { }

void drawSquare(int sideLength, int thickness) { }

void drawSquare(int sideLength, int thickness, Color edgeColor) { }

Kotlin में, एक फ़ंक्शन लिखा जा सकता है और तर्क:

fun drawSquare(
    sideLength: Int,
    thickness: Int = 2,
    edgeColor: Color = Color.Black
) {
}

आपको कई सारे ग़ैर-ज़रूरी फ़ंक्शन लिखने के अलावा, यह काम नहीं करना पड़ेगा सुविधा आपके कोड को पढ़ने में ज़्यादा आसान बनाती है. अगर कॉलर वह मान, जो दिखाता है कि वे डिफ़ॉल्ट वैल्यू. इसके अलावा, नाम वाले पैरामीटर से यह देखना बहुत आसान हो जाता है कि क्या चल रहा है चालू करें. अगर कोड को देखने पर आपको इस तरह का कोई फ़ंक्शन कॉल दिखता है, तो drawSquare() कोड की जांच किए बिना जानें कि पैरामीटर का क्या मतलब है:

drawSquare(30, 5, Color.Red);

इसके उलट, यह कोड खुद दस्तावेज़ बना रहा है:

drawSquare(sideLength = 30, thickness = 5, edgeColor = Color.Red)

ज़्यादातर Compose की लाइब्रेरी डिफ़ॉल्ट आर्ग्युमेंट का इस्तेमाल करती हैं. साथ ही, यह फ़ॉर्मैट, कंपोज़ेबल फ़ंक्शन के लिए भी इस्तेमाल किया जा सकता है. इस तरीके से आपको कंपोज़ेबल को कस्टमाइज़ किया जा सकता है, लेकिन फिर भी डिफ़ॉल्ट के तौर पर सेट किए गए व्यवहार को आसानी से शुरू किया जा सकता है. उदाहरण के लिए, इस तरह का कोई आसान टेक्स्ट एलिमेंट बनाया जा सकता है:

Text(text = "Hello, Android!")

उस कोड का नीचे दिए गए इफ़ेक्ट जैसा ही है, जो कि बहुत ज़्यादा वर्बोस कोड होता है, जिसमें ज़्यादा से ज़्यादा Text पैरामीटर अलग से सेट किए गए हों:

Text(
    text = "Hello, Android!",
    color = Color.Unspecified,
    fontSize = TextUnit.Unspecified,
    letterSpacing = TextUnit.Unspecified,
    overflow = TextOverflow.Clip
)

पहला कोड स्निपेट न सिर्फ़ पढ़ने में ज़्यादा आसान और आसान है, बल्कि यह दस्तावेज़ों को खुद ही रिकॉर्ड किया जा सकता है. सिर्फ़ text पैरामीटर तय करने का मतलब है कि अन्य सभी पैरामीटर के लिए, आप डिफ़ॉल्ट मानों का उपयोग करना चाहते हैं. इसके उलट, दूसरे स्निपेट का मतलब है कि आप उनके लिए अन्य पैरामीटर, हालांकि आपके द्वारा सेट किए गए मान डिफ़ॉल्ट मान के रूप में होते हैं फ़ंक्शन का इस्तेमाल करना होगा.

हाई-ऑर्डर फ़ंक्शन और लैम्डा एक्सप्रेशन

Kotlin, हाई-ऑर्डर ऑर्डर पर काम करता है फ़ंक्शन, फ़ंक्शन जो दूसरे फ़ंक्शन को पैरामीटर के तौर पर पाते हैं. कंपोज़ की सुविधा, इस तरीके का इस्तेमाल करके बनाई जाती है. इसके लिए उदाहरण के लिए, Button कंपोज़ेबल फ़ंक्शन में onClick लैम्डा पैरामीटर दिया गया है. वैल्यू पैरामीटर एक फ़ंक्शन है, जिसे बटन तब कॉल करता है, जब उपयोगकर्ता उस पर क्लिक करता है:

Button(
    // ...
    onClick = myClickFunction
)
// ...

हाई-ऑर्डर फ़ंक्शन, लैम्डा एक्सप्रेशन और एक्सप्रेशन के साथ नैचुरल तरीके से पेयर होते हैं जो किसी फ़ंक्शन का आकलन करता है. अगर आपको सिर्फ़ एक बार फ़ंक्शन की ज़रूरत है, तो आपको का इस्तेमाल करें, ताकि इसे हाई-ऑर्डर फ़ंक्शन को पास करने के लिए किसी दूसरी जगह पर इसका इस्तेमाल किया जा सके. इसके बजाय, आप यह कर सकते है: फ़ंक्शन को ठीक वहीं पर लैम्डा एक्सप्रेशन के साथ परिभाषित करें. पिछला उदाहरण मानता है कि myClickFunction() कहीं और परिभाषित किया गया है. हालांकि, अगर आपको सिर्फ़ इसका इस्तेमाल फ़ंक्शन यहां फ़ंक्शन को एक्ज़ीक्यूट करना चाहिए, ताकि फ़ंक्शन इनलाइन को लैम्डा के साथ आसानी से परिभाषित किया जा सके एक्सप्रेशन:

Button(
    // ...
    onClick = {
        // do something
        // do something else
    }
) { /* ... */ }

पीछे चल रहे लैम्डा

Kotlin हाई-ऑर्डर फ़ंक्शन को कॉल करने के लिए एक खास सिंटैक्स देता है जिनका आखिरी पैरामीटर एक Lambda है. अगर आपको Lambda एक्सप्रेशन को पैरामीटर की मदद से, पीछे का Lambda फ़ंक्शन इस्तेमाल किया जा सकता है सिंटैक्स का इस्तेमाल करें. लैम्डा एक्सप्रेशन को कोष्ठक के अंदर रखने के बजाय, आप इसे उससे बचा जा सकता है. यह Compose में सामान्य स्थिति है. इसलिए, आपको इसके बारे में जानकारी होनी चाहिए कोड कैसा दिखता है.

उदाहरण के लिए, सभी लेआउट का अंतिम पैरामीटर, जैसे कि Column() कंपोज़ेबल फ़ंक्शन, content है, एक ऐसा फ़ंक्शन जो चाइल्ड यूज़र इंटरफ़ेस (यूआई) उत्सर्ज करता है एलिमेंट. मान लें कि आपको तीन टेक्स्ट एलिमेंट वाला एक कॉलम बनाना है, और आपको कुछ फ़ॉर्मैटिंग लागू करनी होगी. यह कोड काम करेगा, लेकिन यह बहुत जटिल:

Column(
    modifier = Modifier.padding(16.dp),
    content = {
        Text("Some text")
        Text("Some more text")
        Text("Last text")
    }
)

क्योंकि फ़ंक्शन सिग्नेचर में, आखिरी पैरामीटर content है और हम इसकी वैल्यू को लैम्डा एक्सप्रेशन के रूप में पास कर रहे हैं, तो हम इसे ब्रैकेट:

Column(modifier = Modifier.padding(16.dp)) {
    Text("Some text")
    Text("Some more text")
    Text("Last text")
}

दोनों उदाहरणों का एक ही मतलब है. ब्रैकेट लैम्डा को परिभाषित करते हैं एक्सप्रेशन, जिसे content पैरामीटर को पास किया जाता है.

यहां तक कि अगर सिर्फ़ एक पैरामीटर को पास किया जा रहा है, तो वह पीछे वाला Lambda फ़ंक्शन है—यानी कि अगर फ़ाइनल पैरामीटर एक Lambda है और आपने कोई अन्य पैरामीटर पास नहीं किया है पैरामीटर—दिए गए ब्रैकेट को पूरी तरह से छोड़ा जा सकता है. उदाहरण के लिए, मान लीजिए कि Column में मॉडिफ़ायर पास करने की ज़रूरत नहीं पड़ी. आप कोड को इस तरह लिख सकते हैं शामिल करें:

Column {
    Text("Some text")
    Text("Some more text")
    Text("Last text")
}

यह सिंटैक्स Compose में काफ़ी आम है. खास तौर पर, इस तरह के लेआउट एलिमेंट के लिए Column. आखिरी पैरामीटर लैम्डा एक्सप्रेशन है, जो एलिमेंट के चाइल्ड शामिल हैं और उन बच्चों को फ़ंक्शन कॉल के बाद ब्रैकेट में दिखाया जाता है.

स्कोप और रिसीवर

कुछ तरीके और प्रॉपर्टी सिर्फ़ किसी खास स्कोप में ही उपलब्ध हैं. सीमित दायरे की मदद से, ज़रूरत के हिसाब से फ़ंक्शन उपलब्ध कराया जा सकता है और गलती से बचा जा सकता है जहां यह उचित न हो वहां उस सुविधा का इस्तेमाल करना.

'लिखें' विंडो में इस्तेमाल किया गया कोई उदाहरण देखें. Row लेआउट पर कॉल करने पर कंपोज़ेबल, आपके कॉन्टेंट lambda को RowScope में अपने-आप शुरू कर दिया जाता है. इससे Row को ऐसे फ़ंक्शन के बारे में पता चलता है जो सिर्फ़ Row में मान्य होती है. नीचे दिए गए उदाहरण से पता चलता है कि Row ने किस तरह align मॉडिफ़ायर:

Row {
    Text(
        text = "Hello world",
        // This Text is inside a RowScope so it has access to
        // Alignment.CenterVertically but not to
        // Alignment.CenterHorizontally, which would be available
        // in a ColumnScope.
        modifier = Modifier.align(Alignment.CenterVertically)
    )
}

कुछ एपीआई, Lambdas को स्वीकार करते हैं जिन्हें रिसीवर स्कोप में कॉल किया जाता है. वो लैम्डा जिनके पास ऐसी प्रॉपर्टी और फ़ंक्शन का ऐक्सेस हो जिन्हें दूसरी जगहों पर, पैरामीटर का एलान:

Box(
    modifier = Modifier.drawBehind {
        // This method accepts a lambda of type DrawScope.() -> Unit
        // therefore in this lambda we can access properties and functions
        // available from DrawScope, such as the `drawRectangle` function.
        drawRect(
            /*...*/
            /* ...
        )
    }
)

ज़्यादा जानकारी के लिए, पाने वाला Kotlin दस्तावेज़ में.

दी गई प्रॉपर्टी

Kotlin, डिलिगेट किए गए लोगों या कंपनियों के साथ काम करती है प्रॉपर्टी के लिए. इन प्रॉपर्टी को उसी रूप में कॉल किया जाता है जैसे वे फ़ील्ड थीं, लेकिन उनका मान यह एक्सप्रेशन का आकलन करके डाइनैमिक तौर पर तय किया जाता है. आप इन्हें पहचान सकते हैं प्रॉपर्टी को by सिंटैक्स का इस्तेमाल करके बनाया जाता है:

class DelegatingClass {
    var name: String by nameGetterFunction()

    // ...
}

अन्य कोड, इस तरह के कोड के साथ प्रॉपर्टी को ऐक्सेस कर सकते हैं:

val myDC = DelegatingClass()
println("The name property is: " + myDC.name)

जब println() काम करता है, तो वैल्यू पाने के लिए nameGetterFunction() को कॉल किया जाता है .

ये प्रॉपर्टी खास तौर पर तब काम आती हैं, जब इनका इस्तेमाल किया जा रहा हो स्टेट-बैक्ड प्रॉपर्टी:

var showDialog by remember { mutableStateOf(false) }

// Updating the var automatically triggers a state change
showDialog = true

डेटा क्लास को बनाया जा रहा है

अगर आपने किसी ऐसे डेटा को क्लास, तो आप आसानी से डेटा को डिस्ट्रक्चरिंग एलान के बारे में ज़्यादा जानें. इसके लिए उदाहरण के लिए, मान लीजिए कि आपने एक Person क्लास तय की है:

data class Person(val name: String, val age: Int)

अगर आपके पास उस तरह का कोई ऑब्जेक्ट है, तो उसकी वैल्यू को ऐक्सेस करने के लिए, इस तरह के कोड का इस्तेमाल करें: शामिल करें:

val mary = Person(name = "Mary", age = 35)

// ...

val (name, age) = mary

आपको Compose फ़ंक्शन में अक्सर इस तरह के कोड दिखेंगे:

Row {

    val (image, title, subtitle) = createRefs()

    // The `createRefs` function returns a data object;
    // the first three components are extracted into the
    // image, title, and subtitle variables.

    // ...
}

डेटा क्लास की मदद से कई काम की सुविधाएं भी मिलती हैं. उदाहरण के लिए, जब डेटा क्लास को परिभाषित करने की कोशिश करते हैं, तो कंपाइलर अपने-आप ही इस तरह के उपयोगी फ़ंक्शन को परिभाषित करता है equals() और copy(). ज़्यादा जानकारी के लिए, डेटा में क्लास के दस्तावेज़ देखें.

सिंगलटन ऑब्जेक्ट

Kotlin की मदद से, सिंगलटन के बारे में एलान करना आसान हो जाता है. ये क्लास ऐसी क्लास होती हैं जिनमें हमेशा एक और नहीं होगा. ये सिंगलटन object कीवर्ड के साथ तय किए जाते हैं. कंपोज़िशन में अक्सर ऐसी चीज़ों का इस्तेमाल किया जाता है. उदाहरण के लिए, MaterialTheme है सिंगलटन ऑब्जेक्ट के तौर पर परिभाषित किया जाएगा; MaterialTheme.colors, shapes, और typography प्रॉपर्टी में मौजूदा थीम की वैल्यू शामिल होती हैं.

टाइप-सेफ़ बिल्डर और डीएसएल

Kotlin की मदद से, डोमेन के हिसाब से भाषाएं (डीएसएल) बनाई जा सकती हैं आसान तरीके से ब्राउज़ करें. डीएसएल की मदद से, जटिल हैरारकी वाला डेटा बनाया जा सकता है रखरखाव और आसानी से पढ़ा जा सकता है.

Jetpack Compose कुछ एपीआई के लिए डीएसएल का इस्तेमाल करता है, जैसे कि LazyRow और LazyColumn.

@Composable
fun MessageList(messages: List<Message>) {
    LazyColumn {
        // Add a single item as a header
        item {
            Text("Message List")
        }

        // Add list of messages
        items(messages) { message ->
            Message(message)
        }
    }
}

Kotlin, ऐसे बिल्डर का इस्तेमाल करने की गारंटी देती है जो टाइप-सेफ़्टी होते हैं रिसीवर के साथ फ़ंक्शन लिटरल. अगर हम Canvas उदाहरण के लिए, यह एक पैरामीटर के रूप में DrawScope पाने वाले के रूप में, onDraw: DrawScope.() -> Unit, जिससे कोड के ब्लॉक को कॉल वाले सदस्य के फ़ंक्शन की जानकारी DrawScope में दी गई है.

Canvas(Modifier.size(120.dp)) {
    // Draw grey background, drawRect function is provided by the receiver
    drawRect(color = Color.Gray)

    // Inset content by 10 pixels on the left/right sides
    // and 12 by the top/bottom
    inset(10.0f, 12.0f) {
        val quadrantSize = size / 2.0f

        // Draw a rectangle within the inset bounds
        drawRect(
            size = quadrantSize,
            color = Color.Red
        )

        rotate(45.0f) {
            drawRect(size = quadrantSize, color = Color.Blue)
        }
    }
}

टाइप-सेफ़ बिल्डर और डीएसएल के बारे में ज़्यादा जानें Kotlin के दस्तावेज़.

Kotlin कोरूटीन

कोरूटीन, इन भाषाओं के स्तर पर एसिंक्रोनस प्रोग्रामिंग की सुविधा देती हैं Kotlin. कोरूटीन, थ्रेड को ब्लॉक किए बिना एक्ज़ीक्यूशन को निलंबित कर सकते हैं. ऐप्लिकेशन रिस्पॉन्सिव यूज़र इंटरफ़ेस (यूआई) मूल रूप से एसिंक्रोनस होता है. Jetpack Compose इस समस्या को हल करता है कॉलबैक के बजाय, एपीआई लेवल पर कोरूटीन को अपनाना.

Jetpack Compose ऐसे एपीआई ऑफ़र करता है जो यूज़र इंटरफ़ेस (यूआई) लेयर में कोरूटीन के इस्तेमाल को सुरक्षित बनाते हैं. rememberCoroutineScope फ़ंक्शन एक CoroutineScope दिखाता है, जिससे आप इवेंट हैंडलर और कॉल में कोरूटीन बना सकते हैं निलंबित एपीआई कंपोज़ करें. नीचे दिया गया उदाहरण देखें. ScrollState की animateScrollTo एपीआई.

// Create a CoroutineScope that follows this composable's lifecycle
val composableScope = rememberCoroutineScope()
Button(
    // ...
    onClick = {
        // Create a new coroutine that scrolls to the top of the list
        // and call the ViewModel to load data
        composableScope.launch {
            scrollState.animateScrollTo(0) // This is a suspend function
            viewModel.loadData()
        }
    }
) { /* ... */ }

कोरूटीन, कोड के ब्लॉक को डिफ़ॉल्ट रूप से एक क्रम में एक्ज़ीक्यूट करते हैं. दौड़ने के लिए कोरूटीन, जो सस्पेंड फ़ंक्शन को कॉल करता है. यह तब तक निलंबित हो जाता है, जब तक सस्पेंड फ़ंक्शन रिटर्न. यह तब भी लागू होता है, जब निलंबन फ़ंक्शन एक्ज़ीक्यूशन अलग-अलग CoroutineDispatcher में करना होगा. पिछले उदाहरण में, loadData फ़ंक्शन को तब तक एक्ज़ीक्यूट नहीं किया जा सकता, जब तक फ़ंक्शन animateScrollTo को निलंबित न कर दिया जाए वापस करना.

कोड को एक साथ चलाने के लिए, नए कोरूटीन बनाने होंगे. उदाहरण में का इस्तेमाल करें. इससे स्क्रीन के सबसे ऊपरी हिस्से तक स्क्रोल करने और viewModel, दो कोरूटीन की ज़रूरत होती है.

// Create a CoroutineScope that follows this composable's lifecycle
val composableScope = rememberCoroutineScope()
Button( // ...
    onClick = {
        // Scroll to the top and load data in parallel by creating a new
        // coroutine per independent work to do
        composableScope.launch {
            scrollState.animateScrollTo(0)
        }
        composableScope.launch {
            viewModel.loadData()
        }
    }
) { /* ... */ }

कोरूटीन की मदद से एसिंक्रोनस एपीआई को जोड़ना आसान हो जाता है. निम्न में उदाहरण के लिए, हम pointerInput मॉडिफ़ायर को ऐनिमेशन एपीआई के साथ मिलाते हैं, ताकि जब उपयोगकर्ता स्क्रीन पर टैप करे, तब एलिमेंट की पोज़िशन को ऐनिमेट करें.

@Composable
fun MoveBoxWhereTapped() {
    // Creates an `Animatable` to animate Offset and `remember` it.
    val animatedOffset = remember {
        Animatable(Offset(0f, 0f), Offset.VectorConverter)
    }

    Box(
        // The pointerInput modifier takes a suspend block of code
        Modifier
            .fillMaxSize()
            .pointerInput(Unit) {
                // Create a new CoroutineScope to be able to create new
                // coroutines inside a suspend function
                coroutineScope {
                    while (true) {
                        // Wait for the user to tap on the screen
                        val offset = awaitPointerEventScope {
                            awaitFirstDown().position
                        }
                        // Launch a new coroutine to asynchronously animate to
                        // where the user tapped on the screen
                        launch {
                            // Animate to the pressed position
                            animatedOffset.animateTo(offset)
                        }
                    }
                }
            }
    ) {
        Text("Tap anywhere", Modifier.align(Alignment.Center))
        Box(
            Modifier
                .offset {
                    // Use the animated offset as the offset of this Box
                    IntOffset(
                        animatedOffset.value.x.roundToInt(),
                        animatedOffset.value.y.roundToInt()
                    )
                }
                .size(40.dp)
                .background(Color(0xff3c1361), CircleShape)
        )
    }

कोरूटीन के बारे में ज़्यादा जानने के लिए, यहां जाएं: Android पर Kotlin कोरूटीन गाइड.