स्थिरता से जुड़ी समस्याएं ठीक करना

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

तेज़ी से स्किप करने की सुविधा चालू करना

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

ज़्यादा जानकारी के लिए, ज़्यादा स्किप करने की सुविधा देखें.

क्लास को बदला न जा सकने वाला बनाएं

इसके अलावा, किसी अस्थिर क्लास को पूरी तरह से बदला नहीं जा सकने वाला बनाया जा सकता है.

  • बदला नहीं जा सकता: यह एक ऐसा टाइप होता है जिसमें उस टाइप का इंस्टेंस बनने के बाद, किसी भी प्रॉपर्टी की वैल्यू कभी नहीं बदल सकती. साथ ही, सभी तरीके रेफ़रेंशियल ट्रांसपैरेंट होते हैं.
    • पक्का करें कि क्लास की सभी प्रॉपर्टी, var के बजाय val हों और इम्यूटेबल टाइप की हों.
    • प्रिमिटिव टाइप, जैसे कि String, Int और Float हमेशा नहीं बदले जा सकते.
    • अगर ऐसा नहीं किया जा सकता, तो आपको बदलने वाली किसी भी प्रॉपर्टी के लिए Compose स्टेट का इस्तेमाल करना होगा.
  • स्टेबल: यह एक ऐसा टाइप है जिसमें बदलाव किया जा सकता है. अगर टाइप की किसी सार्वजनिक प्रॉपर्टी या तरीके के व्यवहार से, पिछले इनवोकेशन के मुकाबले अलग नतीजे मिलते हैं, तो Compose रनटाइम को इसकी जानकारी नहीं मिलती.

इम्यूटेबल कलेक्शन

Compose, किसी क्लास को अस्थिर तब मानता है, जब उसमें कलेक्शन होते हैं. परफ़ॉर्मेंस से जुड़ी समस्याओं का पता लगाना पेज पर बताया गया है कि Compose कंपाइलर, इस बात की पूरी तरह से पुष्टि नहीं कर सकता कि List, Map और Set जैसे कलेक्शन, वाकई में अपरिवर्तनीय हैं. इसलिए, वह उन्हें अस्थिर के तौर पर मार्क करता है.

इस समस्या को हल करने के लिए, ऐसे कलेक्शन का इस्तेमाल किया जा सकता है जिनमें बदलाव नहीं किया जा सकता. Compose कंपाइलर में, Kotlinx Immutable Collections के साथ काम करने की सुविधा शामिल है. इन कलेक्शन में बदलाव नहीं किया जा सकता. साथ ही, Compose कंपाइलर इन्हें इसी तरह से प्रोसेस करता है. यह लाइब्रेरी अब भी ऐल्फ़ा वर्शन में है. इसलिए, इसके एपीआई में बदलाव हो सकते हैं.

स्थिरता से जुड़ी समस्याओं का पता लगाने के लिए दिए गए दिशा-निर्देशों में, इस अस्थिर क्लास के बारे में फिर से सोचें:

unstable class Snack {
  
  unstable val tags: Set<String>
  
}

बदले न जा सकने वाले कलेक्शन का इस्तेमाल करके, tags को स्टेबल बनाया जा सकता है. क्लास में, tags के टाइप को ImmutableSet<String> में बदलें:

data class Snack{
    
    val tags: ImmutableSet<String> = persistentSetOf()
    
}

ऐसा करने के बाद, क्लास के सभी पैरामीटर में बदलाव नहीं किया जा सकता. साथ ही, Compose कंपाइलर क्लास को स्टेबल के तौर पर मार्क करता है.

Stable या Immutable की मदद से एनोटेट करना

ऐप्लिकेशन के हैंग या क्रैश होने से जुड़ी समस्याओं को ठीक करने के लिए, अस्थिर क्लास को @Stable या @Immutable के साथ एनोटेट किया जा सकता है.

किसी क्लास को एनोटेट करने का मतलब है कि कंपाइलर आपकी क्लास के बारे में जो अनुमान लगाता है उसे खारिज करना. यह !! ऑपरेटर इन Kotlin के जैसा ही है. आपको इन एनोटेशन का इस्तेमाल बहुत सावधानी से करना चाहिए. कंपाइलर के व्यवहार को बदलने से, आपको ऐसी गड़बड़ियों का सामना करना पड़ सकता है जिनके बारे में आपको पहले से पता नहीं होता. जैसे, जब आपको लगता है कि कंपोज़ेबल को फिर से कंपोज़ होना चाहिए, तब वह ऐसा नहीं करता.

अगर एनोटेशन के बिना ही आपकी क्लास को स्टेबल किया जा सकता है, तो आपको उसी तरीके से क्लास को स्टेबल करने की कोशिश करनी चाहिए.

नीचे दिए गए स्निपेट में, डेटा क्लास का एक छोटा सा उदाहरण दिया गया है. इसे इम्यूटेबल के तौर पर एनोटेट किया गया है:

@Immutable
data class Snack(

)

@Immutable या @Stable एनोटेशन का इस्तेमाल करने पर, Compose कंपाइलर Snack क्लास को स्टेबल के तौर पर मार्क करता है.

कलेक्शन में शामिल की गई एनोटेट की गई क्लास

मान लें कि आपके पास List<Snack> टाइप का पैरामीटर शामिल करने वाला एक कंपोज़ेबल है:

restartable scheme("[androidx.compose.ui.UiComposable]") fun HighlightedSnacks(
  
  unstable snacks: List<Snack>
  
)

Snack को @Immutable के साथ एनोटेट करने पर भी, Compose कंपाइलर HighlightedSnacks में मौजूद snacks पैरामीटर को अस्थिर के तौर पर मार्क करता है.

कलेक्शन टाइप के मामले में, पैरामीटर को क्लास जैसी ही समस्या का सामना करना पड़ता है. Compose कंपाइलर, List टाइप के पैरामीटर को हमेशा अस्थिर के तौर पर मार्क करता है. भले ही, वह स्टेबल टाइप का कलेक्शन हो.

किसी पैरामीटर को स्टेबल के तौर पर मार्क नहीं किया जा सकता. साथ ही, किसी कंपोज़ेबल को हमेशा स्किप किए जा सकने वाले के तौर पर एनोटेट नहीं किया जा सकता. आगे बढ़ने के कई रास्ते हैं.

कलेक्शन के ठीक से काम न करने की समस्या को हल करने के कई तरीके हैं. यहां दिए गए उप-अनुभागों में, इन अलग-अलग तरीकों के बारे में बताया गया है.

कॉन्फ़िगरेशन फ़ाइल

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

बदलाव न किया जा सकने वाला कलेक्शन

कंपाइल टाइम पर, बदलाव न किए जा सकने वाले डेटा को सुरक्षित रखने के लिए, List के बजाय kotlinx immutable collection का इस्तेमाल किया जा सकता है.

@Composable
private fun HighlightedSnacks(
    
    snacks: ImmutableList<Snack>,
    
)

Wrapper

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

@Immutable
data class SnackCollection(
   val snacks: List<Snack>
)

इसके बाद, इसका इस्तेमाल अपने कंपोज़ेबल में पैरामीटर के टाइप के तौर पर किया जा सकता है.

@Composable
private fun HighlightedSnacks(
    index: Int,
    snacks: SnackCollection,
    onSnackClick: (Long) -> Unit,
    modifier: Modifier = Modifier
)

समाधान

इनमें से कोई भी तरीका अपनाने के बाद, Compose कंपाइलर अब HighlightedSnacks कंपोज़ेबल को skippable और restartable, दोनों के तौर पर मार्क करता है.

restartable skippable scheme("[androidx.compose.ui.UiComposable]") fun HighlightedSnacks(
  stable index: Int
  stable snacks: ImmutableList<Snack>
  stable onSnackClick: Function1<Long, Unit>
  stable modifier: Modifier? = @static Companion
)

रीकंपोज़िशन के दौरान, अगर Compose के किसी भी इनपुट में बदलाव नहीं हुआ है, तो अब वह HighlightedSnacks को स्किप कर सकता है.

स्टेबिलिटी कॉन्फ़िगरेशन फ़ाइल

Compose Compiler 1.5.5 से, कंपाइल टाइम पर उन क्लास की कॉन्फ़िगरेशन फ़ाइल दी जा सकती है जिन्हें स्टेबल माना जाता है. इससे उन क्लास को स्टेबल माना जा सकता है जिन्हें कंट्रोल नहीं किया जा सकता. जैसे, स्टैंडर्ड लाइब्रेरी क्लास, जैसे कि LocalDateTime.

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

कॉन्फ़िगरेशन का उदाहरण:

// Consider LocalDateTime stable
java.time.LocalDateTime
// Consider my datalayer stable
com.datalayer.*
// Consider my datalayer and all submodules stable
com.datalayer.**
// Consider my generic type stable based off it's first type parameter only
com.example.GenericClass<*,_>

इस सुविधा को चालू करने के लिए, कॉन्फ़िगरेशन फ़ाइल का पाथ, Compose कंपाइलर Gradle प्लगिन के कॉन्फ़िगरेशन के composeCompiler options ब्लॉक में पास करें.

composeCompiler {
  stabilityConfigurationFile = rootProject.layout.projectDirectory.file("stability_config.conf")
}

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

एक से ज़्यादा मॉड्यूल

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

अगर आपकी यूज़र इंटरफ़ेस (यूआई) लेयर से अलग मॉड्यूल में डेटा लेयर है, तो आपको यह समस्या आ सकती है. हमारा सुझाव है कि आप ऐसा ही करें.

समाधान

इस समस्या को हल करने के लिए, इनमें से कोई एक तरीका आज़माएं:

  1. अपनी कंपाइलर कॉन्फ़िगरेशन फ़ाइल में क्लास जोड़ें.
  2. अपने डेटा लेयर मॉड्यूल पर Compose कंपाइलर चालू करें या अपनी क्लास को @Stable या @Immutable से टैग करें.
    • इसके लिए, आपको अपनी डेटा लेयर में Compose की डिपेंडेंसी जोड़नी होगी. हालांकि, यह सिर्फ़ कंपोज़ रनटाइम के लिए ज़रूरी है, Compose-UI के लिए नहीं.
  3. अपने यूज़र इंटरफ़ेस (यूआई) मॉड्यूल में, डेटा लेयर क्लास को यूज़र इंटरफ़ेस (यूआई) के हिसाब से रैपर क्लास में रैप करें.

अगर बाहरी लाइब्रेरी, Compose कंपाइलर का इस्तेमाल नहीं करती हैं, तो उनका इस्तेमाल करने पर भी यही समस्या होती है.

हर कंपोज़ेबल को स्किप नहीं किया जा सकता

ऐप्लिकेशन के हैंग या क्रैश होने से जुड़ी समस्याओं को ठीक करते समय, आपको हर कंपोज़ेबल को स्किप करने की सुविधा नहीं देनी चाहिए. ऐसा करने से, समय से पहले ऑप्टिमाइज़ेशन हो सकता है. इससे ठीक होने के बजाय ज़्यादा समस्याएं हो सकती हैं.

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

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

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