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

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

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

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

जब Jetpack Compose, कंपोज़ेबल को पहली बार चलाता है, तो शुरुआती कंपोज़िशन के दौरान, यह उन कंपोज़ेबल को ट्रैक करता है जिन्हें आपने कंपोज़िशन में अपने यूज़र इंटरफ़ेस (यूआई) के बारे में बताने के लिए कॉल किया है. इसके बाद, जब आपके ऐप्लिकेशन की स्थिति बदलती है, तब Jetpack Compose, recomposition को शेड्यूल करता है. रीकंपोज़िशन तब होता है, जब 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)
        }
    }
}

ऊपर दिए गए उदाहरण में, कंपोज़िशन में इंस्टेंस को अलग रखने के लिए, कंपोज़ कॉल साइट के साथ-साथ एक्ज़ीक्यूशन के क्रम का इस्तेमाल करता है. अगर सूची के आखिरी में कोई नया movie जोड़ा जाता है, तो कंपोज़, कंपोज़िशन में पहले से मौजूद इंस्टेंस का फिर से इस्तेमाल कर सकता है. ऐसा इसलिए, क्योंकि सूची में उनकी जगह नहीं बदली है. इसलिए, उन इंस्टेंस के लिए 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 की वैल्यू का ग्लोबल स्तर पर यूनीक होना ज़रूरी नहीं है. यह सिर्फ़ कॉल साइट पर कंपोज़ेबल के इनवोकेशन के बीच यूनीक होनी चाहिए. इसलिए, इस उदाहरण में हर 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)
            }
        }
    }
}

ऊपर दी गई जानकारी के हिसाब से, सूची में मौजूद एलिमेंट में बदलाव होने पर भी, कंपोज़ करने की सुविधा 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 का नतीजा, उन दो इंस्टेंस के लिए हमेशा एक जैसा रहेगा.
  • अगर किसी सार्वजनिक प्रॉपर्टी का टाइप बदलता है, तो Composition को इसकी सूचना दी जाएगी.
  • सभी सार्वजनिक प्रॉपर्टी टाइप भी स्टेबल हैं.

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

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

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

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

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

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 अपने सभी लागू करने के तरीकों को स्थिर मानेगा.