Menggunakan Status Compose di luar UI

State<T> dan MutableState<T> Jetpack Compose adalah holder nilai tujuan umum yang didukung oleh sistem snapshot Compose. Meskipun paling sering digunakan untuk mendorong rekomposisi UI, sistem snapshot itu sendiri tidak terkait dengan rendering UI.

Dengan library AppState—Transform dan StateStore—Anda dapat menyimpan, memperoleh, mengamati, dan mempertahankan State Compose di seluruh ViewModel, pengelola, repositori, dan layanan latar belakang.

Mengapa menggunakan Compose State di luar UI?

Saat Anda membaca State Compose di dalam blok pengamatan status—seperti composable UI, transform, atau derivedStateOf—runtime Compose memberikan dua manfaat utama di seluruh lapisan arsitektur Anda:

  • Pelacakan dependensi otomatis dan dinamis: Membaca .value status (atau menggunakan delegasi properti by) akan otomatis mendaftarkan status tersebut sebagai dependensi blok. Karena pelacakan bersifat dinamis, dependensi ditentukan saat runtime berdasarkan alur kode yang dieksekusi. Jika cabang bersyarat tidak diambil (misalnya, setelah pemeriksaan if atau return awal), status di dalam cabang tersebut tidak dibaca dan perubahan pada status tersebut tidak memicu komputasi ulang yang tidak perlu.
  • Konsistensi dan perincian: Anda dapat mendeklarasikan properti MutableState yang terpisah dan terperinci, serta menggabungkan penulisan multi-properti dalam Snapshot.withMutableSnapshot. Update perantara diisolasi ke snapshot yang dapat diubah dan diterapkan secara atomik saat blok selesai, sehingga pengamat yang membaca di dalam snapshot tidak pernah melihat status perantara yang tidak konsisten dan hanya menghitung ulang saat properti tertentu yang mereka baca berubah. Tidak seperti StateFlow, Anda tidak perlu memilih antara satu StateFlow<UiState> monolitik (yang membatalkan setiap kolektor saat ada kolom yang berubah) dan properti StateFlow terpisah (yang tidak dapat diupdate bersama dalam satu transaksi atomik).

Memilih API status yang tepat di seluruh arsitektur Anda

Pilih cara menyimpan dan mempertahankan State Compose berdasarkan berapa lama suatu state harus aktif dan komponen mana yang perlu mengaksesnya:

Kategori negara bagian Masa aktif dan cakupan Contoh API State Compose yang direkomendasikan
Status live Selalu disimpan dalam memori; dikueri baru saat memulai dan tidak dipertahankan di seluruh penghentian proses. Status koneksi jaringan, status koneksi Bluetooth, status UI sementara seperti pemilihan item.
Status UI sementara Bertahan dari perubahan konfigurasi dan penghentian proses yang dimulai oleh sistem, tidak disimpan tanpa batas waktu; dicakupkan ke satu layar atau aktivitas. Elemen UI yang diperluas atau diciutkan, posisi scroll, kueri penelusuran layar, atau tombol filter.
Status sesi sementara Bertahan dari penghentian proses latar belakang, tidak disimpan tanpa batas waktu; dibagikan di seluruh aktivitas, layanan, atau widget. Timer fokus aktif, keranjang checkout multi-langkah, atau draf formulir yang sedang berlangsung.
  • StateStore yang dienkapsulasi dalam pengelola atau repositori
  • Kunci yang mengimplementasikan PersistToDataStore dan disinkronkan menggunakan syncToDataStore
Status permanen Disimpan di disk tanpa batas waktu. Preferensi dan setelan pengguna (DataStore), data aplikasi terstruktur, dan galeri media (Room). Flow DataStore atau Room yang dikumpulkan di dalam transform menggunakan collectAsState

Menggunakan Status Compose di ViewModel

Di dalam ViewModel, Anda dapat mendeklarasikan input layar independen sebagai properti MutableState terperinci, mempertahankannya di seluruh penghentian proses dengan SavedStateHandle.saveable, mengupdate beberapa properti secara atomik dengan Snapshot.withMutableSnapshot, dan menggabungkannya dengan aliran data Flow repositori di dalam transform:

sealed interface TaskListState {
    data object Loading : TaskListState
    data class Success(
        val tasks: List<Task>,
        val selectedTaskIds: Set<String>,
        val isOnline: Boolean = true,
    ) : TaskListState
}

@HiltViewModel
class TaskListViewModel @Inject constructor(
    private val tasksRepository: TasksRepository,
    savedStateHandle: SavedStateHandle,
) : ViewModel() {

    var selectedTaskIds by mutableStateOf(emptySet<String>())
        private set

    var searchQuery by savedStateHandle.saveable { mutableStateOf("") }
        private set

    fun updateSearchQuery(query: String) {
        Snapshot.withMutableSnapshot {
            searchQuery = query
            selectedTaskIds = emptySet()
        }
    }

    val tasksState: State<TaskListState> = transform<TaskListState>(
        initialValue = TaskListState.Loading,
        scope = viewModelScope,
    ) {
        val tasks = remember(searchQuery) {
            tasksRepository.getTasks(searchQuery)
        }.collectAsState(initial = null).value
            ?: return@transform TaskListState.Loading

        TaskListState.Success(
            tasks = tasks,
            selectedTaskIds = selectedTaskIds,
        )
    }
}

Pola utama dalam ViewModel ini:

  • Gunakan SavedStateHandle.saveable untuk status tersimpan reaktif: Mendelegasikan searchQuery ke SavedStateHandle.saveable dengan mutableStateOf (dari androidx.lifecycle:lifecycle-viewmodel-compose) akan mempertahankan nilai di seluruh penghentian proses sebagai MutableState, sehingga transform melacak pembacaan secara otomatis. Hindari SavedStateHandle.saved kosong, yang menampilkan properti Kotlin biasa, bukan Compose State dan tidak memicu rekomposisi saat diubah.
  • Penulisan multi-properti atomik dengan Snapshot.withMutableSnapshot: Memperbarui searchQuery dan selectedTaskIds di dalam Snapshot.withMutableSnapshot mengisolasi penulisan dan meng-commit kedua perubahan dalam satu transaksi snapshot atomik, sehingga observer (termasuk transform) melihat kedua update bersama-sama dalam satu penerusan rekomposisi, bukan mengamati searchQuery baru yang dipasangkan dengan selectedTaskIds yang sudah tidak berlaku.
  • Menggunakan kembali flow di seluruh rekomposisi dengan remember(key): Menggabungkan tasksRepository.getTasks(searchQuery) dalam remember dengan searchQuery sebagai kunci akan memastikan bahwa saat selectedTaskIds berubah dan merekomposisi blok transform, Compose akan menggunakan kembali Flow database yang ada, bukan memulai ulang kueri.

Membagikan status sistem live di seluruh aplikasi Anda

Untuk status sistem aktif yang dibagikan di beberapa ViewModel, aktivitas, atau layanan—seperti status koneksi jaringan atau Bluetooth—Anda tidak ingin mempertahankan status usang ke disk di seluruh penghentian proses. Sebagai gantinya, buat kueri status sistem saat ini saat memulai, simpan dalam properti mutableStateOf pribadi di dalam singleton cakupan aplikasi, dan ekspos State<T> hanya baca:

interface NetworkMonitor {
    val isOnline: State<Boolean>
}

@Singleton
class LiveNetworkMonitor @Inject constructor(
    @ApplicationContext context: Context,
) : NetworkMonitor {
    private val connectivityManager =
        context.getSystemService(ConnectivityManager::class.java)

    private val _isOnline = mutableStateOf(checkCurrentConnectivity())
    override val isOnline: State<Boolean> = _isOnline

    private val callback = object : ConnectivityManager.NetworkCallback() {
        override fun onCapabilitiesChanged(
            network: Network,
            networkCapabilities: NetworkCapabilities,
        ) {
            _isOnline.value = networkCapabilities.hasCapability(
                NetworkCapabilities.NET_CAPABILITY_INTERNET
            )
        }

        override fun onLost(network: Network) {
            _isOnline.value = false
        }
    }

    init {
        connectivityManager.registerDefaultNetworkCallback(callback)
    }

    private fun checkCurrentConnectivity(): Boolean {
        val network = connectivityManager.activeNetwork ?: return false
        val capabilities =
            connectivityManager.getNetworkCapabilities(network) ?: return false
        return capabilities.hasCapability(
            NetworkCapabilities.NET_CAPABILITY_INTERNET
        )
    }
}

MutableState mendukung pembacaan dan update yang konsisten dari thread mana pun, sehingga NetworkCallback dapat mengupdate _isOnline.value langsung dari thread callback latar belakangnya. Jika callback latar belakang memperbarui beberapa properti MutableState secara bersamaan, gabungkan penulisan dalam Snapshot.withMutableSnapshot agar diterapkan secara atomik.

Komponen apa pun di aplikasi Anda dapat membaca networkMonitor.isOnline.value—baik sebagai pemeriksaan satu kali, di dalam composable UI, atau di dalam blok pengamatan status seperti listener, derivedStateOf, atau transform. Misalnya, TaskListViewModel dapat membaca networkMonitor.isOnline.value di dalam blok transform-nya:

val tasksState: State<TaskListState> = transform<TaskListState>(
    initialValue = TaskListState.Loading,
    scope = viewModelScope,
) {
    val tasks = remember(searchQuery) {
        tasksRepository.getTasks(searchQuery)
    }.collectAsState(initial = null).value
        ?: return@transform TaskListState.Loading

    TaskListState.Success(
        tasks = tasks,
        selectedTaskIds = selectedTaskIds,
        isOnline = networkMonitor.isOnline.value,
    )
}

Karena transform melacak networkMonitor.isOnline.value bersama dengan searchQuery, selectedTaskIds, dan repositori Flow, setiap perubahan pada konektivitas akan otomatis menghitung ulang TaskListState.Success tanpa membuat kueri ulang database.