StateStore   Bagian dari Android Jetpack.

StateStore adalah penampung yang aman dari jenis yang menyimpan dan mengekspos status aplikasi bersama sebagai State Compose di seluruh aplikasi Anda.

Kasus penggunaan

Dengan membuat StateStore cakupan aplikasi, Anda dapat berbagi status antara komponen aplikasi dalam proses yang sama:

  • Beberapa aktivitas: Jika aplikasi Anda menampilkan beberapa aktivitas secara bersamaan (seperti aktivitas kotak masuk dan aktivitas editor di program email) atau memiliki aktivitas yang mendukung multi-instance (seperti aktivitas penampil dokumen), Anda dapat menggunakan StateStore untuk berbagi informasi di antara aktivitas tersebut.
  • Aktivitas dan layanan: Sinkronkan status antara aktivitas dan CarAppService atau AppFunction latar belakang.
  • Aktivitas dan penyedia konten atau widget: Berbagi status antara aktivitas dan widget layar utama.

Jika singleton hanya memerlukan State<T> dalam memori tanpa pencarian dengan kunci atau pemulihan penghentian proses yang didukung DataStore, Anda dapat mengekspos properti mutableStateOf langsung di singleton. Untuk perbandingan API status di seluruh aplikasi, lihat Memilih API status yang tepat di seluruh arsitektur.

Ringkasan API

Paket androidx.appstate.statestore menyediakan class StateStore dan StateStoreKey:

package androidx.appstate.statestore

import androidx.compose.runtime.State
import kotlinx.serialization.Serializable

public class StateStore {
    public val keys: Set<StateStoreKey<*>>
    public fun <T> getState(
        stateKey: StateStoreKey<T>,
        defaultValue: T = stateKey.defaultValue,
    ): State<T>
    public fun <T> setState(stateKey: StateStoreKey<T>, value: T)
    public inline fun <T> updateState(
        stateKey: StateStoreKey<T>,
        defaultValue: T = stateKey.defaultValue,
        update: (T) -> T,
    )
}

@Serializable
public open class StateStoreKey<T>(
    public val defaultValue: T,
)

Tentukan status Anda

Untuk memastikan keamanan jenis saat menyimpan dan mengambil nilai, StateStore menggunakan class StateStoreKey untuk menentukan kunci yang digunakan untuk membaca dan menulis nilai. Simpan definisi StateStoreKey private atau internal ke pengelola, repositori, atau modul yang memiliki bagian status tersebut:

@Serializable
sealed interface FocusSession {
    @Serializable
    data object Idle : FocusSession

    @Serializable
    data class Running(
        val taskId: String,
        val endTimeMillis: Long,
    ) : FocusSession

    @Serializable
    data class Paused(
        val taskId: String,
        val remainingMillis: Long,
    ) : FocusSession
}

@Serializable
private data object FocusSessionKey :
    StateStoreKey<FocusSession>(defaultValue = FocusSession.Idle)

Membuat dan menginjeksikan instance StateStore

StateStore adalah class reguler yang Anda buat instance-nya sekali di cakupan aplikasi atau modul (misalnya, di class Application atau modul injeksi dependensi) dan dimasukkan ke dalam pengelola atau repositori yang membaca dan menulis status, bukan mengekspos StateStore langsung ke class UI:

// Instantiate at the application or DI module level
val stateStore = StateStore()

// Inject into managers or repositories that encapsulate their own keys
class FocusSessionManager(
    private val stateStore: StateStore,
)

Status baca dan tulis

Di dalam pengelola atau repositori, baca, tulis, dan perbarui nilai dalam StateStore yang dimasukkan menggunakan definisi StateStoreKey pribadi, dan ekspos properti State<T> hanya baca dan fungsi mutasi ke pemanggil.

Status baca

Gunakan getState(stateKey, defaultValue) untuk mengambil objek State<T>. Jika kunci belum diinisialisasi, StateStore akan mengisinya dengan parameter defaultValue, yang secara default ditetapkan ke stateKey.defaultValue (atau Anda dapat meneruskan defaultValue kustom saat menginisialisasi kunci yang belum diisi). Karena getState menampilkan State<T>, ViewModel, composable UI, widget, dan blok transform dapat membaca properti pengelola secara reaktif:

class FocusSessionManager(
    private val stateStore: StateStore,
) {
    val session: State<FocusSession> = stateStore.getState(FocusSessionKey)
}

class FocusTimerViewModel(
    focusSessionManager: FocusSessionManager,
) : ViewModel() {
    val session: State<FocusSession> = focusSessionManager.session
}

@Composable
fun FocusBanner(viewModel: FocusTimerViewModel = viewModel()) {
    // Reads state reactively; recomposes when FocusSessionKey updates
    when (val session = viewModel.session.value) {
        FocusSession.Idle -> Text(text = "No active focus session")
        is FocusSession.Running -> Text(text = "Focusing on ${session.taskId}")
        is FocusSession.Paused -> Text(text = "Paused: ${session.taskId}")
    }
}

Status tulis

Gunakan setState(stateKey, value) di dalam pengelola untuk melakukan inisialisasi atau mengganti nilai yang terkait dengan kunci:

class FocusSessionManager(
    private val stateStore: StateStore,
) {
    val session: State<FocusSession> = stateStore.getState(FocusSessionKey)

    fun startFocus(taskId: String, endTimeMillis: Long) {
        stateStore.setState(
            FocusSessionKey,
            FocusSession.Running(
                taskId = taskId,
                endTimeMillis = endTimeMillis,
            ),
        )
    }

    fun endFocus() {
        stateStore.setState(FocusSessionKey, FocusSession.Idle)
    }
}

Memperbarui status

Jika Anda perlu memperbarui status berdasarkan nilainya saat ini, gunakan updateState. Tindakan ini akan mengambil nilai saat ini (atau menggunakan nilai default jika tidak ditetapkan) dan menerapkan lambda update Anda:

class FocusSessionManager(
    private val stateStore: StateStore,
) {
    val session: State<FocusSession> = stateStore.getState(FocusSessionKey)

    fun pauseFocus(nowMillis: Long) {
        stateStore.updateState(FocusSessionKey) { current ->
            if (current is FocusSession.Running) {
                FocusSession.Paused(
                    taskId = current.taskId,
                    remainingMillis = (current.endTimeMillis - nowMillis)
                        .coerceAtLeast(0L),
                )
            } else {
                current
            }
        }
    }

    fun resumeFocus(nowMillis: Long) {
        stateStore.updateState(FocusSessionKey) { current ->
            if (current is FocusSession.Paused) {
                FocusSession.Running(
                    taskId = current.taskId,
                    endTimeMillis = nowMillis + current.remainingMillis,
                )
            } else {
                current
            }
        }
    }
}

Integrasi

StateStore terintegrasi dengan DataStore Jetpack untuk mempertahankan dan memulihkan status di seluruh penghentian proses dan mulai ulang aplikasi.

DataStore

Artefak androidx.appstate.statestore:statestore-datastore memungkinkan Anda mengonfigurasi persistensi sekali pada instance StateStore bersama sehingga kunci yang dipertahankan pengelola akan otomatis disimpan dan dipulihkan dari disk menggunakan DataStore.

Paket androidx.appstate.datastore menyediakan API berikut:

package androidx.appstate.datastore

import androidx.appstate.statestore.StateStore
import androidx.datastore.core.DataStore
import kotlinx.coroutines.CoroutineScope

public interface PersistToDataStore

public suspend fun StateStore.syncToDataStore(
    path: String,
    coroutineScope: CoroutineScope,
)

public suspend fun StateStore.syncToDataStore(
    dataStore: DataStore<StateStorePreferences>,
)
  1. Terapkan PersistToDataStore: Tandai StateStoreKey yang harus dipertahankan dengan menerapkan antarmuka PersistToDataStore:

@Serializable
private data object FocusSessionKey :
    StateStoreKey<FocusSession>(defaultValue = FocusSession.Idle),
    PersistToDataStore

  1. Mengikat StateStore ke DataStore: Panggil syncToDataStore sekali saat menyiapkan instance StateStore bersama (misalnya, di class Application atau modul injeksi dependensi). Karena syncToDataStore adalah fungsi suspend yang ditangguhkan tanpa batas waktu untuk memulihkan nilai yang disimpan dari disk dan terus menyimpan perubahan mendatang dalam coroutineScope yang disediakan, panggil fungsi tersebut di dalam launch dan teruskan CoroutineScope tingkat aplikasi Anda. Saat memulai, getState awalnya menampilkan defaultValue dalam memori dan memperbarui State setelah DataStore selesai membaca dari disk:

class ProductivityApplication : Application() {
    val applicationScope = CoroutineScope(SupervisorJob() + Dispatchers.Default)

    val stateStore = StateStore().also { store ->
        // Restore saved values and automatically persist future changes
        // for all PersistToDataStore keys across managers
        applicationScope.launch {
            store.syncToDataStore(
                path = dataStoreFile("statestore.json").absolutePath,
                coroutineScope = applicationScope,
            )
        }
    }

    val focusSessionManager = FocusSessionManager(stateStore)
}

Panduan persistensi DataStore

Karena syncToDataStore melakukan serialisasi dan menulis ke disk setiap kali nilai kunci PersistToDataStore berubah, ikuti panduan ini saat memilih apa dan kapan harus mempertahankan:

  • Simpan stempel waktu mulai atau akhir, bukan setiap tanda timer: Untuk timer atau progres pemutaran, pertahankan nilai referensi yang stabil (seperti endTimeMillis atau startedAtMillis), bukan memperbarui kunci PersistToDataStore setiap detik atau frame, dan hitung tampilan waktu berlalu atau waktu tersisa secara live di UI.
  • Debounce input pengguna yang cepat sebelum memperbarui kunci yang dipertahankan: Untuk draf teks, kueri penelusuran, atau penggeser, tahan input langsung dalam MutableState dalam memori (atau StateStoreKey yang tidak mengimplementasikan PersistToDataStore) dan hanya panggil setState pada StateStoreKey yang dipertahankan setelah input ditetapkan.
  • Buat kunci yang dipertahankan tetap ringan: Semua kunci PersistToDataStore dalam StateStore diserialisasi bersama menjadi satu file pada setiap update. Pertahankan status sesi yang ringkas—seperti ID aktif, pilihan filter, atau draf formulir yang sedang berlangsung—dan simpan koleksi besar atau set data yang dapat dikueri di Room.