Jetpack Compose, Kotlin पर आधारित है. कुछ मामलों में, Kotlin खास इडियम उपलब्ध कराता है. इनकी मदद से, Compose का अच्छा कोड लिखना आसान हो जाता है. अगर आपको किसी दूसरी प्रोग्रामिंग लैंग्वेज के बारे में जानकारी है और आपने उस लैंग्वेज को Kotlin में ट्रांसलेट कर लिया है, तो हो सकता है कि आपको Compose की कुछ सुविधाओं के बारे में पता न हो. साथ ही, आपको Kotlin के मुहावरों का इस्तेमाल करके लिखे गए कोड को समझने में मुश्किल हो सकती है. Kotlin की स्टाइल के बारे में ज़्यादा जानने से, आपको इन समस्याओं से बचने में मदद मिल सकती है.
डिफ़ॉल्ट आर्ग्युमेंट
Kotlin फ़ंक्शन लिखते समय, फ़ंक्शन के आर्ग्युमेंट के लिए डिफ़ॉल्ट वैल्यू तय की जा सकती हैं. इनका इस्तेमाल तब किया जाता है, जब कॉल करने वाला व्यक्ति उन वैल्यू को साफ़ तौर पर पास नहीं करता है. इस सुविधा से, ज़्यादा फ़ंक्शन इस्तेमाल करने की ज़रूरत कम हो जाती है.
उदाहरण के लिए, मान लीजिए कि आपको एक ऐसा फ़ंक्शन लिखना है जो एक स्क्वेयर बनाता है. इस फ़ंक्शन में एक ज़रूरी पैरामीटर, sideLength हो सकता है. इससे हर साइड की लंबाई तय की जाती है. इसमें कई वैकल्पिक पैरामीटर हो सकते हैं, जैसे कि thickness, 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 में हाई-ऑर्डर फ़ंक्शन इस्तेमाल किए जा सकते हैं. ये ऐसे फ़ंक्शन होते हैं जो अन्य फ़ंक्शन को पैरामीटर के तौर पर लेते हैं. Compose इसी तरीके पर काम करता है. उदाहरण के लिए, Button
कंपोज़ेबल फ़ंक्शन, onClick
लैंबडा पैरामीटर उपलब्ध कराता है. उस पैरामीटर की वैल्यू एक फ़ंक्शन होती है. जब उपयोगकर्ता बटन पर क्लिक करता है, तब बटन इस फ़ंक्शन को कॉल करता है:
Button( // ... onClick = myClickFunction ) // ...
हाई-ऑर्डर फ़ंक्शन, लैम्डा एक्सप्रेशन के साथ काम करते हैं. ये ऐसे एक्सप्रेशन होते हैं जिनका आकलन किसी फ़ंक्शन के तौर पर किया जाता है. अगर आपको फ़ंक्शन का इस्तेमाल सिर्फ़ एक बार करना है, तो आपको इसे किसी दूसरी जगह पर तय करने की ज़रूरत नहीं है, ताकि इसे हाई-ऑर्डर फ़ंक्शन में भेजा जा सके. इसके बजाय, लैम्डा एक्सप्रेशन का इस्तेमाल करके, फ़ंक्शन को वहीं पर तय किया जा सकता है. पिछले उदाहरण में यह माना गया है कि myClickFunction()
को कहीं और तय किया गया है. हालांकि, अगर आपको यहां सिर्फ़ उस फ़ंक्शन का इस्तेमाल करना है, तो लैम्डा एक्सप्रेशन के साथ फ़ंक्शन को इनलाइन तरीके से तय करना ज़्यादा आसान है:
Button( // ... onClick = { // do something // do something else } ) { /* ... */ }
ट्रेलिंग लैम्डा
Kotlin, हाई-ऑर्डर फ़ंक्शन को कॉल करने के लिए खास सिंटैक्स उपलब्ध कराता है. इन फ़ंक्शन का आखिरी पैरामीटर, लैम्डा होता है. अगर आपको उस पैरामीटर के तौर पर 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
पैरामीटर को पास किया जाता है.
दरअसल, अगर पास किया जा रहा only पैरामीटर, ट्रेलिंग लैम्डा है, तो ब्रैकेट को पूरी तरह से हटाया जा सकता है. इसका मतलब है कि अगर आखिरी पैरामीटर लैम्डा है और कोई अन्य पैरामीटर पास नहीं किया जा रहा है, तो ब्रैकेट को पूरी तरह से हटाया जा सकता है. उदाहरण के लिए, मान लें कि आपको Column
में कोई मॉडिफ़ायर पास नहीं करना है. कोड को इस तरह लिखा जा सकता है:
Column { Text("Some text") Text("Some more text") Text("Last text") }
यह सिंटैक्स, Compose में काफ़ी आम है. खास तौर पर, लेआउट एलिमेंट जैसे कि
Column
के लिए. आखिरी पैरामीटर, एलिमेंट के बच्चों को तय करने वाला लैम्डा एक्सप्रेशन होता है. साथ ही, उन बच्चों को फ़ंक्शन कॉल के बाद कर्ली ब्रेसिज़ में तय किया जाता है.
स्कोप और रिसीवर
कुछ तरीके और प्रॉपर्टी, सिर्फ़ किसी खास स्कोप में उपलब्ध होती हैं. स्कोप सीमित होने से, आपको उस जगह पर सुविधा देने में मदद मिलती है जहां इसकी ज़रूरत है. साथ ही, इससे आपको उस जगह पर गलती से सुविधा का इस्तेमाल करने से बचने में मदद मिलती है जहां यह सही नहीं है.
Compose में इस्तेमाल किए गए उदाहरण पर विचार करें. Row
लेआउट कंपोज़ेबल को कॉल करने पर, आपका कॉन्टेंट लैम्डा, 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) ) }
कुछ एपीआई, लैम्डा स्वीकार करते हैं. इन्हें रिसीवर स्कोप में कॉल किया जाता है. इन लैम्डा के पास ऐसी प्रॉपर्टी और फ़ंक्शन का ऐक्सेस होता है जिन्हें पैरामीटर के एलान के आधार पर, कहीं और तय किया गया है:
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
आपको अक्सर इस तरह का कोड, कंपोज़ फ़ंक्शन में दिखेगा:
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
कीवर्ड के साथ एलान किया जाता है.
Compose अक्सर ऐसे ऑब्जेक्ट का इस्तेमाल करता है. उदाहरण के लिए,
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
दिखाता है. इसकी मदद से, इवेंट हैंडलर में कोरूटीन बनाए जा सकते हैं और Compose के सस्पेंड एपीआई को कॉल किया जा सकता है. ScrollState
के animateScrollTo
API का इस्तेमाल करके, यहां दिया गया उदाहरण देखें.
// 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() } } ) { /* ... */ }
डिफ़ॉल्ट रूप से, कोरूटीन कोड के ब्लॉक को क्रम से एक्ज़ीक्यूट करते हैं. चल रही कोई कोरूटीन, सस्पेंड फ़ंक्शन को कॉल करती है. इसके बाद, सस्पेंड फ़ंक्शन के नतीजे मिलने तक, कोरूटीन का एक्ज़ीक्यूशन सस्पेंड हो जाता है. ऐसा तब भी होता है, जब suspend फ़ंक्शन, एक्ज़ीक्यूशन को किसी दूसरे CoroutineDispatcher
पर ले जाता है. पिछले उदाहरण में, जब तक निलंबन फ़ंक्शन animateScrollTo
वैल्यू नहीं दिखाता, तब तक loadData
को लागू नहीं किया जाएगा.
एक साथ कई कोड चलाने के लिए, नई कोरुटीन बनानी होंगी. ऊपर दिए गए उदाहरण में, स्क्रीन के सबसे ऊपर तक स्क्रोल करने और 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 कोरूटीन गाइड देखें.
आपके लिए सुझाव
- ध्यान दें: JavaScript बंद होने पर लिंक टेक्स्ट दिखता है
- मटीरियल कॉम्पोनेंट और लेआउट
- लिखने में मदद करने वाली सुविधा के बुरे असर
- ईमेल लिखने के लेआउट के बारे में बुनियादी बातें