StateStore   Android Jetpack'in bir parçasıdır.

StateStore, paylaşılan uygulama durumunu uygulamanız genelinde Compose State olarak depolayan ve gösteren, tip güvenliği sağlayan bir container'dır.

Kullanım alanları

Uygulama kapsamlı bir StateStore oluşturarak aynı işlemdeki uygulama bileşenleri arasında durum paylaşabilirsiniz:

  • Birden fazla etkinlik: Uygulamanız aynı anda birden fazla etkinlik gösteriyorsa (ör. bir e-posta istemcisinde gelen kutusu etkinliği ve düzenleyici etkinliği) veya aynı anda çalışan birden çok kopyayı destekleyen bir etkinliğe sahipse (ör. doküman görüntüleyici etkinliği) aralarında bilgi paylaşmak için StateStore kullanabilirsiniz.
  • Etkinlik ve hizmet: Bir etkinlik ile arka plan CarAppService veya AppFunction arasındaki durumu senkronize edin.
  • Etkinlik ve içerik sağlayıcı veya widget: Bir etkinlik ile ana ekran widget'ı arasında durum paylaşın.

Bir singleton'ın yalnızca anahtarlı arama veya DataStore destekli işlem sonlandırma geri yüklemesi olmadan bellek içi State<T> değerine ihtiyacı varsa bunun yerine doğrudan singleton'da mutableStateOf özelliklerini gösterebilirsiniz. Uygulamanızdaki durum API'lerinin karşılaştırması için Mimariniz için doğru durum API'sini seçme başlıklı makaleyi inceleyin.

API'ye genel bakış

androidx.appstate.statestore paketi, StateStore ve StateStoreKey sınıflarını sağlar:

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

Durumunuzu tanımlama

StateStore, değerleri depolarken ve alırken tip güvenliği sağlamak için değerleri okumak ve yazmak amacıyla kullanılan anahtarları tanımlamak üzere StateStoreKey sınıfını kullanır. StateStoreKey tanımlarını private veya internal, durumun o bölümüne sahip olan yöneticiye, depoya ya da modüle saklayın:

@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 örnekleri oluşturma ve ekleme

StateStore, uygulama veya modül kapsamında (örneğin, Application sınıfınızda veya bağımlılık ekleme modülünde) bir kez örneklendirdiğiniz ve StateStore'yi doğrudan kullanıcı arayüzü sınıflarına göstermek yerine durumu okuyan ve yazan yöneticilere veya depolara eklediğiniz normal bir sınıftır:

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

Okuma ve yazma durumu

Yöneticinizde veya deponuzda, özel StateStoreKey tanımlarınızı kullanarak eklenen StateStore içindeki değerleri okuyun, yazın ve güncelleyin. Ayrıca, salt okunur State<T> özelliklerini ve mutasyon işlevlerini çağrı yapanlara gösterin.

Okunma durumu

State<T> nesnesi almak için getState(stateKey, defaultValue) yöntemini kullanın. Anahtar henüz başlatılmadıysa StateStore, anahtarı defaultValue parametresiyle doldurur. Bu parametre varsayılan olarak stateKey.defaultValue değerini alır (veya doldurulmamış bir anahtarı başlatırken özel bir defaultValue değeri iletebilirsiniz). getState bir State<T> döndürdüğü için ViewModel'ler, kullanıcı arayüzü composable'ları, widget'lar ve transform blokları, yöneticinin özelliğini reaktif olarak okuyabilir:

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

Eyaleti yazın

Yöneticinizde bir anahtarla ilişkili değeri ilk kullanıma hazırlamak veya değiştirmek için setState(stateKey, value) kullanın:

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

Durumu güncelleme

Bir durumu mevcut değerine göre güncellemeniz gerekiyorsa updateState kullanın. Bu, mevcut değeri alır (veya ayarlanmamışsa varsayılan değeri kullanır) ve güncelleme lambdanızı uygular:

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

Entegrasyonlar

StateStore, işlem sonlandırma ve uygulama yeniden başlatma işlemleri boyunca durumu korumak ve geri yüklemek için Jetpack DataStore ile entegre olur.

DataStore

androidx.appstate.statestore:statestore-datastore yapısı, paylaşılan StateStore örneğinizde kalıcılığı bir kez yapılandırmanıza olanak tanır. Böylece, herhangi bir yöneticinin kalıcı anahtarları otomatik olarak kaydedilir ve DataStore kullanılarak diskten geri yüklenir.

androidx.appstate.datastore paketi aşağıdaki API'leri sağlar:

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 uygulayın: PersistToDataStore arayüzünü uygulayarak kalıcı olması gereken tüm StateStoreKey öğelerini işaretleyin:

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

  1. StateStore öğesini DataStore'a bağlama: Paylaşılan StateStore örneğinizi ayarlarken (ör. Application sınıfınızda veya bağımlılık ekleme modülünüzde) syncToDataStore öğesini bir kez çağırın. syncToDataStore, kaydedilen değerleri diskten geri yüklemek ve gelecekteki değişiklikleri sağlanan coroutineScope içinde kaydetmeye devam etmek için süresiz olarak askıya alınan bir suspend işlevi olduğundan, bu işlevi launch içinde çağırın ve uygulama düzeyindeki CoroutineScope değerinizi iletin. Başlangıçta getState, bellekteki defaultValue değerini döndürür ve DataStore, diskten okuma işlemini tamamladıktan sonra State değerini günceller:

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 kalıcılık yönergeleri

syncToDataStore, bir PersistToDataStore anahtarının değeri her değiştiğinde serileştirme yapıp diske yazdığından, nelerin ne zaman kalıcı hale getirileceğini seçerken aşağıdaki yönergeleri uygulayın:

  • Her zamanlayıcı adımı yerine başlangıç veya bitiş zaman damgalarını kaydedin: Zamanlayıcılar veya oynatma ilerleme durumu için her saniye veya karede bir PersistToDataStore anahtarını güncellemek yerine sabit bir referans değeri (ör. endTimeMillis veya startedAtMillis) kullanın ve kullanıcı arayüzünde canlı olarak geçen veya kalan süreyi hesaplayın.
  • Kalıcı bir anahtarı güncellemeden önce hızlı kullanıcı girişini geciktirin: Metin taslakları, arama sorguları veya kaydırma çubukları için anlık girişi bellek içi MutableState (veya PersistToDataStore uygulamayan bir StateStoreKey) içinde tutun ve yalnızca giriş tamamlandıktan sonra kalıcı StateStoreKey üzerinde setState çağrısı yapın.
  • Kalıcı anahtarları basit tutun: StateStore içindeki tüm PersistToDataStore anahtarları, her güncellemede tek bir dosyada birlikte serileştirilir. Etkin kimlikler, filtre seçimleri veya devam eden form taslakları gibi kompakt oturum durumlarını kalıcı hale getirin ve büyük koleksiyonları veya sorgulanabilir veri kümelerini Room'da saklayın.