সেরা অনুশীলন অনুসরণ করুন

আপনি সাধারণ রচনা ত্রুটি সম্মুখীন হতে পারে. এই ভুলগুলি আপনাকে এমন কোড দিতে পারে যা যথেষ্ট ভাল বলে মনে হয়, কিন্তু আপনার UI কর্মক্ষমতাকে ক্ষতিগ্রস্ত করতে পারে। রচনায় আপনার অ্যাপটি অপ্টিমাইজ করার জন্য সর্বোত্তম অনুশীলনগুলি অনুসরণ করুন৷

ব্যয়বহুল গণনা কমাতে 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)
        }
    }
}

যদিও এই কোডের সাথে একটি সমস্যা আছে। ধরুন নীচের নোটটি পরিবর্তন করা হয়েছে। এটি এখন সবচেয়ে সাম্প্রতিক সংশোধিত নোট, তাই এটি তালিকার শীর্ষে চলে যায়, এবং অন্য প্রতিটি নোট একটি স্পট নিচে চলে যায়।

আপনার সাহায্য ছাড়া, রচনা বুঝতে পারে না যে অপরিবর্তিত আইটেমগুলি কেবল তালিকায় স্থানান্তরিত হচ্ছে৷ পরিবর্তে, কম্পোজ মনে করে পুরানো "আইটেম 2" মুছে ফেলা হয়েছে এবং আইটেম 3, আইটেম 4, এবং সমস্ত নিচের জন্য একটি নতুন তৈরি করা হয়েছে৷ ফলাফল হল যে কম্পোজ তালিকার প্রতিটি আইটেমকে পুনরায় সংকলন করে, যদিও তাদের মধ্যে শুধুমাত্র একটি পরিবর্তন হয়েছে।

এখানে সমাধান হল আইটেম কী প্রদান করা। প্রতিটি আইটেমের জন্য একটি স্থিতিশীল কী প্রদান করা অপ্রয়োজনীয় পুনর্গঠন এড়াতে কম্পোজ করতে দেয়। এই ক্ষেত্রে, রচনা এখন স্পট 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)
        }
    }
}

recompositions সীমিত করতে derivedStateOf ব্যবহার করুন

আপনার কম্পোজিশনে স্টেট ব্যবহার করার একটি ঝুঁকি হল, যদি স্টেট দ্রুত পরিবর্তিত হয়, তাহলে আপনার UI আপনার প্রয়োজনের চেয়ে বেশি রিকম্পোজ করা হতে পারে। উদাহরণস্বরূপ, ধরুন আপনি একটি স্ক্রোলযোগ্য তালিকা প্রদর্শন করছেন। কোন আইটেমটি তালিকার প্রথম দৃশ্যমান আইটেম তা দেখতে আপনি তালিকার অবস্থা পরীক্ষা করেন:

val listState = rememberLazyListState()

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

val showButton = listState.firstVisibleItemIndex > 0

AnimatedVisibility(visible = showButton) {
    ScrollToTopButton()
}

এখানে সমস্যা হল, ব্যবহারকারী যদি তালিকাটি স্ক্রোল করে, ব্যবহারকারী তাদের আঙুল টেনে আনলে listState ক্রমাগত পরিবর্তিত হয়। তার মানে তালিকাটি ক্রমাগত পুনর্গঠন করা হচ্ছে। যাইহোক, আপনাকে আসলে এটিকে প্রায়শই পুনঃকম্পোজ করার দরকার নেই - যতক্ষণ না নীচে একটি নতুন আইটেম দৃশ্যমান হয় ততক্ষণ পর্যন্ত আপনাকে পুনরায় কম্পোজ করতে হবে না। সুতরাং, এটি অনেক অতিরিক্ত গণনা, যা আপনার UI কে খারাপভাবে পারফর্ম করে।

সমাধান হল derived state ব্যবহার করা। প্রাপ্ত রাষ্ট্র আপনাকে লিখতে দেয় যে রাষ্ট্রের কোন পরিবর্তনগুলি আসলে পুনর্গঠনকে ট্রিগার করবে। এই ক্ষেত্রে, প্রথম দৃশ্যমান আইটেম পরিবর্তিত হলে আপনি যত্নশীল তা উল্লেখ করুন। যখন সেই অবস্থার মান পরিবর্তিত হয়, তখন UI-কে পুনরায় কম্পোজ করতে হবে, কিন্তু ব্যবহারকারী যদি এখনও শীর্ষে একটি নতুন আইটেম আনার জন্য যথেষ্ট স্ক্রোল না করে থাকে, তাহলে এটিকে পুনরায় রচনা করতে হবে না।

val listState = rememberLazyListState()

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

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

AnimatedVisibility(visible = showButton) {
    ScrollToTopButton()
}

যতক্ষণ সম্ভব পড়া বিলম্বিত করুন

যখন একটি কর্মক্ষমতা সমস্যা চিহ্নিত করা হয়, স্থগিত রাষ্ট্র রিড সাহায্য করতে পারে. স্থগিত করা স্টেট রিডগুলি নিশ্চিত করবে যে রচনাটি পুনর্গঠনের ন্যূনতম সম্ভাব্য কোড পুনরায় চালাবে৷ উদাহরণস্বরূপ, যদি আপনার UI-তে এমন অবস্থা থাকে যা কম্পোজেবল ট্রিতে উঁচুতে উত্তোলন করা হয় এবং আপনি একটি চাইল্ড কম্পোজেবল অবস্থায় রাজ্যটি পড়েন, আপনি একটি ল্যাম্বডা ফাংশনে পড়া অবস্থাটিকে মোড়ানো করতে পারেন। এটি করার ফলে পঠন তখনই ঘটে যখন এটি প্রকৃতপক্ষে প্রয়োজন হয়। রেফারেন্সের জন্য, Jetsnack নমুনা অ্যাপে বাস্তবায়ন দেখুন। Jetsnack তার বিস্তারিত স্ক্রিনে একটি ভেঙে পড়া-টুলবারের মতো প্রভাব প্রয়োগ করে। এই কৌশলটি কেন কাজ করে তা বুঝতে, Jetpack Compose: Debugging Recomposition ব্লগ পোস্টটি দেখুন।

এই প্রভাব অর্জনের জন্য, একটি Modifier ব্যবহার করে নিজেকে অফসেট করার জন্য Title কম্পোজেবলের স্ক্রোল অফসেট প্রয়োজন। এখানে অপ্টিমাইজেশন তৈরি করার আগে Jetsnack কোডের একটি সরলীকৃত সংস্করণ রয়েছে:

@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)
    ) {
        // ...
    }
}

যখন স্ক্রোল অবস্থা পরিবর্তিত হয়, রচনাটি নিকটতম অভিভাবক পুনর্গঠনের সুযোগকে বাতিল করে। এই ক্ষেত্রে, নিকটতম সুযোগ হল SnackDetail রচনাযোগ্য। নোট করুন যে Box একটি ইনলাইন ফাংশন, এবং তাই একটি পুনর্গঠন সুযোগ নয়। তাই কম্পোজ 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)
    ) {
        // ...
    }
}

স্ক্রোল প্যারামিটার এখন একটি ল্যাম্বডা। তার মানে 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) ব্যবহার করত, যা অফসেটটিকে একটি প্যারামিটার হিসাবে নেয়। মডিফায়ারের ল্যাম্বডা সংস্করণে স্যুইচ করার মাধ্যমে, আপনি নিশ্চিত করতে পারেন যে ফাংশনটি লেআউট পর্যায়ে স্ক্রোল অবস্থা পড়ে। ফলস্বরূপ, যখন স্ক্রোল অবস্থা পরিবর্তিত হয়, তখন কম্পোজ কম্পোজিশন ফেজটিকে সম্পূর্ণভাবে এড়িয়ে যেতে পারে এবং সরাসরি লেআউট পর্যায়ে যেতে পারে। আপনি যখন ঘন ঘন স্টেট ভেরিয়েবলগুলিকে মডিফায়ারে পরিবর্তন করছেন, আপনার যখনই সম্ভব মডিফায়ারগুলির ল্যাম্বডা সংস্করণগুলি ব্যবহার করা উচিত।

এখানে এই পদ্ধতির আরেকটি উদাহরণ। এই কোডটি এখনও অপ্টিমাইজ করা হয়নি:

// 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>
}

এই কোডটি পূর্ববর্তী লাইনে পড়ার পরে কম্পোজেবলের শেষে গণনা আপডেট করে। আপনি যদি এই কোডটি চালান, তাহলে আপনি দেখতে পাবেন যে আপনি বোতামটি ক্লিক করার পরে, যা একটি পুনর্গঠন ঘটায়, কাউন্টারটি একটি অসীম লুপে দ্রুত বৃদ্ধি পায় কারণ কম্পোজ এই কম্পোজেবলটিকে পুনরায় কম্পোজ করে, একটি স্ট্যাটাস রিড দেখে যা পুরানো হয়ে গেছে, এবং তাই আরেকটি সময়সূচী করে পুনর্গঠন

আপনি কম্পোজিশনে স্টেট করার জন্য কখনও না লিখে পিছনের দিকের লেখাগুলিকে এড়াতে পারেন৷ যদি সম্ভব হয়, সর্বদা একটি ইভেন্টের প্রতিক্রিয়া হিসাবে এবং পূর্ববর্তী onClick উদাহরণের মতো একটি ল্যাম্বডাতে লিখুন।

অতিরিক্ত সম্পদ

{% শব্দার্থে %} {% endverbatim %} {% শব্দার্থে %} {% endverbatim %}