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