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 composable फ़ंक्शन, onClick lambda पैरामीटर उपलब्ध कराता है. उस पैरामीटर की वैल्यू एक फ़ंक्शन होती है. जब उपयोगकर्ता बटन पर क्लिक करता है, तो बटन उस फ़ंक्शन को कॉल करता है:
Button( // ... onClick = myClickFunction ) // ...
हाई-ऑर्डर फ़ंक्शन, लैम्ब्डा एक्सप्रेशन के साथ आसानी से काम करते हैं. ये ऐसे एक्सप्रेशन होते हैं जो किसी फ़ंक्शन का आकलन करते हैं. अगर आपको फ़ंक्शन की ज़रूरत सिर्फ़ एक बार है, तो आपको इसे हायर-ऑर्डर फ़ंक्शन में पास करने के लिए, कहीं और तय करने की ज़रूरत नहीं है. इसके बजाय,
लैम्डा एक्सप्रेशन की मदद से, फ़ंक्शन को वहीं पर तय किया जा सकता है.
पिछले उदाहरण में यह माना गया है कि myClickFunction() को कहीं और तय किया गया है.
हालांकि, अगर आपको इस फ़ंक्शन का इस्तेमाल सिर्फ़ यहां करना है, तो लैम्डा एक्सप्रेशन के साथ इनलाइन फ़ंक्शन को तय करना ज़्यादा आसान है:
Button( // ... onClick = { // do something // do something else } ) { /* ... */ }
ट्रेलिंग लैम्ब्डा
Kotlin, हाई-ऑर्डर फ़ंक्शन को कॉल करने के लिए एक खास सिंटैक्स उपलब्ध कराता है. इसका आखिरी पैरामीटर लैम्डा होता है. अगर आपको उस पैरामीटर के तौर पर लैम्ब्डा एक्सप्रेशन पास करना है, तो ट्रेलिंग लैम्ब्डा सिंटैक्स का इस्तेमाल किया जा सकता है. लैम्ब्डा एक्सप्रेशन को ब्रैकेट में रखने के बजाय, उसे बाद में रखें. 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 पैरामीटर में पास किया जाता है.
अगर पास किया जाने वाला सिर्फ़ पैरामीटर, ट्रेलिंग लैम्डा है, तो ब्रैकेट को पूरी तरह से हटाया जा सकता है. इसका मतलब है कि अगर आखिरी पैरामीटर लैम्डा है और कोई दूसरा पैरामीटर पास नहीं किया जा रहा है, तो ब्रैकेट को पूरी तरह से हटाया जा सकता है. उदाहरण के लिए, मान लें कि आपको 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
आपको 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 कीवर्ड के साथ एलान किया जाता है.
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 में, Coroutines की मदद से एसिंक्रोनस प्रोग्रामिंग की जा सकती है. कोरूटीन, थ्रेड को ब्लॉक किए बिना एक्ज़ीक्यूशन को रोक सकते हैं. रिस्पॉन्सिव यूआई, एसिंक्रोनस होता है. Jetpack Compose, कॉलबैक का इस्तेमाल करने के बजाय, एपीआई लेवल पर कोरूटीन का इस्तेमाल करके इस समस्या को हल करता है.
Jetpack Compose ऐसे एपीआई उपलब्ध कराता है जो यूज़र इंटरफ़ेस (यूआई) लेयर में कोरूटीन का इस्तेमाल सुरक्षित बनाते हैं.
rememberCoroutineScope फ़ंक्शन, CoroutineScope दिखाता है. इसकी मदद से, इवेंट हैंडलर में कोरूटीन बनाए जा सकते हैं और Compose के सस्पेंड एपीआई को कॉल किया जा सकता है. 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() } } ) { /* ... */ }
कोरूटीन, कोड के ब्लॉक को डिफ़ॉल्ट रूप से क्रम से चलाते हैं. सस्पेंड फ़ंक्शन को कॉल करने वाला एक रनिंग कोरूटीन, सस्पेंड फ़ंक्शन के वापस आने तक अपने एक्ज़ीक्यूशन को सस्पेंड कर देता है. यह तब भी लागू होता है, जब suspend फ़ंक्शन, एक्ज़ीक्यूशन को किसी दूसरे 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 and animate // in the same block awaitPointerEventScope { val offset = 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 बंद होने पर, लिंक टेक्स्ट दिखता है
- मटीरियल कॉम्पोनेंट और लेआउट
- Compose में साइड-इफ़ेक्ट
- Compose लेआउट की बुनियादी बातें