AndroidX 將引入全新的 AppState,用於管理 Compose 狀態

最近 AndroidX 為「跨元件 Compose 狀態託管場景」引入了 AppState,目前還沒有完全落地,但從程式碼骨架可以看出,AppState 的核心目的是:

把 Compose 的可觀察狀態從某個「Composable/元件生命週期」裡抽出來,然後放到更上層的狀態容器裡管理。

這次新增的是 androidx.appstate 套件,目前對外公開的核心型別有三個:

AppState
AppStateKey<T>
AppStateToken

對應的公開方法包括:

class AppState {
    fun <T> getState(stateKey: AppStateKey<T>, defaultValue: T): State<T>
    fun <T> setState(stateKey: AppStateKey<T>, value: T)
    fun <T> updateState(stateKey: AppStateKey<T>, defaultValue: T, update: (T) -> T)
    fun addAppStateListener(...): AppStateToken
    fun removeAppStateListener(token: AppStateToken)
}

另外提交內容也寫了:Added new library to help support hoisting of Compose state,也就是說 AppState 不是用來「取代 ViewModel」,也不是要「提供完整架構層的狀態管理」,而是 help support hoisting of Compose state

那為什麼需要 AppState?首先我們知道,Compose 原本的狀態模型大概是這樣的流程:

因為 Compose 是宣告式 UI,而 UI 是狀態的表現,所以 MutableState.value 發生改變時,會觸發讀取它的 composable 重新組合。

但問題在於,狀態要放在哪裡:

remember 存在於 Composition 中,而如果呼叫它的 composable 被移除後,狀態就會消失;rememberSaveable 主要是幫助狀態跨重新組合、Activity 重建、系統觸發的程序復原等場景。

所以針對這個場景,AppState 的註解也提到:

Class that contains a store that maintains compose state beyond individual Android components.

There is no limitation on where these instance should be instantiated as that is left up to the developer.

也就是它不是把狀態綁定到某個 Activity、Fragment、NavBackStackEntry,或單一 Composable,而是讓你自己決定 AppState 實例的作用域。 換句話說:

Compose runtime 的 State<T> / MutableState<T> 仍然是底層可觀察狀態模型,但狀態容器不再一定活在某個 Composable 裡,而是可以被提升到更外層。

所以實際上 AppState 就是一個 keyed 的 MutableState store,這次原始碼裡最核心的就是這兩個 map:

private val stateStore: MutableMap<AppStateKey<*>, MutableState<*>> = mutableMapOf()

private val appStateListeners = mutableMapOf<AppStateToken, CoroutineScope>()

第一個 stateStore 是真正存放狀態的地方,第二個 appStateListeners 是為未來的監聽器準備的 token 到 coroutine scope 對應表:

實際上這裡的重要點是:

  • AppState 存的不是普通值,而是 MutableState<*>,也就是說它不是傳統的 Map<Key, Value>,而是一個 Compose runtime 可觀察狀態倉庫

  • 對外的 getState() 回傳的是 State<T>,不是 MutableState<T>

public fun <T> getState(stateKey: AppStateKey<T>, defaultValue: T): State<T> {
    @Suppress("UNCHECKED_CAST")
    return stateStore.getOrPut(stateKey) { mutableStateOf(defaultValue) } as MutableState<T>
}

這代表在 API 設計上偏向:

也就是讀寫分離:讀取時拿 State,寫入時走 AppState 的 set / update API。

getState 是 lazy 建立 StategetState() 的邏輯很簡單:

stateStore.getOrPut(stateKey) { mutableStateOf(defaultValue) }

也就是它不是每次都重新取值,而是取同一個 MutableState 物件;只要 AppState 實例還活著,對應 key 的 state 就還在。

從這裡也能看出它和一般 remember { mutableStateOf() } 的不同:

remember 的生命週期綁定在 Composition 位置上,而 AppState 的生命週期則綁定在 AppState 實例本身。

setState 第一次設定時可能會註冊 auto-clear listener,這其實也是目前這個庫最有意思的地方:

public fun <T> setState(stateKey: AppStateKey<T>, value: T) {
    if (!stateStore.contains(stateKey) && stateKey.autoClearKey != null) {
        addAppStateListener { map ->
            val key = stateKey.autoClearKey
            val currentValue = map[key]?.value
            val initialValue = remember { currentValue }
            if (currentValue != initialValue && stateKey.predicate(this@AppState)) {
                LaunchedEffect(currentValue) {
                    stateStore.remove(stateKey)
                    removeAppStateListener(this@addAppStateListener)
                }
            }
        }
    }
    (getState(stateKey, value) as MutableState<T>).value = value
}

也就是 AppStateKey 不只是一般的 key,它還能定義「什麼時候這個狀態應該被自動清理」。

另外,對應的 updateState 方法看起來很像 reducer 風格:

public fun <T> updateState(
    stateKey: AppStateKey<T>,
    defaultValue: T,
    update: (T) -> T
) {
    val currentState = getState(stateKey, defaultValue)
    setState(stateKey, update(currentState.value))
}

這個 API 設計很像 reducer:

appState.updateState(IntKey, 0) { it + 1 }

就目前程式碼來看,這個庫目前能確定可以做的大概像這樣:

val appState = AppState()

@Serializable
object NameKey : AppStateKey<String>()

val state = appState.getState(NameKey, "default")
// state.value == "default"

appState.setState(NameKey, "Asher")
// 再 getState(NameKey, "default").value == "Asher"

另外這個庫是 Kotlin consumers only,屬於高度 Kotlin / Compose 化,使用了大量 @Composable lambda、泛型 key、Kotlin serialization、object singleton key 等:

AppStateKey<T>
listener: @Composable AppStateToken.(Map<AppStateKey<*>, State<*>>) -> Unit
update: (T) -> T
@Serializable object Key : AppStateKey<String>()

當然,最重要的是,這次修改不只是 Android API,還包含 native bcv

Targets: [iosArm64, iosSimulatorArm64, linuxArm64, linuxX64, macosArm64, mingwX64, tvosArm64, ...]
Library unique name: <androidx.appstate:appstate>

同時原始碼路徑是:

src/commonMain/kotlin/androidx/appstate/AppState.kt
src/commonTest/kotlin/androidx/appstate/AppStateTest.kt

也就是說,這個專案不是只給 Android JVM 寫的,而是 AndroidX Multiplatform 架構下的 commonMain 函式庫,所以這是一個面向 Kotlin Multiplatform 的專案。

我覺得,它不是傳統 SavedStateHandle / ViewModel 的替代品,而更像是 Compose Runtime 層的狀態託管基礎建設

對比 ViewModel 大概是:

  • ViewModel 解決的是 Android 架構中的畫面級狀態與業務邏輯存取
  • 而 AppState 更像是希望某些 MutableState 不要被某個 Composable 或 Android component 的生命週期限制,那就可以用一個外部 AppState 容器來託管

所以 AppState 更像是把 MutableState<T> 本身放進一個統一容器裡;註解也明確寫了 beyond individual Android components,這不是「單一 composable 內保存狀態」,也不是「某個 Activity 內部欄位」,而是更外層的狀態 store。

所以它可能想支援跨元件協作狀態?另外 autoClearKey + predicate + listener + LaunchedEffect 這套設計,看起來更像是當某個關聯狀態發生變化時,自動清掉另一個狀態,感覺可以像這樣:

object CurrentUserKey : AppStateKey<User?>()

object DraftMessageKey : AppStateKey<String>(
    autoClearKey = CurrentUserKey,
    predicate = { appState -> true }
)

比如當使用者變更時,清理某些跟使用者綁定的草稿、快取、臨時 UI 狀態?當然這只是推測。

所以整體看起來,AppState 更像是 AndroidX 正在引入的一個 Compose Runtime 層狀態託管容器

不過專案目前還處於落地階段,更詳細的實作還沒有完整公開,但主方向應該差不多。總的來看,核心就是可以把 Compose 狀態提升到元件之外,解決多個 Composable/多個元件之間共享 Compose State 的問題

鏈接

android-review.googlesource.com/c/platform/…


原文出處:https://juejin.cn/post/7638535912314929206


精選技術文章翻譯,幫助開發者持續吸收新知。

共有 0 則留言


精選技術文章翻譯,幫助開發者持續吸收新知。
🏆 本月排行榜
🥇
站長阿川
📝16   💬11   ❤️1
541
🥈
alicec
📝1   ❤️2
77
🥉
我愛JS
💬2  
7
評分標準:發文×10 + 留言×3 + 獲讚×5 + 點讚×1 + 瀏覽數÷10
本數據每小時更新一次
📢 贊助商廣告 · 我要刊登