StateStore   Android Jetpack 的一部分。

StateStore 是類型安全的容器,可在整個應用程式中儲存及公開共用應用程式狀態,做為 Compose State。

用途

建立應用程式範圍的 StateStore,即可在同一程序中的應用程式元件之間共用狀態:

  • 多項活動:如果應用程式同時顯示多項活動 (例如電子郵件用戶端中的收件匣活動和編輯器活動),或有支援多實體的活動 (例如文件檢視器活動),您可以使用 StateStore 在這些活動之間共用資訊。
  • 活動和服務:同步處理活動與背景 CarAppService 或 AppFunction 之間的狀態。
  • Activity 和內容供應器或小工具:在 Activity 和主畫面小工具之間共用狀態。

如果單例模式只需要記憶體內 State<T>,不需要鍵控查詢或 DataStore 支援的程序終止還原,您可以改為直接在單例模式上公開 mutableStateOf 屬性。如要比較應用程式中的狀態 API,請參閱「選擇適合架構的狀態 API」。

API 總覽

androidx.appstate.statestore 套件提供 StateStore 和 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,
)

定義狀態

為確保在儲存及擷取值時的類型安全,StateStore 會使用 StateStoreKey 類別定義用於讀取及寫入值的索引鍵。將 StateStoreKey 定義 private 或 internal 保留給擁有該狀態切片的管理員、存放區或模組:

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

建立及插入 StateStore 執行個體

StateStore 是一般類別,您可以在應用程式或模組範圍 (例如 Application 類別或依附元件插入模組) 中例項化一次,並插入讀取和寫入狀態的管理工具或存放區,而不是直接向 UI 類別公開 StateStore:

// 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,
)

讀取和寫入狀態

在管理工具或存放區中,使用私人 StateStoreKey 定義讀取、寫入及更新插入的 StateStore 中的值,並向呼叫端公開唯讀 State<T> 屬性和變動函式。

讀取狀態

使用 getState(stateKey, defaultValue) 擷取 State<T> 物件。如果金鑰尚未初始化,StateStore 會使用 defaultValue 參數填入金鑰,預設值為 stateKey.defaultValue (或者,您可以在初始化未填入的金鑰時傳遞自訂 defaultValue)。由於 getState 會傳回 State<T>,因此 ViewModel、UI 可組合項、小工具和 transform 區塊可以回應式讀取管理員的屬性:

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

寫入狀態

在管理工具中使用 setState(stateKey, value),初始化或取代與鍵相關聯的值:

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

更新狀態

如需根據目前的值更新狀態,請使用 updateState。這會擷取目前的值 (如果未設定,則使用預設值),並套用更新 lambda:

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

整合

StateStore 與 Jetpack DataStore 整合,可在程序終止和應用程式重新啟動時,保留及還原狀態。

DataStore

androidx.appstate.statestore:statestore-datastore 構件可讓您在共用的 StateStore 執行個體上設定一次持續性,這樣任何管理員的持續性金鑰都會自動儲存,並使用 DataStore 從磁碟還原。

androidx.appstate.datastore 套件提供下列 API:

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. 實作 PersistToDataStore:實作 PersistToDataStore 介面,標記應保留的任何 StateStoreKey:

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

  1. 將 StateStore 繫結至 DataStore:設定共用 StateStore 執行個體時 (例如在 Application 類別或依附元件插入模組中),呼叫 syncToDataStore 一次。由於 syncToDataStore 是 suspend 函式,會無限期暫停,從磁碟還原已儲存的值,並繼續將日後的變更儲存在提供的 coroutineScope 中,因此請在 launch 內呼叫該函式,並傳遞應用程式層級的 CoroutineScope。啟動時,getState 一開始會傳回記憶體中的 defaultValue,並在 DataStore 完成從磁碟讀取後更新 State:

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

DataStore 持續性指南

每當 PersistToDataStore 鍵的值變更時,syncToDataStore 都會序列化並寫入磁碟,因此在選擇要保留的內容和時間時,請遵循下列指南:

  • 儲存開始或結束時間戳記,而非每個計時器刻度:針對計時器或播放進度,請保留穩定的參考值 (例如 endTimeMillis 或 startedAtMillis),而非每秒或每影格更新 PersistToDataStore 鍵,並在 UI 中計算即時經過或剩餘的顯示時間。
  • 在更新永久金鑰前,先對快速使用者輸入進行去抖動處理:對於文字草稿、搜尋查詢或滑桿,請將即時輸入保留在記憶體內 MutableState (或未實作 PersistToDataStore 的 StateStoreKey) 中,並在輸入穩定後,才對永久 StateStoreKey 呼叫 setState。
  • 保持輕量化的永久金鑰:每次更新時,StateStore 中的所有 PersistToDataStore 金鑰都會一起序列化為單一檔案。保留精簡的工作階段狀態,例如有效 ID、篩選器選項或進行中的表單草稿,並將大型集合或可查詢的資料集保留在 Room 中。