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 berbasis siklus proses memberikan dukungan kelas satu untuk coroutine untuk cakupan logis di aplikasi Anda. Dokumen ini menjelaskan cara menggunakan coroutine secara efektif dengan komponen berbasis siklus proses.

Menambahkan dependensi

Cakupan coroutine bawaan yang dijelaskan dalam topik ini terdapat 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

ViewModelScope ditentukan untuk setiap ViewModel di aplikasi Anda. Setiap coroutine yang diluncurkan dalam cakupan ini akan otomatis dibatalkan jika ViewModel dihapus. Coroutine berguna di sini saat Anda memiliki pekerjaan yang perlu dilakukan hanya jika 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 menggantikan viewModelScope default. Pendekatan ini menawarkan kontrol dan fleksibilitas yang lebih besar, terutama untuk:

  • Pengujian: Memungkinkan Anda memasukkan TestScope, sehingga memudahkan kontrol waktu dan verifikasi perilaku coroutine dalam pengujian unit.

  • Konfigurasi kustom: Anda dapat mengonfigurasi cakupan dengan CoroutineDispatcher tertentu (seperti Dispatchers.Default untuk komputasi berat) atau CoroutineExceptionHandler kustom bahkan 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 API LaunchedEffect untuk menangani pencakupan Komposisi secara deklaratif.

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

  • Masuk: Coroutine dimulai saat composable memasuki komposisi.
  • Keluar: Coroutine dibatalkan saat composable keluar dari komposisi.
  • Luncurkan kembali: Jika 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 terkait dengan 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 flow 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 dalam status STARTED dan berhenti saat siklus proses dalam status 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 flow 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 Flow

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

Cuplikan berikut menggunakan Flow standar yang dikonversi menjadi 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 diupdate secara reaktif setiap kali data berubah.

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

Referensi lainnya

Konten penayangan

Contoh