StateStore Android Jetpack の一部。
StateStore は、共有アプリケーションの状態を Compose State としてアプリ全体に保存して公開する、タイプセーフなコンテナです。
ユースケース
アプリケーション スコープの StateStore を作成することで、同じプロセス内のアプリケーション コンポーネント間で状態を共有できます。
- 複数のアクティビティ: アプリが複数のアクティビティを同時に表示する場合(メール クライアントの受信トレイ アクティビティとエディタ アクティビティなど)、またはマルチインスタンスをサポートするアクティビティ(ドキュメント ビューア アクティビティなど)がある場合は、
StateStoreを使用してアクティビティ間で情報を共有できます。 - アクティビティとサービス: アクティビティとバックグラウンドの
CarAppServiceまたは AppFunction の間で状態を同期します。 - Activity とコンテンツ プロバイダまたはウィジェット: Activity とホーム画面ウィジェットの間で状態を共有します。
シングルトンが、キー付きルックアップや DataStore を利用したプロセスの終了の復元を必要とせず、メモリ内 State<T> のみを必要とする場合は、代わりにシングルトンで 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 クラスや依存関係挿入モジュールなど)で 1 回インスタンス化し、状態の読み取りと書き込みを行うマネージャーまたはリポジトリに挿入する通常のクラスです。StateStore を 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, )
読み取りと書き込みの状態
マネージャーまたはリポジトリ内で、非公開の 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 を使用します。これにより、現在の値が取得され(設定されていない場合はデフォルトが使用されます)、更新ラムダが適用されます。
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>,
)
PersistToDataStoreを実装する:PersistToDataStoreインターフェースを実装して、永続化する必要があるStateStoreKeyをマークします。
@Serializable private data object FocusSessionKey : StateStoreKey<FocusSession>(defaultValue = FocusSession.Idle), PersistToDataStore
StateStoreを DataStore にバインドする: 共有StateStoreインスタンスを設定するときに(Applicationクラスや依存関係挿入モジュールなどで)syncToDataStoreを 1 回呼び出します。syncToDataStoreは、ディスクから保存された値を復元し、指定されたcoroutineScopeで今後の変更を保存し続けるために無期限に一時停止するsuspend関数であるため、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 の永続性に関するガイドライン
syncToDataStore は、PersistToDataStore キーの値が変更されるたびにシリアル化してディスクに書き込むため、何をいつ永続化するかを選択する際は、次のガイドラインに沿ってください。
- タイマーのティックごとに保存するのではなく、開始または終了のタイムスタンプを保存する: タイマーや再生の進行状況については、
PersistToDataStoreキーを 1 秒ごとまたはフレームごとに更新するのではなく、安定した参照値(endTimeMillisやstartedAtMillisなど)を保持し、UI でライブの経過時間または残り時間を計算します。 - 永続化されたキーを更新する前に、ユーザーの急速な入力をデバウンスする: テキストの下書き、検索クエリ、スライダーの場合、即時入力をメモリ内の
MutableState(またはPersistToDataStoreを実装しないStateStoreKey)に保持し、入力が確定した後にのみ、永続化されたStateStoreKeyでsetStateを呼び出します。 - 永続化されたキーを軽量に保つ:
StateStore内のすべてのPersistToDataStoreキーは、更新のたびに 1 つのファイルにシリアル化されます。アクティブな ID、フィルタの選択、進行中のフォームの下書きなど、コンパクトなセッション状態を保持し、大規模なコレクションやクエリ可能なデータセットを Room に保持します。