Jetpack Compose के चरण

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

Compose के दस्तावेज़ में, Thinking in Compose और State and Jetpack Compose में कंपोज़िशन के बारे में बताया गया है.

फ़्रेम के तीन चरण

Compose के तीन मुख्य फ़ेज़ होते हैं:

  1. कंपोज़िशन: कौनसा यूज़र इंटरफ़ेस (यूआई) दिखाना है. Compose, कंपोज़ेबल फ़ंक्शन चलाता है और आपके यूज़र इंटरफ़ेस (यूआई) का ब्यौरा बनाता है.
  2. लेआउट: यूज़र इंटरफ़ेस (यूआई) को कहां रखना है. इस चरण में दो काम शामिल हैं: मेज़रमेंट और प्लेसमेंट. लेआउट एलिमेंट, लेआउट ट्री में मौजूद हर नोड के लिए, खुद को और किसी भी चाइल्ड एलिमेंट को 2D कोऑर्डिनेट में मेज़र और प्लेस करते हैं.
  3. ड्रॉइंग: यह कैसे रेंडर होती है. यूज़र इंटरफ़ेस (यूआई) एलिमेंट, कैनवस पर ड्रॉ होते हैं. आम तौर पर, यह कैनवस डिवाइस की स्क्रीन होती है.
तीन चरण, जिनमें Compose, डेटा को यूज़र इंटरफ़ेस (यूआई) में बदलता है. ये चरण इस क्रम में होते हैं: डेटा, कंपोज़िशन, लेआउट, ड्रॉइंग, और यूज़र इंटरफ़ेस (यूआई).
पहली इमेज. Compose, डेटा को यूज़र इंटरफ़ेस में बदलने के लिए तीन चरणों का इस्तेमाल करता है.

इन चरणों का क्रम आम तौर पर एक जैसा होता है. इससे डेटा को एक ही दिशा में फ़्लो करने की अनुमति मिलती है. जैसे, कंपोज़िशन से लेआउट, फिर ड्रॉइंग, और फिर फ़्रेम बनाना. इसे एक ही दिशा में डेटा फ़्लो भी कहा जाता है. BoxWithConstraints, LazyColumn, और LazyRow इसके अपवाद हैं. इनमें चाइल्ड कंपोज़ेबल का कंपोज़िशन, पैरंट कंपोज़ेबल के लेआउट फ़ेज़ पर निर्भर करता है.

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

माइग्रेशन के चरणों को समझना

इस सेक्शन में, कंपोज़ेबल के लिए Compose के तीनों फ़ेज़ को लागू करने के तरीके के बारे में ज़्यादा जानकारी दी गई है.

कंपोज़िशन

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

दूसरी इमेज. यह ट्री, आपके यूज़र इंटरफ़ेस (यूआई) को दिखाता है. इसे कंपोज़िशन फ़ेज़ में बनाया जाता है.

कोड और यूज़र इंटरफ़ेस (यूआई) ट्री का एक सब-सेक्शन इस तरह दिखता है:

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

इन उदाहरणों में, कोड में मौजूद हर composable फ़ंक्शन, यूज़र इंटरफ़ेस (यूआई) ट्री में मौजूद एक लेआउट नोड से मैप होता है. ज़्यादा जटिल उदाहरणों में, कंपोज़ेबल में लॉजिक और कंट्रोल फ़्लो शामिल हो सकते हैं. साथ ही, अलग-अलग स्थितियों के हिसाब से अलग-अलग ट्री जनरेट किए जा सकते हैं.

लेआउट

लेआउट फ़ेज़ में, Compose कंपोज़िशन फ़ेज़ में बनाए गए यूज़र इंटरफ़ेस (यूआई) ट्री का इस्तेमाल इनपुट के तौर पर करता है. लेआउट नोड के कलेक्शन में, हर नोड के साइज़ और 2D स्पेस में उसकी जगह तय करने के लिए ज़रूरी सारी जानकारी होती है.

चौथी इमेज. लेआउट फ़ेज़ के दौरान, यूज़र इंटरफ़ेस (यूआई) ट्री में हर लेआउट नोड का मेज़रमेंट और प्लेसमेंट.

लेआउट फ़ेज़ के दौरान, ट्री को तीन चरणों वाले इस एल्गोरिदम का इस्तेमाल करके ट्रैवर्स किया जाता है:

  1. चाइल्ड एलिमेंट को मेज़र करना: अगर कोई नोड मौजूद है, तो वह अपने चाइल्ड एलिमेंट को मेज़र करता है.
  2. खुद का साइज़ तय करना: इन मेज़रमेंट के आधार पर, नोड खुद का साइज़ तय करता है.
  3. चाइल्ड नोड को प्लेस करना: हर चाइल्ड नोड को नोड की अपनी पोज़िशन के हिसाब से प्लेस किया जाता है.

इस फ़ेज़ के आखिर में, हर लेआउट नोड में ये चीज़ें होती हैं:

  • असाइन की गई चौड़ाई और ऊंचाई
  • x, y कोऑर्डिनेट, जहां इसे ड्रॉ किया जाना चाहिए

पिछले सेक्शन में दिए गए यूज़र इंटरफ़ेस (यूआई) ट्री को याद करें:

पांच कंपोज़ेबल और उनसे बनने वाले यूज़र इंटरफ़ेस (यूआई) ट्री वाला कोड स्निपेट. इसमें चाइल्ड नोड, अपने पैरंट नोड से जुड़े हुए हैं

इस ट्री के लिए, एल्गोरिदम इस तरह काम करता है:

  1. Row अपने चाइल्ड एलिमेंट, Image और Column को मेज़र करता है.
  2. Image को मेज़र किया जाता है. इसका कोई चाइल्ड व्यू नहीं होता. इसलिए, यह अपना साइज़ खुद तय करता है और साइज़ की जानकारी Row को देता है.
  3. इसके बाद, Column को मेज़र किया जाता है. यह सबसे पहले अपने चाइल्ड एलिमेंट (दो Text कंपोज़ेबल) को मेज़र करता है.
  4. पहले Text को मेज़र किया जाता है. इसमें कोई चाइल्ड व्यू नहीं होता है. इसलिए, यह अपना साइज़ खुद तय करता है और Column को अपने साइज़ की जानकारी देता है.
    1. दूसरे Text को मेज़र किया जाता है. इसका कोई चाइल्ड नहीं है, इसलिए यह अपना साइज़ खुद तय करता है और इसकी जानकारी Column को देता है.
  5. Column, चाइल्ड एलिमेंट के साइज़ के हिसाब से अपना साइज़ तय करता है. यह चाइल्ड व्यू की ज़्यादा से ज़्यादा चौड़ाई और चाइल्ड व्यू की ऊंचाई के योग का इस्तेमाल करता है.
  6. Column अपने चाइल्ड एलिमेंट को अपने हिसाब से रखता है. यह उन्हें वर्टिकल तौर पर एक-दूसरे के नीचे रखता है.
  7. Row, चाइल्ड एलिमेंट के साइज़ के हिसाब से अपना साइज़ तय करता है. यह चाइल्ड व्यू की ज़्यादा से ज़्यादा ऊंचाई और चाइल्ड व्यू की चौड़ाई के योग का इस्तेमाल करता है. इसके बाद, यह अपने चाइल्ड एलिमेंट को प्लेस करता है.

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

ड्रॉइंग

ड्रॉइंग फ़ेज़ में, ट्री को फिर से ऊपर से नीचे की ओर ट्रैवर्स किया जाता है. साथ ही, हर नोड स्क्रीन पर बारी-बारी से खुद को ड्रॉ करता है.

पांचवीं इमेज. ड्रॉइंग फ़ेज़, स्क्रीन पर पिक्सल ड्रॉ करता है.

पिछले उदाहरण का इस्तेमाल करके, ट्री कॉन्टेंट को इस तरह से ड्रॉ किया जाता है:

  1. Row, अपने किसी भी कॉन्टेंट को ड्रॉ करता है, जैसे कि बैकग्राउंड का रंग.
  2. Image अपने-आप ड्रॉ हो जाता है.
  3. Column अपने-आप ड्रॉ हो जाता है.
  4. पहला और दूसरा Text, खुद को ड्रॉ करते हैं.

छठी इमेज. यूज़र इंटरफ़ेस (यूआई) ट्री और उसका ड्रॉ किया गया प्रज़ेंटेशन.

स्टेट रीड

जब पहले बताए गए किसी फ़ेज़ के दौरान, snapshot state की value को पढ़ा जाता है, तो Compose अपने-आप ट्रैक करता है कि value को पढ़ते समय वह क्या कर रहा था. इस ट्रैकिंग की मदद से, Compose को रीडर को फिर से लागू करने की अनुमति मिलती है. ऐसा तब होता है, जब स्टेट का value बदलता है. साथ ही, यह Compose में स्टेट को मॉनिटर करने की सुविधा का आधार होता है.

आम तौर पर, mutableStateOf() का इस्तेमाल करके स्टेट बनाई जाती है. इसके बाद, इसे दो में से किसी एक तरीके से ऐक्सेस किया जाता है: value प्रॉपर्टी को सीधे ऐक्सेस करके या Kotlin प्रॉपर्टी डेलिगेट का इस्तेमाल करके. इनके बारे में ज़्यादा जानने के लिए, कंपोज़ेबल में स्टेट लेख पढ़ें. इस गाइड के मुताबिक, "स्टेट रीड" का मतलब, ऐक्सेस करने के इन दोनों तरीकों में से किसी एक से है.

// State read without property delegate.
val paddingState: MutableState<Dp> = remember { mutableStateOf(8.dp) }
Text(
    text = "Hello",
    modifier = Modifier.padding(paddingState.value)
)

// State read with property delegate.
var padding: Dp by remember { mutableStateOf(8.dp) }
Text(
    text = "Hello",
    modifier = Modifier.padding(padding)
)

प्रॉपर्टी डेलिगेट के अंदर, "गेटर" और "सेटर" फ़ंक्शन का इस्तेमाल, स्टेट के value को ऐक्सेस और अपडेट करने के लिए किया जाता है. ये getter और setter फ़ंक्शन सिर्फ़ तब ट्रिगर होते हैं, जब प्रॉपर्टी को वैल्यू के तौर पर रेफ़र किया जाता है. ये फ़ंक्शन, प्रॉपर्टी बनाते समय ट्रिगर नहीं होते. इसलिए, पहले बताए गए दोनों तरीके एक जैसे हैं.

कोड का हर वह ब्लॉक जो रीड स्टेट में बदलाव होने पर फिर से लागू किया जा सकता है, रीस्टार्ट स्कोप होता है. Compose, अलग-अलग फ़ेज़ में स्टेट value में होने वाले बदलावों और रीस्टार्ट स्कोप को ट्रैक करता है.

फ़ेज़ के हिसाब से स्टेट रीड

जैसा कि पहले बताया गया है, Compose में तीन मुख्य फ़ेज़ होते हैं. Compose, हर फ़ेज़ में रीड की गई स्टेट को ट्रैक करता है. इससे Compose, सिर्फ़ उन फ़ेज़ को सूचना दे पाता है जिनमें आपके यूज़र इंटरफ़ेस (यूआई) के हर एलिमेंट के लिए काम करना ज़रूरी है.

यहां दिए गए सेक्शन में, हर फ़ेज़ के बारे में बताया गया है. साथ ही, यह भी बताया गया है कि किसी फ़ेज़ में स्टेट वैल्यू को रीड करने पर क्या होता है.

पहला चरण: कंपोज़िशन

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

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

var padding by remember { mutableStateOf(8.dp) }
Text(
    text = "Hello",
    // The `padding` state is read in the composition phase
    // when the modifier is constructed.
    // Changes in `padding` will invoke recomposition.
    modifier = Modifier.padding(padding)
)

दूसरा चरण: लेआउट

लेआउट फ़ेज़ में दो चरण होते हैं: मेज़रमेंट और प्लेसमेंट. मेज़रमेंट का चरण, Layout कंपोज़ेबल को पास किए गए मेज़र लैम्डा को चलाता है. साथ ही, LayoutModifier इंटरफ़ेस के MeasureScope.measure तरीके के साथ-साथ अन्य तरीकों को भी चलाता है. प्लेसमेंट स्टेप, layout फ़ंक्शन के प्लेसमेंट ब्लॉक, Modifier.offset { … } के लैम्ब्डा ब्लॉक, और मिलते-जुलते फ़ंक्शन को चलाता है.

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

var offsetX by remember { mutableStateOf(8.dp) }
Text(
    text = "Hello",
    modifier = Modifier.offset {
        // The `offsetX` state is read in the placement step
        // of the layout phase when the offset is calculated.
        // Changes in `offsetX` restart the layout.
        IntOffset(offsetX.roundToPx(), 0)
    }
)

तीसरा चरण: ड्रॉइंग

ड्रॉइंग कोड के दौरान स्टेट रीड, ड्रॉइंग फ़ेज़ पर असर डालते हैं. इसके सामान्य उदाहरणों में Canvas(), Modifier.drawBehind, और Modifier.drawWithContent शामिल हैं. जब राज्य का value बदलता है, तो Compose यूज़र इंटरफ़ेस (यूआई) सिर्फ़ ड्रॉ फ़ेज़ को चलाता है.

var color by remember { mutableStateOf(Color.Red) }
Canvas(modifier = modifier) {
    // The `color` state is read in the drawing phase
    // when the canvas is rendered.
    // Changes in `color` restart the drawing.
    drawRect(color)
}

डायग्राम में दिखाया गया है कि ड्रॉ फ़ेज़ के दौरान रीड की गई स्टेट, ड्रॉ फ़ेज़ को फिर से ट्रिगर करती है.

स्टेट रीड को ऑप्टिमाइज़ करना

Compose, स्थानीय स्थिति को ट्रैक करता है. इसलिए, हर स्थिति को सही फ़ेज़ में पढ़कर, काम को कम किया जा सकता है.

नीचे दिया गया उदाहरण देखें. इस उदाहरण में एक Image() है, जो ऑफ़सेट मॉडिफ़ायर का इस्तेमाल करके, अपने लेआउट की आखिरी पोज़िशन को ऑफ़सेट करता है. इससे उपयोगकर्ता के स्क्रोल करने पर, पैरालैक्स इफ़ेक्ट दिखता है.

Box {
    val listState = rememberLazyListState()

    Image(
        // ...
        // Non-optimal implementation!
        Modifier.offset(
            with(LocalDensity.current) {
                // State read of firstVisibleItemScrollOffset in composition
                (listState.firstVisibleItemScrollOffset / 2).toDp()
            }
        )
    )

    LazyColumn(state = listState) {
        // ...
    }
}

यह कोड काम करता है, लेकिन इससे परफ़ॉर्मेंस बेहतर नहीं होती. कोड में लिखे गए तरीके के मुताबिक, यह firstVisibleItemScrollOffset स्टेट का value पढ़ता है और उसे Modifier.offset(offset: Dp) फ़ंक्शन को पास करता है. उपयोगकर्ता के स्क्रोल करने पर, firstVisibleItemScrollOffset का value बदल जाएगा. जैसा कि आपने सीखा है, Compose किसी भी स्टेट रीड को ट्रैक करता है, ताकि वह रीडिंग कोड को फिर से शुरू (फिर से शुरू) कर सके. इस उदाहरण में, यह Box का कॉन्टेंट है.

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

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

लैम्ब्डा के साथ ऑफ़सेट

ऑफ़सेट मॉडिफ़ायर का एक और वर्शन उपलब्ध है: Modifier.offset(offset: Density.() -> IntOffset).

यह वर्शन, लैम्ब्डा पैरामीटर लेता है. इसमें लैम्ब्डा ब्लॉक, नतीजे के तौर पर ऑफ़सेट दिखाता है. कोड का इस्तेमाल करने के लिए, उसे अपडेट करें:

Box {
    val listState = rememberLazyListState()

    Image(
        // ...
        Modifier.offset {
            // State read of firstVisibleItemScrollOffset in Layout
            IntOffset(x = 0, y = listState.firstVisibleItemScrollOffset / 2)
        }
    )

    LazyColumn(state = listState) {
        // ...
    }
}

तो यह ज़्यादा बेहतर परफ़ॉर्म क्यों करता है? मॉडिफ़ायर को दिया गया लैम्ब्डा ब्लॉक, लेआउट फ़ेज़ के दौरान ट्रिगर होता है. खास तौर पर, लेआउट फ़ेज़ के प्लेसमेंट चरण के दौरान. इसका मतलब है कि कंपोज़िशन के दौरान, firstVisibleItemScrollOffset स्टेट को अब नहीं पढ़ा जाता. Compose, स्टेट को पढ़े जाने के समय को ट्रैक करता है. इस बदलाव का मतलब है कि अगर firstVisibleItemScrollOffset का value बदलता है, तो Compose को सिर्फ़ लेआउट और ड्रॉइंग फ़ेज़ को रीस्टार्ट करना होगा.

हालांकि, कंपोज़िशन फ़ेज़ में स्टेट को पढ़ना अक्सर ज़रूरी होता है. इसके बावजूद, कुछ मामलों में स्टेट में होने वाले बदलावों को फ़िल्टर करके, रीकंपोज़िशन की संख्या को कम किया जा सकता है. इस बारे में ज़्यादा जानने के लिए, derivedStateOf: एक या एक से ज़्यादा स्टेट ऑब्जेक्ट को दूसरी स्टेट में बदलना लेख पढ़ें.

रीकंपोज़िशन लूप (साइक्लिक फ़ेज़ डिपेंडेंसी)

इस गाइड में पहले बताया गया था कि Compose के फ़ेज़ हमेशा एक ही क्रम में शुरू होते हैं. साथ ही, एक ही फ़्रेम में रहते हुए पीछे जाने का कोई तरीका नहीं है. हालांकि, इससे ऐप्लिकेशन को अलग-अलग फ़्रेम में कंपोज़िशन लूप में जाने से नहीं रोका जा सकता. इस उदाहरण से समझें:

Box {
    var imageHeightPx by remember { mutableIntStateOf(0) }

    Image(
        painter = painterResource(R.drawable.rectangle),
        contentDescription = "I'm above the text",
        modifier = Modifier
            .fillMaxWidth()
            .onSizeChanged { size ->
                // Don't do this
                imageHeightPx = size.height
            }
    )

    Text(
        text = "I'm below the image",
        modifier = Modifier.padding(
            top = with(LocalDensity.current) { imageHeightPx.toDp() }
        )
    )
}

इस उदाहरण में, वर्टिकल कॉलम लागू किया गया है. इसमें इमेज सबसे ऊपर है और उसके नीचे टेक्स्ट है. यह इमेज का साइज़ पता करने के लिए, Modifier.onSizeChanged() का इस्तेमाल करता है. इसके बाद, टेक्स्ट को नीचे शिफ़्ट करने के लिए, Modifier.padding() का इस्तेमाल करता है. Px से Dp में असामान्य कन्वर्ज़न से पहले ही पता चलता है कि कोड में कोई समस्या है.

इस उदाहरण में समस्या यह है कि कोड, एक ही फ़्रेम में "फ़ाइनल" लेआउट तक नहीं पहुंचता. कोड कई फ़्रेम पर निर्भर करता है, जिससे ग़ैर-ज़रूरी काम होता है. साथ ही, उपयोगकर्ता के लिए स्क्रीन पर यूआई जंप करता है.

पहले फ़्रेम का कंपोज़िशन

पहले फ़्रेम के कंपोज़िशन फ़ेज़ के दौरान, imageHeightPx की शुरुआती वैल्यू 0 होती है. इसलिए, कोड टेक्स्ट को Modifier.padding(top = 0) के साथ दिखाता है. इसके बाद, लेआउट फ़ेज़ में onSizeChanged मॉडिफ़ायर का कॉलबैक ट्रिगर होता है. इससे imageHeightPx, इमेज की असल ऊंचाई के हिसाब से अपडेट हो जाता है. इसके बाद, कंपोज़ अगले फ़्रेम के लिए रीकंपोज़िशन शेड्यूल करता है. हालांकि, मौजूदा ड्रॉइंग फ़ेज़ के दौरान, टेक्स्ट 0 की पैडिंग के साथ रेंडर होता है, क्योंकि अपडेट की गई imageHeightPx वैल्यू अभी तक नहीं दिख रही है.

दूसरे फ़्रेम का कंपोज़िशन

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

इस डायग्राम में, रीकंपोज़िशन लूप दिखाया गया है. इसमें लेआउट फ़ेज़ में साइज़ बदलने पर, रीकंपोज़िशन ट्रिगर होता है. इसके बाद, लेआउट फिर से होता है.

यह उदाहरण बनावटी लग सकता है, लेकिन इस सामान्य पैटर्न से सावधान रहें:

  • Modifier.onSizeChanged(), onGloballyPositioned() या लेआउट से जुड़े कुछ अन्य ऑपरेशन
  • किसी राज्य की जानकारी अपडेट करना
  • उस स्टेट को लेआउट मॉडिफ़ायर (padding(), height() या इससे मिलते-जुलते) के इनपुट के तौर पर इस्तेमाल करें
  • दोहराए जाने की संभावना

ऊपर दिए गए सैंपल को ठीक करने के लिए, सही लेआउट प्रिमिटिव का इस्तेमाल करें. पिछले उदाहरण को Column() की मदद से लागू किया जा सकता है. हालांकि, आपके पास कोई ऐसा जटिल उदाहरण हो सकता है जिसके लिए कस्टम लेआउट की ज़रूरत हो. इसके लिए, आपको कस्टम लेआउट लिखना होगा. ज़्यादा जानकारी के लिए, कस्टम लेआउट गाइड देखें.

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

रीकंपोज़िशन लूप के बारे में ज़्यादा जानने और अलग-अलग फ़ेज़ में स्टेट में लिखने से बचने का तरीका जानने के लिए, Compose में बैकवर्ड राइट देखें.