सबसे अच्छे तरीके आज़माएं

आपको लिखने से जुड़ी कुछ सामान्य समस्याएं आ सकती हैं. इन गलतियों से आपको कोड मिल सकता है की तरह काम करता है. हालांकि, इससे आपके यूज़र इंटरफ़ेस (यूआई) की परफ़ॉर्मेंस पर बुरा असर पड़ सकता है. सर्वश्रेष्ठ का अनुसरण करें Compose पर अपने ऐप्लिकेशन को ऑप्टिमाइज़ करने के तरीके.

महंगे कैलकुलेशन के लिए, remember का इस्तेमाल करें

कंपोज़ेबल फ़ंक्शन बहुत जल्दी-जल्दी काम कर सकते हैं. ऐसा अक्सर हर फ़्रेम के लिए किया जाता है एक ऐनिमेशन है. इस कारण से, आपको आपके कंपोज़ेबल का मुख्य हिस्सा.

एक महत्वपूर्ण तकनीक है, गिनती के नतीजों को सेव करना remember. इस तरह, कैलकुलेशन एक बार चलती है और खोज सकते हैं.

उदाहरण के लिए, यहां कुछ कोड दिए गए हैं जो नामों की क्रम से लगाई गई सूची दिखाता है, लेकिन बहुत महंगे तरीके से क्रम में लगाना:

@Composable
fun ContactList(
    contacts: List<Contact>,
    comparator: Comparator<Contact>,
    modifier: Modifier = Modifier
) {
    LazyColumn(modifier) {
        // DON’T DO THIS
        items(contacts.sortedWith(comparator)) { contact ->
            // ...
        }
    }
}

जब भी ContactsList को फिर से बनाया जाता है, तो पूरी संपर्क सूची सभी क्रम में लग जाती है लेकिन सूची नहीं बदली है. अगर लोग सूची स्क्रोल करते हैं, जब भी कोई नई लाइन दिखती है, तो कंपोज़ेबल को फिर से कंपोज़ किया जाता है.

इस समस्या को हल करने के लिए, सूची को LazyColumn से बाहर क्रम से लगाएं, और remember के साथ क्रम से लगाई गई सूची:

@Composable
fun ContactList(
    contacts: List<Contact>,
    comparator: Comparator<Contact>,
    modifier: Modifier = Modifier
) {
    val sortedContacts = remember(contacts, comparator) {
        contacts.sortedWith(comparator)
    }

    LazyColumn(modifier) {
        items(sortedContacts) {
            // ...
        }
    }
}

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

लेज़ी लेआउट वाले बटन इस्तेमाल करना

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

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

@Composable
fun NotesList(notes: List<Note>) {
    LazyColumn {
        items(
            items = notes
        ) { note ->
            NoteRow(note)
        }
    }
}

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

आपकी मदद के बिना, Compose को यह पता नहीं चलता कि बदलाव नहीं किए गए आइटम ले गया पर क्लिक करें. इसके बजाय, Compose पुरानी "आइटम 2" के बारे में सोचता है को हटा दिया गया था और एक नया आइटम 3, आइटम 4, और नीचे के आइटम के लिए बनाया गया है. इसका नतीजा यह होता है कि Compose में सूची के सभी आइटम शामिल हैं. भले ही, उनमें से सिर्फ़ एक आइटम मौजूद हो बदल गया है.

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

@Composable
fun NotesList(notes: List<Note>) {
    LazyColumn {
        items(
            items = notes,
            key = { note ->
                // Return a stable, unique key for the note
                note.id
            }
        ) { note ->
            NoteRow(note)
        }
    }
}

शॉर्ट वीडियो के सुझाव सीमित करने के लिए, derivedStateOf का इस्तेमाल करें

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

val listState = rememberLazyListState()

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

val showButton = listState.firstVisibleItemIndex > 0

AnimatedVisibility(visible = showButton) {
    ScrollToTopButton()
}

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

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

val listState = rememberLazyListState()

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

val showButton by remember {
    derivedStateOf {
        listState.firstVisibleItemIndex > 0
    }
}

AnimatedVisibility(visible = showButton) {
    ScrollToTopButton()
}

जितना हो सके उतने समय तक पढ़ने वाले कॉन्टेंट को रोकें

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

यह इफ़ेक्ट पाने के लिए, Title कंपोज़ेबल को स्क्रोल ऑफ़सेट की ज़रूरत है ताकि Modifier का इस्तेमाल करके खुद को ऑफ़सेट किया जा सके. यहाँ GA4 का सबसे आसान वर्शन दिया गया है. ऑप्टिमाइज़ेशन होने से पहले JetSnacks कोड:

@Composable
fun SnackDetail() {
    // ...

    Box(Modifier.fillMaxSize()) { // Recomposition Scope Start
        val scroll = rememberScrollState(0)
        // ...
        Title(snack, scroll.value)
        // ...
    } // Recomposition Scope End
}

@Composable
private fun Title(snack: Snack, scroll: Int) {
    // ...
    val offset = with(LocalDensity.current) { scroll.toDp() }

    Column(
        modifier = Modifier
            .offset(y = offset)
    ) {
        // ...
    }
}

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

@Composable
fun SnackDetail() {
    // ...

    Box(Modifier.fillMaxSize()) { // Recomposition Scope Start
        val scroll = rememberScrollState(0)
        // ...
        Title(snack) { scroll.value }
        // ...
    } // Recomposition Scope End
}

@Composable
private fun Title(snack: Snack, scrollProvider: () -> Int) {
    // ...
    val offset = with(LocalDensity.current) { scrollProvider().toDp() }
    Column(
        modifier = Modifier
            .offset(y = offset)
    ) {
        // ...
    }
}

स्क्रोल पैरामीटर को अब Lambda फ़ंक्शन में बदल दिया गया है. इसका मतलब है कि Title अब भी होस्टेड स्टेट, लेकिन वैल्यू सिर्फ़ Title के अंदर पढ़ी जाती है, जहां यह असल में होती है की ज़रूरत नहीं है. इस वजह से, जब स्क्रोल की वैल्यू में बदलाव होता है, तो स्कोप अब Title कंपोज़ेबल हो गया है–लिखें की अब कंपोज़ेबल में वैल्यू जोड़ने की ज़रूरत नहीं है पूरा Box.

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

@Composable
private fun Title(snack: Snack, scrollProvider: () -> Int) {
    // ...
    Column(
        modifier = Modifier
            .offset { IntOffset(x = 0, y = scrollProvider()) }
    ) {
        // ...
    }
}

पहले, कोड Modifier.offset(x: Dp, y: Dp) का इस्तेमाल करता था, जो पैरामीटर के रूप में ऑफ़सेट करें. कार्रवाई बदलने वाली कुंजी के Lambda वर्शन पर स्विच करके, यह पक्का किया जा सकता है कि फ़ंक्शन, लेआउट फ़ेज़ में स्क्रोल की स्थिति को पढ़े. बतौर नतीजा, जब स्क्रोल की स्थिति बदलती है, तब कंपोज़ की सुविधा चालू होने पर, स्क्रोल करने के चरण को छोड़कर आगे बढ़ सकती है और लेआउट फ़ेज़ पर जाएं. बार-बार पास होने पर स्टेट वैरिएबल को मॉडिफ़ायर में बदलते समय, आपको मॉडिफ़ायर का इस्तेमाल करें.

इसका एक और उदाहरण यहां दिया गया है. यह कोड अभी तक ऑप्टिमाइज़ नहीं किया गया है:

// Here, assume animateColorBetween() is a function that swaps between
// two colors
val color by animateColorBetween(Color.Cyan, Color.Magenta)

Box(
    Modifier
        .fillMaxSize()
        .background(color)
)

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

इसे बेहतर बनाने के लिए, लैम्डा-आधारित मॉडिफ़ायर का इस्तेमाल करें—इस मामले में, drawBehind. इसका मतलब है कि कलर स्टेट को सिर्फ़ ड्रॉ करने के दौरान पढ़ा जाता है. इस वजह से, कंपोज़िशन और लेआउट के चरणों को पूरी तरह स्किप किया जा सकता है—जब रंग बदलाव, कंपोज़ की सुविधा सीधे ड्रॉ के चरण में जाती है.

val color by animateColorBetween(Color.Cyan, Color.Magenta)
Box(
    Modifier
        .fillMaxSize()
        .drawBehind {
            drawRect(color)
        }
)

पुराने टेक्स्ट को लिखने से बचें

कंपोज़ की सुविधा के पीछे मुख्य धारणा है कि कभी भी ऐसा नहीं लिखा जाएगा पहले ही पढ़ लिया गया है. जब आप ऐसा करते हैं, तो इसे बैकवर्ड राइट कहा जाता है और यह इससे हर फ़्रेम पर, कभी न कभी रीकंपोज़िशन हो सकती है.

इस तरह की गलती का उदाहरण नीचे दिया गया है.

@Composable
fun BadComposable() {
    var count by remember { mutableStateOf(0) }

    // Causes recomposition on click
    Button(onClick = { count++ }, Modifier.wrapContentSize()) {
        Text("Recompose")
    }

    Text("$count")
    count++ // Backwards write, writing to state after it has been read</b>
}

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

बैकवर्ड राइटिंग से पूरी तरह से बचें. ऐसा न करने पर, कंपोज़िशन. अगर मुमकिन हो, तो किसी इवेंट के जवाब में हमेशा लिखें और पिछले onClick उदाहरण की तरह, Lambda फ़ंक्शन में.

अतिरिक्त संसाधन

{% endverba नया %} {% हूबहू %}