Siklus proses dan efek samping

Composable harus bebas efek samping. Namun, jika diperlukan untuk mengubah status aplikasi, composable harus dipanggil dari lingkungan terkontrol yang mengetahui siklus proses composable. Dalam halaman ini, Anda akan mempelajari siklus proses composable dan API efek samping berbeda yang ditawarkan Jetpack Compose.

Siklus proses composable

Seperti yang disebutkan dalam dokumentasi Mengelola status, Komposisi mendeskripsikan UI aplikasi Anda dan dihasilkan dengan menjalankan composable. Komposisi adalah struktur hierarki composable yang mendeskripsikan UI Anda.

Saat Jetpack Compose menjalankan composable Anda untuk pertama kalinya, selama komposisi awal, fitur ini akan terus melacak composable komponen yang Anda panggil untuk mendeskripsikan UI Anda di Komposisi. Kemudian, saat status aplikasi berubah, Jetpack Compose menjadwalkan rekomposisi. Rekomposisi adalah saat Jetpack Compose mengeksekusi ulang composable yang mungkin telah berubah sebagai respons terhadap perubahan status, lalu mengupdate Komposisi untuk mencerminkan setiap perubahan.

Komposisi hanya dapat dihasilkan oleh komposisi awal dan diperbarui dengan rekomposisi. Satu-satunya cara untuk mengubah Komposisi adalah melalui rekomposisi.

Diagram yang menunjukkan siklus proses composable

Gambar 1. Siklus proses composable dalam Komposisi. Siklus Proses tersebut memasuki Komposisi, dikomposisi ulang 0 kali atau lebih, dan keluar dari.

Rekomposisi biasanya dipicu oleh perubahan pada objek State<T>. Compose melacak ini dan menjalankan semua composable di Komposisi yang membaca State<T> tertentu, dan semua composable yang dipanggil yang tidak dapat dilewati.

Jika sebuah composable dipanggil beberapa kali, beberapa instance ditempatkan di Komposisi. Setiap panggilan memiliki siklus prosesnya sendiri di Komposisi.

@Composable
fun MyComposable() {
    Column {
        Text("Hello")
        Text("World")
    }
}

Diagram yang menunjukkan susunan hierarki elemen dalam cuplikan kode sebelumnya

Gambar 2. Representasi MyComposable dalam Komposisi. Jika sebuah composable dipanggil beberapa kali, beberapa instance ditempatkan di Komposisi. Elemen yang memiliki warna berbeda menunjukkan bahwa elemen tersebut adalah instance terpisah.

Anatomi composable dalam Komposisi

Instance composable dalam Komposisi diidentifikasi oleh situs panggilan. Compiler Compose menganggap setiap situs panggilan berbeda. Memanggil composable dari beberapa situs panggilan akan membuat beberapa instance composable di Komposisi.

Jika selama rekomposisi composable memanggil composable yang berbeda dari yang dipanggil selama komposisi sebelumnya, Compose akan mengidentifikasi composable mana yang dipanggil atau tidak dipanggil dan untuk composable yang dipanggil di kedua komposisi, Compose akan menghindari pengubahan komposisi jika inputnya belum berubah.

Mempertahankan identitas sangat penting untuk mengaitkan efek samping dengan composable, sehingga dapat diselesaikan dengan baik, bukan memulai ulang untuk setiap rekomposisi.

Perhatikan contoh berikut:

@Composable
fun LoginScreen(showError: Boolean) {
    if (showError) {
        LoginError()
    }
    LoginInput() // This call site affects where LoginInput is placed in Composition
}

@Composable
fun LoginInput() { /* ... */ }

Pada cuplikan kode di atas, LoginScreen akan memanggil composable LoginError dan akan selalu memanggil composable LoginInput. Setiap panggilan memiliki situs panggilan dan posisi sumber unik, yang akan digunakan oleh compiler untuk mengidentifikasinya secara unik.

Diagram yang menunjukkan bagaimana kode sebelumnya dikomposisi ulang jika flag showError diubah ke benar (true). Composable LoginError ditambahkan, tetapi composable lainnya tidak dikomposisi ulang.

Gambar 3. Representasi LoginScreen dalam Komposisi saat status berubah dan terjadi rekomposisi. Warna yang sama berarti model belum dikomposisi ulang.

Meskipun LoginInput dipanggil pertama kali lalu menjadi yang kedua, instance LoginInput akan dipertahankan di seluruh rekomposisi. Selain itu, karena LoginInput tidak memiliki parameter yang telah diubah di rekomposisi, panggilan ke LoginInput akan dilewati oleh Compose.

Tambahkan informasi tambahan untuk membantu rekomposisi smart

Memanggil composable beberapa kali akan menambahkannya ke Komposisi beberapa kali juga. Saat memanggil composable beberapa kali dari situs panggilan yang sama, Compose tidak memiliki informasi apa pun untuk mengidentifikasi setiap panggilan secara unik ke composable tersebut, sehingga digunakan urutan eksekusi selain situs panggilan untuk menjaga instance tetap berbeda. Perilaku ini terkadang diperlukan, tetapi dalam beberapa kasus, perilaku tersebut dapat menyebabkan perilaku yang tidak diinginkan.

@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)
        }
    }
}

Dalam contoh di atas, Compose menggunakan urutan eksekusi selain situs panggilan agar instance dalam Komposisi tetap berbeda. Jika movie baru ditambahkan ke bagian bawah daftar, Compose dapat kembali menggunakan instance yang sudah ada pada Komposisi karena lokasinya tidak ada dalam daftar, sehingga input movie sama untuk instance tersebut.

Diagram yang menunjukkan bagaimana kode sebelumnya dikomposisi ulang jika elemen baru ditambahkan ke bagian bawah daftar. Posisi item lain dalam daftar tidak berubah, dan tidak dikomposisi ulang.

Gambar 4. Representasi MoviesScreen di Komposisi saat elemen baru ditambahkan ke bagian bawah daftar. Composable MovieOverview dalam Komposisi dapat digunakan kembali. Warna yang sama di MovieOverview berarti composable belum dikomposisi ulang.

Namun, jika daftar movies berubah dengan menambahkan ke bagian atas atau tengah dari daftar, menghapus atau menyusun ulang item akan menyebabkan rekomposisi di semua panggilan MovieOverview yang posisi parameter inputnya telah berubah dalam daftar. Hal tersebut sangat penting jika, misalnya, MovieOverview mengambil gambar film menggunakan efek samping. Jika rekomposisi terjadi saat efek sedang berlangsung, rekomposisi akan dibatalkan dan akan dimulai lagi.

@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)

        /* ... */
    }
}

Diagram yang menunjukkan bagaimana kode sebelumnya dikomposisi ulang jika elemen baru ditambahkan ke bagian atas daftar. Setiap posisi item lain dalam daftar akan berubah dan harus dikomposisi ulang.

Gambar 5. Representasi MoviesScreen dalam Komposisi saat elemen baru ditambahkan ke daftar. Composable MovieOverview tidak dapat digunakan kembali dan semua efek samping akan dimulai ulang. Warna yang berbeda di MovieOverview berarti composable telah dikomposisi ulang.

Idealnya, kita ingin menganggap identitas instance MovieOverview terkait dengan identitas movie yang diteruskan ke instance tersebut. Jika kita mengurutkan ulang daftar film, idealnya kita juga akan mengurutkan ulang instance dalam hierarki Komposisi alih-alih mengomposisi ulang setiap composable MovieOverview dengan instance film yang berbeda. Compose memberikan cara untuk memberi tahu runtime nilai apa saja yang ingin Anda gunakan untuk mengidentifikasi bagian hierarki tertentu: composable key.

Dengan menggabungkan blok kode dengan panggilan ke composable kunci dengan satu atau beberapa nilai yang diteruskan, nilai tersebut akan digabungkan agar dapat digunakan untuk mengidentifikasi instance tersebut dalam komposisi. Nilai untuk key tidak harus unik secara global, tetapi hanya perlu unik di antara pemanggilan composable di situs panggilan. Jadi, dalam contoh ini, setiap movie harus memiliki key yang unik di antara movies; tidak menjadi masalah jika aplikasi tersebut membagikan key dengan beberapa composable lain di tempat lain dalam aplikasi.

@Composable
fun MoviesScreen(movies: List<Movie>) {
    Column {
        for (movie in movies) {
            key(movie.id) { // Unique ID for this movie
                MovieOverview(movie)
            }
        }
    }
}

Dengan begitu, meskipun elemen dalam daftar berubah, Compose mengenali panggilan individual ke MovieOverview dan dapat menggunakannya kembali.

Diagram yang menunjukkan bagaimana kode sebelumnya dikomposisi ulang jika elemen baru ditambahkan ke bagian atas daftar. Karena item daftar diidentifikasi oleh kunci, Compose tahu tidak perlu mengomposisi ulang kembali, meskipun posisinya telah berubah.

Gambar 6. Representasi MoviesScreen dalam Komposisi saat elemen baru ditambahkan ke daftar. Karena composable MovieOverview memiliki kunci unik, Compose mengenali instance MovieOverview yang belum berubah, dan dapat menggunakannya kembali; efek sampingnya akan terus berjalan.

Beberapa composable memiliki dukungan bawaan untuk composable key. Misalnya, LazyColumn menerima penetapan key kustom di DSL items.

@Composable
fun MoviesScreen(movies: List<Movie>) {
    LazyColumn {
        items(movies, key = { movie -> movie.id }) { movie ->
            MovieOverview(movie)
        }
    }
}

Melewati jika input belum berubah

Jika dalam Komposisi sudah terdapat composable, maka dapat melewati rekomposisi jika semua input stabil dan belum berubah.

Jenis yang stabil harus mematuhi kontrak berikut:

  • Hasil equals untuk dua instance akan selamanya sama untuk dua instance yang sama.
  • Jika properti publik dari jenis tersebut berubah, Komposisi akan diberi tahu.
  • Semua jenis properti publik juga stabil.

Ada beberapa jenis umum penting yang termasuk dalam kontrak ini yang akan ditangani oleh compiler tulis sebagai stabil, meskipun tidak secara eksplisit ditandai sebagai stabil menggunakan anotasi @Stable:

  • Semua jenis nilai primitif: Boolean, Int, Long, Float, Char, dll.
  • String
  • Semua jenis Fungsi (lambda)

Semua jenis ini dapat mengikuti kontrak stabil karena tidak dapat diubah. Karena jenis yang tidak dapat diubah tidak pernah berubah, jenis tersebut tidak perlu memberi tahu Komposisi tentang perubahan tersebut, sehingga jauh lebih mudah untuk mengikuti kontrak ini.

Salah satu jenis penting yang stabil tetapi dapat diubah adalah jenis MutableState dari Compose. Jika nilai disimpan dalam MutableState, objek status secara keseluruhan dianggap stabil karena Compose akan diberi tahu tentang perubahan apa pun pada properti .value dari State.

Jika semua jenis yang diteruskan sebagai parameter ke composable dapat stabil, nilai parameter akan dibandingkan untuk persamaan berdasarkan posisi composable di hierarki UI. Rekomposisi dilewati jika semua nilai tidak berubah sejak panggilan sebelumnya.

Compose menganggap jenis stabil hanya jika dapat membuktikannya. Misalnya, antarmuka umumnya diperlakukan sebagai tidak stabil, dan jenis dengan properti publik yang dapat diubah yang penerapannya tidak dapat diubah juga tidak stabil.

Jika Compose tidak dapat menyimpulkan jenisnya stabil, tetapi Anda ingin memaksa Compose untuk memperlakukannya sebagai stabil, tandai dengan anotasi @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
}

Pada cuplikan kode di atas, karena UiState adalah antarmuka, Compose biasanya dapat menganggap jenis ini tidak stabil. Dengan menambahkan anotasi @Stable, Anda memberi tahu Compose bahwa jenis ini stabil, dengan memungkinkan Compose mendukung rekomposisi smart. Ini juga berarti bahwa Compose akan memperlakukan semua implementasinya sebagai stabil jika antarmuka digunakan sebagai jenis parameter.

Kasus penggunaan status dan efek

Seperti yang dibahas dalam dokumentasi Paradigma Compose, composable harus bebas efek samping. Saat Anda perlu melakukan perubahan pada status aplikasi (seperti yang dijelaskan dalam dokumen dokumentasi Mengelola status), Anda harus menggunakan Effect API agar efek samping tersebut dieksekusi dengan cara yang dapat diprediksi.

Karena berbagai kemungkinan efek terbuka di Compose, efek tersebut dapat dengan mudah digunakan secara berlebihan. Pastikan tugas yang Anda lakukan di dalamnya berkaitan dengan UI dan tidak merusak aliran data searah seperti yang dijelaskan dalam dokumentasi Mengelola status.

LaunchedEffect: menjalankan fungsi penangguhan dalam cakupan composable

Untuk memanggil fungsi penangguhan dengan aman dari dalam composable, gunakan composable LaunchedEffect. Saat memasuki Komposisi, LaunchedEffect akan meluncurkan , coroutine dengan blok kode yang diteruskan sebagai parameter. Coroutine akan dibatalkan jika LaunchedEffect keluar dari komposisi. Jika LaunchedEffect dikomposisi ulang dengan kunci yang berbeda (lihat bagian Memulai Ulang Efek di bawah), coroutine yang ada akan dibatalkan dan fungsi penangguhan yang baru akan diluncurkan dalam coroutine baru.

Misalnya, menampilkan Snackbar dalam Scaffold dilakukan dengan fungsi SnackbarHostState.showSnackbar, yang merupakan penangguhan fungsi.

@Composable
fun MyScreen(
    state: UiState<List<Movie>>,
    scaffoldState: ScaffoldState = rememberScaffoldState()
) {

    // If the UI state contains an error, show snackbar
    if (state.hasError) {

        // `LaunchedEffect` will cancel and re-launch if `scaffoldState` changes
        LaunchedEffect(scaffoldState.snackbarHostState) {
            // Show snackbar using a coroutine, when the coroutine is cancelled the
            // snackbar will automatically dismiss. This coroutine will cancel whenever
            // `state.hasError` is false, and only start when `state.hasError`
            // is true (due to the above if-check), or if `scaffoldState` changes.
            scaffoldState.snackbarHostState.showSnackbar(
                message = "Error message",
                actionLabel = "Retry message"
            )
        }
    }

    Scaffold(scaffoldState = scaffoldState) {
        /* ... */
    }
}

Pada kode di atas, coroutine akan dipicu jika status berisi error dan akan dibatalkan jika tidak terjadi. Karena situs panggilan LaunchedEffect ada di dalam pernyataan if, jika pernyataan salah, jika LaunchedEffect berada di Komposisi, situs akan dihapus, jadi, coroutine akan dibatalkan.

rememberCoroutineScope: memperoleh cakupan yang mendukung komposisi untuk meluncurkan coroutine di luar composable

Karena LaunchedEffect adalah fungsi composable, fungsi ini hanya dapat digunakan di dalam fungsi composable. Untuk meluncurkan coroutine di luar composable, tetapi disertakan agar otomatis dibatalkan setelah keluar dari komposisi, gunakan rememberCoroutineScope. Selain itu, gunakan rememberCoroutineScope setiap kali Anda perlu mengontrol siklus proses dari satu atau beberapa coroutine secara manual, misalnya, membatalkan animasi saat terjadi peristiwa pengguna.

rememberCoroutineScope adalah fungsi yang dapat dikomposisi yang menampilkan CoroutineScope yang terikat ke titik Komposisi tempatnya dipanggil. Cakupan akan dibatalkan saat panggilan keluar dari Komposisi.

Dengan mengikuti contoh sebelumnya, Anda dapat menggunakan kode ini untuk menampilkan Snackbar saat pengguna mengetuk Button:

@Composable
fun MoviesScreen(scaffoldState: ScaffoldState = rememberScaffoldState()) {

    // Creates a CoroutineScope bound to the MoviesScreen's lifecycle
    val scope = rememberCoroutineScope()

    Scaffold(scaffoldState = scaffoldState) {
        Column {
            /* ... */
            Button(
                onClick = {
                    // Create a new coroutine in the event handler
                    // to show a snackbar
                    scope.launch {
                        scaffoldState.snackbarHostState
                            .showSnackbar("Something happened!")
                    }
                }
            ) {
                Text("Press me")
            }
        }
    }
}

rememberUpdatedState: mereferensikan nilai dalam efek yang seharusnya tidak dimulai ulang jika nilai berubah

LaunchedEffect dimulai ulang saat salah satu parameter utama berubah. Namun, dalam beberapa situasi Anda mungkin ingin menangkap nilai dalam efek yang, jika berubah, Anda tidak ingin efek dimulai ulang. Untuk melakukannya, gunakan rememberUpdatedState untuk membuat referensi ke nilai ini yang dapat diambil dan diupdate. Pendekatan ini berguna untuk efek yang berisi operasi berdurasi panjang yang mungkin mahal atau sulit untuk dibuat ulang dan dimulai ulang.

Misalnya, aplikasi Anda memiliki LandingScreen yang hilang setelah beberapa saat. Meskipun LandingScreen dikomposisi ulang, efek yang menunggu beberapa saat dan memberi tahu bahwa waktu yang telah berlalu tidak boleh dimulai ulang:

@Composable
fun LandingScreen(onTimeout: () -> Unit) {

    // This will always refer to the latest onTimeout function that
    // LandingScreen was recomposed with
    val currentOnTimeout by rememberUpdatedState(onTimeout)

    // Create an effect that matches the lifecycle of LandingScreen.
    // If LandingScreen recomposes, the delay shouldn't start again.
    LaunchedEffect(true) {
        delay(SplashWaitTimeMillis)
        currentOnTimeout()
    }

    /* Landing screen content */
}

Untuk membuat efek yang cocok dengan siklus proses situs panggilan, konstanta yang tidak pernah berubah seperti Unit atau true diteruskan sebagai parameter. Pada kode di atas, LaunchedEffect(true) digunakan. Untuk memastikan bahwa lambda onTimeout selalu berisi nilai terbaru dengan LandingScreen dikomposisi ulang, onTimeout harus digabungkan dengan fungsi rememberUpdatedState. State yang ditampilkan, currentOnTimeout dalam kode, harus digunakan dalam efek.

DisposableEffect: efek yang perlu dibersihkan

Untuk efek samping yang perlu dibersihkan setelah kunci berubah atau jika composable keluar dari Komposisi, gunakan DisposableEffect. Jika kunci DisposableEffect berubah, composable harus membuang (melakukan pembersihan) efek saat ini, dan menyetel ulang dengan memanggil efek lagi.

Misalnya, OnBackPressedCallback harus didaftarkan untuk mendengarkan tombol kembali yang ditekan pada OnBackPressedDispatcher. Untuk mendengarkan peristiwa tersebut di Compose, gunakan DisposableEffect untuk mendaftar dan membatalkan pendaftaran callback jika diperlukan.

@Composable
fun BackHandler(backDispatcher: OnBackPressedDispatcher, onBack: () -> Unit) {

    // Safely update the current `onBack` lambda when a new one is provided
    val currentOnBack by rememberUpdatedState(onBack)

    // Remember in Composition a back callback that calls the `onBack` lambda
    val backCallback = remember {
        // Always intercept back events. See the SideEffect for
        // a more complete version
        object : OnBackPressedCallback(true) {
            override fun handleOnBackPressed() {
                currentOnBack()
            }
        }
    }

    // If `backDispatcher` changes, dispose and reset the effect
    DisposableEffect(backDispatcher) {
        // Add callback to the backDispatcher
        backDispatcher.addCallback(backCallback)

        // When the effect leaves the Composition, remove the callback
        onDispose {
            backCallback.remove()
        }
    }
}

Dalam kode di atas, efek akan menambahkan backCallback yang diingat ke backDispatcher. Jika backDispatcher berubah, efek akan dibuang dan dimulai ulang lagi.

DisposableEffect harus menyertakan klausa onDispose sebagai pernyataan akhir dalam blok kodenya. Jika tidak, IDE akan menampilkan error waktu-build.

SideEffect: memublikasikan status Compose ke kode non-compose

Untuk membagikan status Compose dengan objek yang tidak dikelola oleh Compose, gunakan composable SideEffect, karena dipanggil pada setiap rekomposisi yang berhasil.

Menggunakan kode BackHandler sebelumnya sebagai contoh, untuk mengomunikasikan apakah callback harus diaktifkan atau tidak, gunakan SideEffect untuk memperbarui nilainya.

@Composable
fun BackHandler(
    backDispatcher: OnBackPressedDispatcher,
    enabled: Boolean = true, // Whether back events should be intercepted or not
    onBack: () -> Unit
) {
    /* ... */
    val backCallback = remember { /* ... */ }

    // On every successful composition, update the callback with the `enabled` value
    // to tell `backCallback` whether back events should be intercepted or not
    SideEffect {
        backCallback.isEnabled = enabled
    }

    /* Rest of the code */
}

produceState: mengonversi status non-Compose menjadi status Compose

produceState meluncurkan coroutine yang dicakupkan ke Komposisi yang dapat mendorong nilai ke dalam State yang ditampilkan. Gunakan coroutine tersebut untuk mengonversi status non-Compose menjadi status Compose, misalnya menerapkan status berbasis langganan eksternal seperti Flow, LiveData, atau RxJava ke dalam Komposisi.

Producer diluncurkan saat produceState memasuki Komposisi, dan akan dibatalkan saat keluar dari Komposisi. State yang ditampilkan bercampur; menyetel nilai yang sama tidak akan memicu rekomposisi.

Meskipun membuat coroutine, produceState juga dapat digunakan untuk mengamati sumber data yang tidak ditangguhkan. Untuk menghapus langganan ke sumber tersebut, gunakan fungsi awaitDispose.

Contoh berikut menunjukkan cara menggunakan produceState untuk memuat gambar dari jaringan. Fungsi yang dapat dikomposisi loadNetworkImage menampilkan State yang dapat digunakan di composable lain.

@Composable
fun loadNetworkImage(
    url: String,
    imageRepository: ImageRepository
): State<Result<Image>> {

    // Creates a State<T> with Result.Loading as initial value
    // If either `url` or `imageRepository` changes, the running producer
    // will cancel and will be re-launched with the new keys.
    return produceState(initialValue = Result.Loading, url, imageRepository) {

        // In a coroutine, can make suspend calls
        val image = imageRepository.load(url)

        // Update State with either an Error or Success result.
        // This will trigger a recomposition where this State is read
        value = if (image == null) {
            Result.Error
        } else {
            Result.Success(image)
        }
    }
}

derivedStateOf: mengonversi satu atau beberapa objek status menjadi status lain

Gunakan derivedStateOf saat status tertentu dihitung atau berasal dari objek status lainnya. Penggunaan fungsi ini menjamin bahwa penghitungan hanya akan terjadi setiap kali salah satu status yang digunakan dalam penghitungan berubah

Contoh berikut menampilkan Daftar Tugas dasar yang tugasnya dengan kata kunci prioritas tinggi buatan pengguna muncul terlebih dahulu:

@Composable
fun TodoList(
    highPriorityKeywords: List<String> = listOf("Review", "Unblock", "Compose")
) {
    val todoTasks = remember { mutableStateListOf<String>() }

    // Calculate high priority tasks only when the todoTasks or
    // highPriorityKeywords change, not on every recomposition
    val highPriorityTasks by remember(todoTasks, highPriorityKeywords) {
        derivedStateOf {
            todoTasks.filter { it.containsWord(highPriorityKeywords) }
        }
    }

    Box(Modifier.fillMaxSize()) {
        LazyColumn {
            items(highPriorityTasks) { /* ... */ }
            items(todoTasks) { /* ... */ }
        }
        /* Rest of the UI where users can add elements to the list */
    }
}

Dalam kode di atas, derivedStateOf menjamin bahwa setiap todoTasks atau highPriorityKeywords berubah, penghitungan highPriorityTasks akan terjadi dan UI akan diupdate sesuai dengan itu. Karena pemfilteran untuk menghitung highPriorityTasks bisa menjadi mahal, pemfilteran hanya boleh dijalankan jika ada daftar yang berubah, bukan pada setiap rekomposisi.

Selain itu, pembaruan pada status yang dihasilkan oleh derivedStateOf tidak menyebabkan fungsi yang dapat dikomposisi, tempatnya dideklarasikan untuk dikomposisi ulang. Compose hanya akan mengomposisi ulang fungsi yang dapat dikomposisi tersebut tempat statusnya yang ditampilkan dibaca, di dalam LazyColumn dalam contoh.

snapshotFlow: konversikan Status Compose ke dalam Alur

Gunakan snapshotFlow untuk mengonversi objek State<T> ke dalam Alur dingin. snapshotFlow menjalankan bloknya saat dikumpulkan dan memancarkan hasil objek State yang dibaca di dalamnya. Saat salah satu objek State yang dibaca di dalam blok snapshotFlow berubah, Alur akan memunculkan nilai baru ke kolektornya jika nilai baru tidak sama dengan nilai yang muncul sebelumnya (perilaku ini mirip dengan Flow.distinctUntilChanged).

Contoh berikut menunjukkan efek samping yang dicatat saat pengguna men-scroll melewati item pertama dalam daftar ke analisis:

val listState = rememberLazyListState()

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

LaunchedEffect(listState) {
    snapshotFlow { listState.firstVisibleItemIndex }
        .map { index -> index > 0 }
        .distinctUntilChanged()
        .filter { it == true }
        .collect {
            MyAnalyticsService.sendScrolledPastFirstItemEvent()
        }
}

Dalam kode di atas, listState.firstVisibleItemIndex dikonversi menjadi Alur yang dapat memanfaatkan kekuatan operator Alur.

Memulai ulang efek

Beberapa efek di Compose, seperti LaunchedEffect, produceState, atau DisposableEffect, mengambil sejumlah variabel argumen, kunci, yang digunakan untuk membatalkan efek yang sedang berjalan dan memulai yang baru dengan kunci baru

Bentuk umum untuk API ini adalah:

EffectName(restartIfThisKeyChanges, orThisKey, orThisKey, ...) { block }

Karena kerumitan perilaku ini, masalah dapat terjadi jika parameter yang digunakan untuk memulai ulang efek tidak tepat:

  • Memulai ulang efek kurang dari seharusnya dapat menyebabkan bug di aplikasi Anda.
  • Memulai ulang efek lebih dari seharusnya bisa menjadi tidak efisien.

Prinsipnya adalah variabel yang dapat diubah dan tidak dapat diubah yang digunakan dalam blok kode efek harus ditambahkan sebagai parameter ke composable efek. Selain itu, parameter lainnya dapat ditambahkan untuk memaksa efek dimulai ulang. Jika perubahan variabel tidak menyebabkan efek dimulai ulang, variabel harus digabungkan dalam rememberUpdatedState. Jika variabel tidak pernah berubah karena digabungkan dalam remember tanpa kunci, Anda tidak perlu meneruskan variabel sebagai kunci ke efek.

Pada kode DisposableEffect yang ditampilkan di atas, efek yang diambil sebagai parameter yang digunakan oleh backDispatcher dalam bloknya karena setiap perubahan pada kode tersebut harus menyebabkan efek dimulai ulang.

@Composable
fun BackHandler(backDispatcher: OnBackPressedDispatcher, onBack: () -> Unit) {
    /* ... */
    val backCallback = remember { /* ... */ }

    DisposableEffect(backDispatcher) {
        backDispatcher.addCallback(backCallback)
        onDispose {
            backCallback.remove()
        }
    }
}

backCallback tidak diperlukan sebagai kunci DisposableEffect karena nilainya tidak akan pernah berubah di Komposisi; melainkan digabungkan dalam remember tanpa kunci. Jika backDispatcher tidak diteruskan sebagai parameter dan berubah, BackHandler akan mengomposisi ulang tetapi DisposableEffect tidak akan membuang dan memulai ulang lagi. Hal itu akan menyebabkan masalah karena backDispatcher yang salah akan digunakan sejak saat itu.

Konstanta sebagai kunci

Anda dapat menggunakan konstanta seperti true sebagai kunci efek untuk membuatnya mengikuti siklus proses situs panggilan. Terdapat kasus penggunaan yang valid untuk hal ini, seperti contoh LaunchedEffect yang ditampilkan di atas. Namun, sebelum melakukannya, pikirkan kembali dan pastikan hal tersebut adalah yang Anda perlukan.