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 클래스 또는 종속 항목 삽입 모듈)에서 한 번 인스턴스화하고 상태를 읽고 쓰는 관리자 또는 저장소에 삽입하는 일반 클래스입니다. 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>,
)
  1. PersistToDataStore 구현: PersistToDataStore 인터페이스를 구현하여 유지해야 하는 StateStoreKey를 표시합니다.

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

  1. StateStore를 DataStore에 바인딩: 공유 StateStore 인스턴스를 설정할 때 (예: Application 클래스 또는 종속 항목 삽입 모듈에서) syncToDataStore를 한 번 호출합니다. 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 키를 업데이트하는 대신 안정적인 참조 값 (예: endTimeMillis 또는 startedAtMillis)을 유지하고 UI에서 실시간 경과 시간 또는 남은 시간 표시를 계산합니다.
  • 지속 키를 업데이트하기 전에 빠른 사용자 입력 디바운스: 텍스트 초안, 검색어 또는 슬라이더의 경우 즉각적인 입력을 메모리 내 MutableState (또는 PersistToDataStore를 구현하지 않는 StateStoreKey)에 보관하고 입력이 안정된 후에만 지속 StateStoreKey에서 setState를 호출합니다.
  • 지속 키를 가볍게 유지: StateStore의 모든 PersistToDataStore 키는 업데이트할 때마다 단일 파일로 함께 직렬화됩니다. 활성 ID, 필터 선택, 진행 중인 양식 초안과 같은 간단한 세션 상태를 유지하고 대규모 컬렉션 또는 쿼리 가능한 데이터 세트를 Room에 보관합니다.