Menggunakan coroutine Kotlin dengan komponen yang mendukung siklus proses

Coroutine Kotlin menyediakan API yang memungkinkan Anda menulis kode asinkron. Dengan coroutine Kotlin, Anda dapat menentukan CoroutineScope, yang membantu Anda mengelola kapan coroutine harus dijalankan. Setiap operasi asinkron berjalan dalam cakupan tertentu.

Komponen yang mendukung siklus proses memberikan dukungan terbaik bagi coroutine untuk cakupan logis di aplikasi Anda. Dokumen ini menjelaskan cara menggunakan coroutine secara efektif dengan komponen yang mendukung siklus proses.

Menambahkan dependensi

Cakupan coroutine bawaan yang dijelaskan dalam topik ini dimuat dalam Lifecycle API. Pastikan untuk menambahkan dependensi yang sesuai saat menggunakan cakupan ini.

  • Untuk utilitas ViewModel di Compose, gunakan implementation("androidx.lifecycle:lifecycle-viewmodel-compose:$lifecycle_version").
  • Untuk utilitas Lifecycle di Compose, gunakan implementation("androidx.lifecycle:lifecycle-runtime-compose:$lifecycle_version").

Cakupan coroutine berbasis Lifecycle

Compose dan library Lifecycle menyediakan cakupan bawaan berikut yang dapat Anda gunakan di aplikasi.

ViewModelScope

A ViewModelScope ditentukan untuk setiap ViewModel di aplikasi Anda. Setiap coroutine yang diluncurkan dalam cakupan ini akan otomatis dibatalkan jika ViewModel di hapus. Coroutine sangat berguna di sini jika Anda memiliki pekerjaan yang harus dilakukan hanya saat ViewModel aktif. Misalnya, jika Anda menghitung beberapa data untuk tata letak, sebaiknya tentukan cakupan pekerjaan tersebut ke ViewModel agar saat ViewModel dihapus, pekerjaan otomatis dibatalkan untuk menghindari konsumsi resource.

Anda dapat mengakses CoroutineScope dari ViewModel melalui properti viewModelScope dari ViewModel, seperti yang ditunjukkan dalam contoh berikut:

class MyViewModel: ViewModel() {
    init {
        viewModelScope.launch {
            // Coroutine that will be canceled when the ViewModel is cleared.
        }
    }
}

Untuk kasus penggunaan lanjutan lainnya, Anda dapat meneruskan CoroutineScope kustom langsung ke konstruktor ViewModel untuk mengganti viewModelScope default. Pendekatan ini menawarkan lebih banyak kontrol dan fleksibilitas, terutama untuk:

  • Pengujian: Memungkinkan Anda menyuntikkan TestScope, sehingga lebih mudah mengontrol waktu dan memverifikasi perilaku coroutine dalam uji unit.

  • Konfigurasi kustom: Anda dapat mengonfigurasi cakupan dengan CoroutineDispatcher tertentu (seperti Dispatchers.Default untuk komputasi berat) atau CoroutineExceptionHandler kustom sebelum ViewModel memulai pekerjaannya.

Cakupan terikat komposisi

Efek samping seperti animasi, panggilan jaringan, atau timer harus dicakup ke siklus proses composable. Dengan cara ini, saat composable keluar dari layar (keluar dari komposisi), coroutine yang berjalan akan otomatis dibatalkan untuk mencegah kebocoran memori.

Compose menyediakan LaunchedEffect API untuk menangani cakupan Komposisi secara deklaratif.

LaunchedEffect membuat CoroutineScope yang memungkinkan Anda menjalankan fungsi penangguhan. Cakupan ini terikat ke siklus proses Komposisi composable, bukan Siklus Proses Aktivitas host.

  • Masuk: Coroutine dimulai saat composable memasuki komposisi.
  • Keluar: Coroutine dibatalkan saat composable keluar dari komposisi.
  • Diluncurkan kembali: Jika ada kunci yang diteruskan ke LaunchedEffect berubah, coroutine yang ada akan dibatalkan dan coroutine baru akan diluncurkan.

Contoh berikut menunjukkan cara menggunakan LaunchedEffect untuk membuat animasi berdenyut. Coroutine terikat ke keberadaan composable dalam komposisi dan bereaksi terhadap perubahan konfigurasi:

// Allow the pulse rate to be configured, so it can be sped up if the user is running
// out of time
var pulseRateMs by remember { mutableLongStateOf(3000L) }
val alpha = remember { Animatable(1f) }
LaunchedEffect(pulseRateMs) { // Restart the effect when the pulse rate changes
    while (isActive) {
        delay(pulseRateMs) // Pulse the alpha every pulseRateMs to alert the user
        alpha.animateTo(0f)
        alpha.animateTo(1f)
    }
}

Untuk mengetahui informasi selengkapnya tentang LaunchedEffect, lihat Efek samping di Compose.

Pengumpulan alur berbasis Siklus proses

Untuk mengumpulkan alur dengan aman di Jetpack Compose, gunakan API collectAsStateWithLifecycle. Fungsi tunggal ini mengonversi Flow menjadi objek State Compose dan otomatis mengelola langganan siklus proses untuk Anda. Secara default, pengumpulan dimulai saat siklus proses STARTED dan berhenti saat siklus proses STOPPED. Untuk mengganti perilaku default ini, teruskan parameter minActiveState dengan metode siklus proses yang Anda inginkan, seperti Lifecycle.State.RESUMED.

Contoh berikut menunjukkan cara mengumpulkan StateFlow ViewModel dalam composable:

@Composable
private fun ConversationScreen(
    conversationViewModel: ConversationViewModel = viewModel()
) {

    val messages by conversationViewModel.messages.collectAsStateWithLifecycle()

    ConversationScreen(
        messages = messages,
        onSendMessage = { message: Message -> conversationViewModel.sendMessage(message) }
    )
}

@Composable
private fun ConversationScreen(
    messages: List<Message>,
    onSendMessage: (Message) -> Unit
) {

    MessagesList(messages, onSendMessage)
    /* ... */
}

Pengumpulan paralel beberapa alur

Di Compose, Anda dapat mengumpulkan beberapa alur secara paralel dengan mendeklarasikan beberapa variabel status. Karena collectAsStateWithLifecycle mengelola cakupan dasarnya sendiri, pengumpulan paralel ditangani secara otomatis:

@Composable
fun DashboardScreen(viewModel: DashboardViewModel = viewModel()) {
    // Both flows are collected safely in parallel and will emit updates when either changes, the composables will recompose
    val userData by viewModel.userFlow.collectAsStateWithLifecycle()
    val feedData by viewModel.feedFlow.collectAsStateWithLifecycle()

    // ...
}

Menghitung nilai secara asinkron menggunakan Alur

Jika Anda perlu menghitung nilai secara asinkron, gunakan StateFlow dengan operator stateIn.

Cuplikan berikut menggunakan Flow standar yang dikonversi ke StateFlow. Parameter WhileSubscribed(5000) membuat langganan tetap aktif selama lima detik setelah UI menghilang untuk menangani perubahan konfigurasi.

val uiState: StateFlow<Result> = flow {
    emit(repository.fetchData())
}
.stateIn(
    scope = viewModelScope,
    started = SharingStarted.WhileSubscribed(5_000),
    initialValue = Result.Loading
)

Gunakan collectAsStateWithLifecycle untuk mengonversi nilai yang dikumpulkan menjadi Compose State, sehingga UI Anda dapat bereaksi secara reaktif setiap kali data berubah.

Untuk mengetahui informasi selengkapnya tentang status, lihat Status dan Jetpack Compose.

Referensi lainnya

Konten tampilan

Contoh