कंपोज़ेबल का लाइफ़साइकल

इस पेज पर, आपको कंपोज़ेबल की लाइफ़साइकल के बारे में जानकारी मिलेगी. साथ ही, यह भी पता चलेगा कि Compose कैसे तय करता है कि किसी कंपोज़ेबल को फिर से कंपोज़ करने की ज़रूरत है या नहीं.

लाइफ़साइकल की खास जानकारी

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

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

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

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

आम तौर पर, रीकंपोज़िशन की प्रोसेस तब ट्रिगर होती है, जब State<T> ऑब्जेक्ट में कोई बदलाव होता है. Compose, इन बदलावों को ट्रैक करता है और कंपोज़िशन में मौजूद उन सभी कंपोज़ेबल को चलाता है जो उस State<T> को पढ़ते हैं. साथ ही, यह उन सभी कंपोज़ेबल को भी चलाता है जिन्हें वे कॉल करते हैं और जिन्हें स्किप नहीं किया जा सकता.

अगर किसी कंपोज़ेबल को कई बार कॉल किया जाता है, तो कंपोज़िशन में कई इंस्टेंस जोड़े जाते हैं. कंपोज़िशन में हर कॉल का अपना लाइफ़साइकल होता है.

@Composable
fun MyComposable() {
    Column {
        Text("Hello")
        Text("World")
    }
}

पिछले कोड स्निपेट में मौजूद एलिमेंट के क्रम को दिखाने वाला डायग्राम
दूसरी इमेज. कंपोज़िशन में MyComposable का प्रज़ेंटेशन. अगर किसी कंपोज़ेबल को कई बार कॉल किया जाता है, तो कंपोज़िशन में कई इंस्टेंस डाले जाते हैं. किसी एलिमेंट का रंग अलग होने का मतलब है कि वह एक अलग इंस्टेंस है.

कंपोज़िशन में कंपोज़ेबल की बनावट

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

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

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

नीचे दिए गए उदाहरण पर ध्यान दें:

@Composable
fun LoginScreen(showError: Boolean) {
    if (showError) {
        LoginError()
    }
    LoginInput() // This call site affects where LoginInput is placed in Composition
}

@Composable
fun LoginInput() { /* ... */ }

@Composable
fun LoginError() { /* ... */ }

ऊपर दिए गए कोड स्निपेट में, LoginScreen शर्त के हिसाब से LoginError कंपोज़ेबल को कॉल करेगा और हमेशा LoginInput कंपोज़ेबल को कॉल करेगा. हर कॉल की एक यूनीक कॉल साइट और सोर्स पोज़िशन होती है. कंपाइलर इसका इस्तेमाल करके, कॉल की यूनीक तरीके से पहचान करेगा.

इस डायग्राम में दिखाया गया है कि showError फ़्लैग को true में बदलने पर, ऊपर दिए गए कोड को फिर से कैसे कंपोज़ किया जाता है. LoginError कंपोज़ेबल को जोड़ा गया है, लेकिन अन्य कंपोज़ेबल को फिर से कंपोज़ नहीं किया गया है.
तीसरी इमेज. स्टेट बदलने और फिर से कंपोज़िशन होने पर, कंपोज़िशन में LoginScreen को दिखाना. एक ही रंग का मतलब है कि उसे फिर से कंपोज़ नहीं किया गया है.

LoginInput को पहले कॉल किए जाने के बजाय, अब दूसरे नंबर पर कॉल किया जाता है. इसके बावजूद, LoginInput इंस्टेंस को रीकंपोज़िशन के दौरान सुरक्षित रखा जाएगा. इसके अलावा, LoginInput में ऐसा कोई पैरामीटर नहीं है जो रीकंपोज़िशन के दौरान बदला हो. इसलिए, Compose, LoginInput को कॉल नहीं करेगा.

स्मार्ट तरीके से रीकंपोज़ करने में मदद करने के लिए, अतिरिक्त जानकारी जोड़ें

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

@Composable
fun MoviesScreen(movies: List<Movie>) {
    Column {
        for (movie in movies) {
            // MovieOverview composables are placed in Composition given its
            // index position in the for loop
            MovieOverview(movie)
        }
    }
}

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

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

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

@Composable
fun MovieOverview(movie: Movie) {
    Column {
        // Side effect explained later in the docs. If MovieOverview
        // recomposes, while fetching the image is in progress,
        // it is cancelled and restarted.
        val image = loadNetworkImage(movie.url)
        MovieHeader(image)

        /* ... */
    }
}

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

आम तौर पर, हम MovieOverview इंस्टेंस की पहचान को, उसे पास किए गए movie की पहचान से लिंक मानते हैं. अगर हम फ़िल्मों की सूची का क्रम बदलते हैं, तो हमें कंपोज़िशन ट्री में मौजूद इंस्टेंस का क्रम भी बदलना चाहिए. ऐसा करने से, हमें हर MovieOverview कंपोज़ेबल को अलग-अलग फ़िल्म इंस्टेंस के साथ फिर से कंपोज़ नहीं करना पड़ेगा. Compose की मदद से, रनटाइम को यह बताया जा सकता है कि ट्री के किसी हिस्से की पहचान करने के लिए, आपको किन वैल्यू का इस्तेमाल करना है: key कंपोज़ेबल.

कोड के ब्लॉक को, एक या उससे ज़्यादा वैल्यू के साथ पास किए गए key composable के कॉल के साथ रैप करके, उन वैल्यू को एक साथ जोड़ दिया जाएगा. इसके बाद, कंपोज़िशन में उस इंस्टेंस की पहचान करने के लिए उनका इस्तेमाल किया जाएगा. key की वैल्यू ग्लोबल तौर पर यूनीक होना ज़रूरी नहीं है. इसे सिर्फ़ कॉल साइट पर कंपोज़ेबल के इन्वोकेशन के बीच यूनीक होना चाहिए. इसलिए, इस उदाहरण में हर movie के पास एक ऐसा key होना चाहिए जो movies में यूनीक हो. अगर वह key, ऐप्लिकेशन में किसी अन्य जगह पर मौजूद किसी अन्य कंपोज़ेबल के साथ शेयर किया जाता है, तो कोई समस्या नहीं है.

@Composable
fun MoviesScreenWithKey(movies: List<Movie>) {
    Column {
        for (movie in movies) {
            key(movie.id) { // Unique ID for this movie
                MovieOverview(movie)
            }
        }
    }
}

ऊपर दिए गए उदाहरण में, सूची में मौजूद एलिमेंट बदलने पर भी Compose, MovieOverview के अलग-अलग कॉल को पहचान लेता है और उनका फिर से इस्तेमाल कर सकता है.

इस डायग्राम में दिखाया गया है कि अगर सूची में सबसे ऊपर कोई नया एलिमेंट जोड़ा जाता है, तो ऊपर दिए गए कोड को फिर से कैसे कंपोज़ किया जाता है. सूची के आइटम की पहचान कुंजियों से की जाती है. इसलिए, Compose को पता होता है कि उन्हें फिर से कंपोज़ नहीं करना है. भले ही, उनकी जगह बदल गई हो.
छठी इमेज. सूची में नया एलिमेंट जोड़ने पर, कंपोज़िशन में MoviesScreen को दिखाने का तरीका. MovieOverview कंपोज़ेबल में यूनीक कुंजियां होती हैं. इसलिए, Compose यह पहचान लेता है कि MovieOverview के किन इंस्टेंस में बदलाव नहीं हुआ है. साथ ही, वह उनका फिर से इस्तेमाल कर सकता है. उनके साइड इफ़ेक्ट लागू होते रहेंगे.

कुछ कंपोज़ेबल में, key कंपोज़ेबल के लिए पहले से सहायता मौजूद होती है. उदाहरण के लिए, LazyColumn, items डीएसएल में कस्टम key तय करने की अनुमति देता है.

@Composable
fun MoviesScreenLazy(movies: List<Movie>) {
    LazyColumn {
        items(movies, key = { movie -> movie.id }) { movie ->
            MovieOverview(movie)
        }
    }
}

इनपुट में बदलाव न होने पर, इस चरण को स्किप किया जा रहा है

रीकंपोज़िशन के दौरान, ज़रूरी शर्तें पूरी करने वाले कुछ कंपोज़ेबल फ़ंक्शन को पूरी तरह से स्किप किया जा सकता है. ऐसा तब होता है, जब उनके इनपुट में पिछले कंपोज़िशन के बाद से कोई बदलाव न हुआ हो.

ऐप्लिकेशन बनाने की सुविधा को स्किप किया जा सकता है. हालांकि, ऐसा तब नहीं किया जा सकता, जब:

  • फ़ंक्शन का रिटर्न टाइप Unit नहीं है
  • फ़ंक्शन को @NonRestartableComposable या @NonSkippableComposable के साथ एनोटेट किया गया है
  • ज़रूरी पैरामीटर, नॉन-स्टेबल टाइप का है

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

किसी टाइप को स्टेबल माना जाए, इसके लिए उसे इस कॉन्ट्रैक्ट का पालन करना होगा:

  • दो इंस्टेंस के लिए equals का नतीजा, उन दो इंस्टेंस के लिए हमेशा एक जैसा रहेगा.
  • अगर टाइप की कोई सार्वजनिक प्रॉपर्टी बदलती है, तो कंपोज़िशन को इसकी सूचना दी जाएगी.
  • सभी सार्वजनिक प्रॉपर्टी टाइप भी स्टेबल होते हैं.

इस कॉन्ट्रैक्ट में कुछ अहम सामान्य टाइप शामिल हैं जिन्हें Compose कंपाइलर, स्टेबल के तौर पर ट्रीट करेगा. भले ही, उन्हें @Stable एनोटेशन का इस्तेमाल करके, साफ़ तौर पर स्टेबल के तौर पर मार्क न किया गया हो:

  • सभी प्रिमिटिव वैल्यू टाइप: Boolean, Int, Long, Float, Char वगैरह.
  • स्ट्रिंग
  • सभी फ़ंक्शन टाइप (लैम्डा)

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

एक अहम टाइप, जो स्टेबल है, लेकिन म्यूटेबल है, वह Compose का MutableState टाइप है. अगर किसी वैल्यू को MutableState में रखा जाता है, तो पूरे स्टेट ऑब्जेक्ट को स्टेबल माना जाता है. ऐसा इसलिए, क्योंकि State की .value प्रॉपर्टी में होने वाले किसी भी बदलाव की सूचना Compose को दी जाएगी.

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

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

अगर Compose को यह पता नहीं चल पाता है कि कोई टाइप स्टेबल है या नहीं, लेकिन आपको Compose को उसे स्टेबल के तौर पर ट्रीट करने के लिए मजबूर करना है, तो उसे @Stable एनोटेशन के साथ मार्क करें.

// Marking the type as stable to favor skipping and smart recompositions.
@Stable
interface UiState<T : Result<T>> {
    val value: T?
    val exception: Throwable?

    val hasError: Boolean
        get() = exception != null
}

ऊपर दिए गए कोड स्निपेट में, UiState एक इंटरफ़ेस है. इसलिए, Compose आम तौर पर इस टाइप को स्टेबल नहीं मान सकता. @Stable एनोटेशन जोड़कर, Compose को यह बताया जाता है कि यह टाइप स्टेबल है. इससे Compose, स्मार्ट रीकंपोज़िशन को प्राथमिकता दे पाता है. इसका मतलब यह भी है कि अगर इंटरफ़ेस का इस्तेमाल पैरामीटर टाइप के तौर पर किया जाता है, तो Compose अपने सभी लागू किए गए तरीकों को स्टेबल मानेगा.