StateStore   جزء من Android Jetpack.

‫StateStore هي حاوية تمنع أخطاء الكتابة وتخزّن حالة التطبيق المشتركة وتعرضها على شكل State في تطبيقك.

حالات الاستخدام

من خلال إنشاء StateStore على مستوى التطبيق، يمكنك مشاركة الحالة بين مكوّنات التطبيق ضمن العملية نفسها:

  • الأنشطة المتعددة: إذا كان تطبيقك يعرض أنشطة متعددة في الوقت نفسه (مثل نشاط البريد الوارد ونشاط المحرِّر في برنامج بريد إلكتروني) أو يتضمّن نشاطًا يتيح فتح نسختين من التطبيق (مثل نشاط عارض المستندات)، يمكنك استخدام StateStore لمشاركة المعلومات بينها.
  • النشاط والخدمة: يمكنك مزامنة الحالة بين نشاط وCarAppService في الخلفية أو AppFunction.
  • النشاط وأداة توفير المحتوى أو التطبيق المصغّر: يمكنك مشاركة الحالة بين نشاط وتطبيق مصغّر على الشاشة الرئيسية.

إذا كان الكائن الأحادي يحتاج فقط إلى State<T> في الذاكرة بدون البحث باستخدام مفتاح أو استعادة البيانات بعد إيقاف العملية نهائيًا من خلال DataStore، يمكنك عرض خصائص 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> بإذن بالقراءة فقط ودوال التغيير للمتصلين.

حالة القراءة

استخدِم getState(stateKey, defaultValue) لاسترداد عنصر State<T>. إذا لم تتم تهيئة المفتاح بعد، يملأه 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. يؤدي ذلك إلى استرداد القيمة الحالية (أو استخدام القيمة التلقائية في حال عدم ضبطها) وتطبيق دالة 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 واجهات برمجة التطبيقات التالية:

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: يمكنك استدعاء syncToDataStore مرة واحدة عند إعداد مثيل StateStore المشترك (على سبيل المثال، في فئة Application أو وحدة حقن التبعية). بما أنّ syncToDataStore هي دالة suspend يتم تعليقها إلى أجل غير مسمى لاستعادة القيم المحفوظة من القرص ومواصلة حفظ التغييرات المستقبلية في coroutineScope المقدَّم، يمكنك استدعاؤها داخل launch وتمرير CoroutineScope على مستوى التطبيق. عند بدء التشغيل، تعرض getState في البداية defaultValue في الذاكرة وتعدّل State بعد انتهاء DataStore من القراءة من القرص:

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، اتّبِع هذه الإرشادات عند اختيار البيانات التي تريد الاحتفاظ بها ووقت الاحتفاظ بها:

  • حفظ الطوابع الزمنية للبدء أو الانتهاء بدلاً من كل علامة في الموقّت: بالنسبة إلى الموقّتات أو مستوى تقدّم التشغيل، يمكنك الاحتفاظ بقيمة مرجعية ثابتة (مثل endTimeMillis أو startedAtMillis) بدلاً من تعديل مفتاح PersistToDataStore كل ثانية أو إطار، واحتساب الوقت المنقضي أو المتبقي في واجهة المستخدم.
  • تجنُّب الإدخال السريع للمستخدم قبل تعديل مفتاح محفوظ: بالنسبة إلى مسودات النصوص أو طلبات البحث أو أشرطة التمرير، يمكنك الاحتفاظ بالإدخال الفوري في MutableState في الذاكرة (أو StateStoreKey لا ينفّذ PersistToDataStore) واستدعاء setState فقط على StateStoreKey المحفوظ بعد استقرار الإدخال.
  • الحفاظ على خفة وزن المفاتيح الثابتة: يتم إجراء تسلسل لجميع مفاتيح PersistToDataStore في StateStore معًا في ملف واحد في كل تحديث. يمكنك الاحتفاظ بحالة الجلسة المدمجة، مثل أرقام التعريف النشطة أو اختيارات الفلاتر أو مسودات النماذج قيد التقدم، والاحتفاظ بالمجموعات الكبيرة أو مجموعات البيانات القابلة للاستعلام في Room.