在 Compose 中儲存 UI 狀態

視狀態提升至的位置和所需的邏輯而定 可以使用不同的 API 來儲存及還原 UI 狀態。每個應用程式都會使用 以達到這個目的。

任何 Android 應用程式都可能會因為活動或程序而遺失 UI 狀態 重新建立。以下是可能引發這類情況的事件:

,瞭解如何調查及移除這項存取權。

對正面使用者來說,在事件發生後保留狀態至關重要 無須專人管理而您該選擇保留哪些狀態,則視應用程式的不重複使用者人數而定 流程最佳做法是至少保存使用者輸入內容 導覽相關狀態。舉例來說, 清單、使用者想進一步瞭解的項目 ID、進行中 使用者偏好設定選項,或在文字欄位中輸入的內容

本頁總結了可用 API 儲存 UI 狀態的 API,具體情況取決於 狀態就會提升至需要狀態的邏輯

UI 邏輯

如果狀態是在 UI 中提升 (無論是在可組合函式中或純狀態), 範圍限定為組合的狀態容器類別 rememberSaveable 用於在重新建立活動和程序後保留狀態。

在以下程式碼片段中,rememberSaveable 的作用是儲存單一布林值 UI 元素狀態:

@Composable
fun ChatBubble(
    message: Message
) {
    var showDetails by rememberSaveable { mutableStateOf(false) }

    ClickableText(
        text = AnnotatedString(message.content),
        onClick = { showDetails = !showDetails }
    )

    if (showDetails) {
        Text(message.timestamp)
    }
}

圖 1.依輕觸操作展開與收合的即時通訊訊息泡泡。

showDetails 是一個布林值變數,可在即時通訊泡泡處於收合狀態時儲存 或已展開的情況

rememberSaveable 會透過 BundleUI 元素狀態儲存在 Bundle 中 儲存的執行個體狀態機制

基本類型可以自動儲存在套件中。如果您所在的州/省 以非原始的類型 (例如資料類別) 保留 不同的儲存機制,例如使用 Parcelize 註解。 使用 listSavermapSaver 等 Compose API,或實作 自訂 Saver 類別,擴充 Compose 執行階段 Saver 類別。請參閱方式 儲存狀態說明文件,進一步瞭解這些方法。

在以下程式碼片段中,rememberLazyListState Compose API 會儲存 LazyListState,其中包含 LazyColumnLazyRow,使用 rememberSaveable。該公式採用 LazyListState.Saver 是自訂儲存工具,可以 儲存並還原捲動狀態重新建立活動或程序後 ( 例如,在設定變更 (例如變更裝置螢幕方向) 後, 因此會保留捲動狀態

@Composable
fun rememberLazyListState(
    initialFirstVisibleItemIndex: Int = 0,
    initialFirstVisibleItemScrollOffset: Int = 0
): LazyListState {
    return rememberSaveable(saver = LazyListState.Saver) {
        LazyListState(
            initialFirstVisibleItemIndex, initialFirstVisibleItemScrollOffset
        )
    }
}

最佳做法

rememberSaveable 使用 Bundle 儲存 UI 狀態,由該狀態共用 其他也會寫入其資料的 API,例如onSaveInstanceState() 您的活動。不過,這個 Bundle 的大小有限,且會儲存大型 物件可能會在執行階段中發生 TransactionTooLarge 例外狀況。這個 這在單個 Activity 應用程式中可能特別有問題 目前已在應用程式中使用 Bundle

為了避免這種異常終止情形,請勿儲存大型物件或 組合中的物件清單

請改為儲存所需的最低狀態 (例如 ID 或鍵),並用於 委派將還原更複雜的 UI 狀態給其他機制,例如永久性機制 如果 30 天內讀取資料不到一次 建議使用 Nearline Storage

請根據應用程式的用途,選擇適合的選項 使用者期望應用程式能正常運作

驗證狀態還原

您可以使用 rememberSaveable,確認儲存狀態是否位於 當活動或程序發生時,Compose 元素會正確還原 重新建立有些 API 就是為此設計 StateRestorationTester。請參閱測試說明文件, 瞭解詳情。

商業邏輯

如果 UI 元素狀態ViewModel 為了讓商業邏輯所需,您可以使用 ViewModel 的 API。

在 Android 應用程式中使用 ViewModel 的主要優點之一,就是 免費處理設定變更如果有設定 系統就會刪除並重新建立活動,UI 狀態也提升為 ViewModel 會保存在記憶體中。重新建立完畢後,舊的 ViewModel 附加至新的活動例項。

不過,ViewModel 例項無法在系統終止程序後繼續存留。 如果想保留 UI 狀態,請使用 [儲存狀態] 模組 ViewModel,內含 SavedStateHandle API。

最佳做法

SavedStateHandle 也會使用 Bundle 機制儲存 UI 狀態,因此 應該只用來儲存簡單的 UI 元素狀態

畫面 UI 狀態,此狀態是套用業務規則並存取 應用程式層中,請勿使用 UI 以外的其他層 SavedStateHandle,因為可能相當複雜且大小不容小覷。別擔心!您可以使用 不同機制 (例如儲存複雜或大型資料) 如果 30 天內讀取資料不到一次 建議使用 Nearline Storage重新建立程序後,系統會使用 還原的暫時性狀態,儲存在 SavedStateHandle 中 (如有),以及 系統會再次從資料層產生畫面 UI 狀態。

SavedStateHandle API

SavedStateHandle 有各種用來儲存 UI 元素狀態的 API,大部分 值得注意的是:

撰寫 State saveable()
StateFlow getStateFlow()

撰寫 State

使用 SavedStateHandlesaveable API 讀取及寫入 UI 元素 狀態變為 MutableState,因此在活動和程序重建後仍然有效 僅需少量程式碼設定

saveable API 可直接支援基本類型,並會接收 stateSaver 參數以使用自訂 Saver,就像 rememberSaveable() 一樣。

在以下程式碼片段中,message 會將使用者輸入類型儲存至 TextField:

class ConversationViewModel(
    savedStateHandle: SavedStateHandle
) : ViewModel() {

    var message by savedStateHandle.saveable(stateSaver = TextFieldValue.Saver) {
        mutableStateOf(TextFieldValue(""))
    }
        private set

    fun update(newMessage: TextFieldValue) {
        message = newMessage
    }

    /*...*/
}

val viewModel = ConversationViewModel(SavedStateHandle())

@Composable
fun UserInput(/*...*/) {
    TextField(
        value = viewModel.message,
        onValueChange = { viewModel.update(it) }
    )
}

詳情請參閱 SavedStateHandle 說明文件 透過 saveable API 部署該物件

StateFlow

使用 getStateFlow() 儲存 UI 元素狀態,並將其做為資料流使用 SavedStateHandle。已讀取 StateFlow ,且 API 會要求您指定金鑰,這樣您就能將流程 發出新值。設定金鑰後,您就能擷取 StateFlow 並收集最新的值

在以下程式碼片段中,savedFilterTypeStateFlow 變數, 儲存套用至即時通訊頻道清單的篩選器類型:

private const val CHANNEL_FILTER_SAVED_STATE_KEY = "ChannelFilterKey"

class ChannelViewModel(
    channelsRepository: ChannelsRepository,
    private val savedStateHandle: SavedStateHandle
) : ViewModel() {

    private val savedFilterType: StateFlow<ChannelsFilterType> = savedStateHandle.getStateFlow(
        key = CHANNEL_FILTER_SAVED_STATE_KEY, initialValue = ChannelsFilterType.ALL_CHANNELS
    )

    private val filteredChannels: Flow<List<Channel>> =
        combine(channelsRepository.getAll(), savedFilterType) { channels, type ->
            filter(channels, type)
        }.onStart { emit(emptyList()) }

    fun setFiltering(requestType: ChannelsFilterType) {
        savedStateHandle[CHANNEL_FILTER_SAVED_STATE_KEY] = requestType
    }

    /*...*/
}

enum class ChannelsFilterType {
    ALL_CHANNELS, RECENT_CHANNELS, ARCHIVED_CHANNELS
}

每當使用者選取新的篩選器類型,就會呼叫 setFiltering。這個 在 SavedStateHandle 中使用鍵儲存新值 _CHANNEL_FILTER_SAVED_STATE_KEY_savedFilterType 是會發出 最新的值。filteredChannels 已訂閱以下資料流: 執行頻道篩選條件

請參閱SavedStateHandle說明文件,進一步瞭解 getStateFlow() API。

摘要

下表歸納了本節介紹的 API 及使用時機 逐一儲存 UI 狀態:

活動 UI 邏輯 ViewModel 中的商業邏輯
設定變更 rememberSaveable 自動
系統終止程序 rememberSaveable SavedStateHandle

要使用哪一個 API,取決於狀態儲存的位置和其邏輯 而負責任的 AI 技術做法 有助於達成這項目標對於 UI 邏輯中使用的狀態,請使用 rememberSaveable;適用對象 商業邏輯中使用的狀態 (如果您的保存在 ViewModel 中)。 使用 SavedStateHandle 儲存。

您應使用 bundle API (rememberSaveableSavedStateHandle) 執行 儲存少量 UI 狀態這項資料是還原所需最低限度的資料 UI 和其他儲存機制適用對象 舉例來說,如果您將使用者先前查看的設定檔 ID 儲存在套件中, 您可以從資料層擷取商家檔案詳情等大量資料。

如要進一步瞭解各種儲存 UI 狀態的方式,請參閱一般 儲存 UI 狀態說明文件 架構指南