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

इस पेज पर, आपको कॉम्पोज़ेबल के लाइफ़साइकल के बारे में जानकारी मिलेगी. साथ ही, यह भी पता चलेगा कि 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 फ़्लैग को &#39;सही&#39; पर सेट करने पर, पहले के कोड को फिर से कैसे बनाया जाता है. LoginError कॉम्पोज़ेबल जोड़ा गया है, लेकिन अन्य कॉम्पोज़ेबल फिर से कॉम्पोज़ नहीं किए गए हैं.

तीसरी इमेज. जब स्थिति बदलती है और फिर से कॉम्पोज़िशन होता है, तो कॉम्पोज़िशन में LoginScreen का रेप्रज़ेंटेशन. एक ही रंग का मतलब है कि उसे फिर से कॉम्पोज़ नहीं किया गया है.

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

स्मार्ट रीकंपोज़िशन की सुविधा को बेहतर बनाने के लिए ज़्यादा जानकारी जोड़ना

किसी कॉम्पोज़ेबल को कई बार कॉल करने पर, वह कंपोज़िशन में भी कई बार जुड़ जाएगा. जब किसी कॉल साइट से किसी कॉम्पोज़ेबल को कई बार कॉल किया जाता है, तो 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 composable.

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