StateStore   เป็นส่วนหนึ่งของ Android Jetpack

StateStore เป็นคอนเทนเนอร์ที่ปลอดภัยในการกำหนดประเภท ซึ่งจัดเก็บและแสดงสถานะแอปพลิเคชันที่แชร์เป็น Compose State ทั่วทั้งแอป

กรณีการใช้งาน

การสร้าง StateStore ที่กำหนดขอบเขตระดับแอปพลิเคชันจะช่วยให้คุณแชร์สถานะระหว่างคอมโพเนนต์ของแอปพลิเคชันภายในกระบวนการเดียวกันได้ ดังนี้

  • กิจกรรมหลายรายการ: หากแอปแสดงกิจกรรมหลายรายการพร้อมกัน (เช่น กิจกรรมกล่องจดหมายและกิจกรรมเครื่องมือแก้ไขในโปรแกรมรับส่งอีเมล) หรือมีกิจกรรมที่รองรับอินสแตนซ์หลายรายการ (เช่น กิจกรรมโปรแกรมดูเอกสาร) คุณสามารถใช้ StateStore เพื่อแชร์ข้อมูลระหว่างกิจกรรมได้
  • กิจกรรมและบริการ: ซิงค์สถานะระหว่างกิจกรรมกับ CarAppService หรือ AppFunction ในเบื้องหลัง
  • กิจกรรมและ Content Provider หรือวิดเจ็ต: แชร์สถานะระหว่างกิจกรรมกับวิดเจ็ตบนหน้าจอหลัก

หากซิงเกิลตันต้องการเพียง State<T> ในหน่วยความจำโดยไม่มีการค้นหาแบบคีย์หรือการกู้คืนการสิ้นสุดการประมวลผลที่รองรับโดย DataStore คุณสามารถแสดงพร็อพเพอร์ตี้ 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 หรือโมดูลการแทรกทรัพยากร Dependency) และแทรกลงในตัวจัดการหรือที่เก็บซึ่งอ่านและเขียนสถานะ แทนที่จะแสดง 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,
)

สถานะการอ่านและเขียน

ภายในเครื่องมือจัดการหรือที่เก็บ ให้อ่าน เขียน และอัปเดตค่าใน StateStore ที่แทรกโดยใช้คำจำกัดความ StateStoreKey ส่วนตัว และแสดงพร็อพเพอร์ตี้ State<T> แบบอ่านอย่างเดียวและฟังก์ชันการเปลี่ยนแปลงต่อผู้เรียก

สถานะการอ่าน

ใช้ getState(stateKey, defaultValue) เพื่อดึงข้อมูลออบเจ็กต์ State<T> หากยังไม่ได้เริ่มต้นคีย์ StateStore จะป้อนข้อมูลด้วยพารามิเตอร์ defaultValue ซึ่งมีค่าเริ่มต้นเป็น stateKey.defaultValue (หรือคุณสามารถส่ง defaultValue ที่กำหนดเองเมื่อเริ่มต้นคีย์ที่ไม่มีข้อมูล) เนื่องจาก getState แสดงผล State<T>, ViewModels, UI composables, วิดเจ็ต และบล็อก 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 ช่วยให้คุณกำหนดค่าความคงอยู่ได้ 1 ครั้งในอินสแตนซ์ 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: ทำเครื่องหมาย StateStoreKey ที่ควรคงอยู่โดยใช้ PersistToDataStore interface:

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

  1. เชื่อมโยง StateStore กับ DataStore: เรียกใช้ syncToDataStore 1 ครั้งเมื่อตั้งค่าอินสแตนซ์ StateStore ที่ใช้ร่วมกัน (เช่น ในคลาส Application หรือโมดูลการแทรกทรัพยากร Dependency) เนื่องจาก 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 ทุกวินาทีหรือทุกเฟรม และคำนวณเวลาที่ผ่านไปหรือเวลาที่เหลืออยู่แบบเรียลไทม์ใน UI
  • ลดการป้อนข้อมูลของผู้ใช้อย่างรวดเร็วก่อนอัปเดตคีย์ที่เก็บไว้: สำหรับข้อความร่าง คำค้นหา หรือแถบเลื่อน ให้เก็บข้อมูลที่ป้อนทันทีไว้ใน MutableState ในหน่วยความจำ (หรือ StateStoreKey ที่ไม่ได้ใช้ PersistToDataStore) และเรียกใช้ setState ใน StateStoreKey ที่เก็บไว้หลังจากที่ข้อมูลที่ป้อนคงที่แล้วเท่านั้น
  • ทำให้คีย์ที่คงอยู่มีขนาดเล็ก: คีย์ PersistToDataStore ทั้งหมดใน StateStore จะได้รับการจัดเรียงเป็นอนุกรมร่วมกันเป็นไฟล์เดียวในการอัปเดตแต่ละครั้ง คงสถานะเซสชันแบบกะทัดรัดไว้ เช่น รหัสที่ใช้งานอยู่ การเลือกตัวกรอง หรือแบบร่างแบบฟอร์มที่อยู่ระหว่างดำเนินการ และเก็บคอลเล็กชันขนาดใหญ่หรือชุดข้อมูลที่ค้นหาได้ไว้ใน Room