StateStore   Android Jetpack का हिस्सा.

StateStore, टाइप-सेफ़ कंटेनर है. यह आपके ऐप्लिकेशन में, शेयर किए गए ऐप्लिकेशन की स्थिति को Compose State के तौर पर सेव करता है और दिखाता है.

इस्तेमाल के उदाहरण

ऐप्लिकेशन के स्कोप वाला StateStore बनाकर, एक ही प्रोसेस में ऐप्लिकेशन के कॉम्पोनेंट के बीच स्टेट शेयर किया जा सकता है:

  • एक से ज़्यादा गतिविधियां: अगर आपका ऐप्लिकेशन एक साथ कई गतिविधियां दिखाता है (जैसे, ईमेल क्लाइंट में इनबॉक्स गतिविधि और एडिटर गतिविधि) या उसमें कोई ऐसी गतिविधि है जो एक से ज़्यादा इंस्टेंस के साथ काम करती है (जैसे, दस्तावेज़ व्यूअर गतिविधि), तो उनके बीच जानकारी शेयर करने के लिए StateStore का इस्तेमाल किया जा सकता है.
  • गतिविधि और सेवा: किसी गतिविधि और बैकग्राउंड CarAppService या AppFunction के बीच की स्थिति को सिंक करें.
  • ऐक्टिविटी और कॉन्टेंट देने वाला या विजेट: किसी ऐक्टिविटी और होम स्‍क्रीन विजेट के बीच स्थिति शेयर करें.

अगर किसी सिंगलटन को सिर्फ़ इन-मेमोरी State<T> की ज़रूरत है और उसे कीड लुकअप या DataStore-backed process-death restoration की ज़रूरत नहीं है, तो सिंगलटन पर सीधे तौर पर mutableStateOf प्रॉपर्टी को एक्सपोज़ किया जा सकता है. अपने ऐप्लिकेशन में स्टेट एपीआई की तुलना करने के लिए, अपने आर्किटेक्चर के लिए सही स्टेट एपीआई चुनें लेख पढ़ें.

एपीआई के बारे में खास जानकारी

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 को सीधे यूज़र इंटरफ़ेस (यूआई) क्लास में एक्सपोज़ करने की ज़रूरत नहीं पड़ती:

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

स्टेट को पढ़ने और उसमें बदलाव करने की अनुमति

अपने मैनेजर या रिपॉज़िटरी में, इंजेक्ट किए गए StateStore में वैल्यू पढ़ें, लिखें, और अपडेट करें. इसके लिए, अपनी निजी StateStoreKey डेफ़िनिशन का इस्तेमाल करें. साथ ही, कॉलर को सिर्फ़ पढ़ने के लिए उपलब्ध State<T> प्रॉपर्टी और म्यूटेशन फ़ंक्शन दिखाएं.

पढ़े जाने की स्थिति

State<T> ऑब्जेक्ट को वापस पाने के लिए, getState(stateKey, defaultValue) का इस्तेमाल करें. अगर बटन को अब तक शुरू नहीं किया गया है, तो StateStore इसे defaultValue पैरामीटर से भर देता है. यह डिफ़ॉल्ट रूप से stateKey.defaultValue पर सेट होता है. इसके अलावा, खाली बटन को शुरू करते समय, कस्टम defaultValue को पास किया जा सकता है. getState, State<T> दिखाता है. इसलिए, ViewModels, यूज़र इंटरफ़ेस (यूआई) कंपोज़ेबल, विजेट, और 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 पैकेज में ये एपीआई उपलब्ध हैं:

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 लागू करें: किसी भी ऐसे StateStoreKey को मार्क करें जिसे PersistToDataStore इंटरफ़ेस लागू करके बनाए रखना है:

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

  1. StateStore को DataStore से बाइंड करें: अपने शेयर किए गए StateStore इंस्टेंस को सेट अप करते समय, syncToDataStore को एक बार कॉल करें. उदाहरण के लिए, अपनी Application क्लास या डिपेंडेंसी इंजेक्शन मॉड्यूल में. 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 में डेटा सेव करने से जुड़े दिशा-निर्देश

syncToDataStore, PersistToDataStore कुंजी की वैल्यू में बदलाव होने पर, उसे क्रम में लगाता है और डिस्क में सेव करता है. इसलिए, क्या और कब सेव करना है, यह चुनते समय इन दिशा-निर्देशों का पालन करें:

  • टाइमर की हर टिक के बजाय, शुरू या खत्म होने के टाइमस्टैंप सेव करें: टाइमर या प्लेबैक की प्रोग्रेस के लिए, हर सेकंड या फ़्रेम में PersistToDataStore कुंजी को अपडेट करने के बजाय, एक स्थिर रेफ़रंस वैल्यू (जैसे, endTimeMillis या startedAtMillis) को बनाए रखें. साथ ही, यूज़र इंटरफ़ेस (यूआई) में लाइव बीता हुआ या बचा हुआ समय दिखाएं.
  • सेव की गई कुंजी को अपडेट करने से पहले, उपयोगकर्ता के तेज़ी से किए गए इनपुट को डीबाउंस करना: टेक्स्ट ड्राफ़्ट, सर्च क्वेरी या स्लाइडर के लिए, तुरंत किए गए इनपुट को इन-मेमोरी MutableState (या ऐसे StateStoreKey में जो PersistToDataStore को लागू नहीं करता) में सेव करें. इसके बाद, इनपुट सेटल होने के बाद ही, सेव किए गए StateStoreKey पर setState को कॉल करें.
  • सेव की गई कुंजियों को छोटा रखें: हर अपडेट पर, StateStore में मौजूद सभी PersistToDataStore कुंजियों को एक साथ एक ही फ़ाइल में क्रम से लगाया जाता है. सेशन की छोटी-मोटी जानकारी को सेव करके रखें. जैसे, ऐक्टिव आईडी, फ़िल्टर के लिए चुने गए विकल्प या फ़ॉर्म के ड्राफ़्ट जिन पर काम चल रहा है. साथ ही, बड़े कलेक्शन या क्वेरी किए जा सकने वाले डेटासेट को Room में सेव करके रखें.