ViewModel 的已儲存狀態模組 Android Jetpack 的一部分。

如「儲存 UI 狀態」一文所述,ViewModel 物件可以處理設定變更,因此您不必擔心旋轉或其他情況下的狀態。但如果您需要處理系統啟動的程序 建議使用 SavedStateHandle API 做為備份。

UI 狀態通常是在 ViewModel 物件中儲存或參照,而不是 因此,使用 onSaveInstanceState()rememberSaveable 時,需要一些 已儲存狀態模組的樣板 自動化處理功能

使用這個模組時,ViewModel 物件會透過其建構函式接收 SavedStateHandle 物件。這個物件是鍵/值對應,可讓你寫入及擷取儲存狀態的物件。這些值會在系統終止程序後保留,並且仍可透過相同的物件存取。

已儲存狀態會與工作堆疊建立關聯。如果工作堆疊消失,表示已儲存 狀態也會消失如果強制停止應用程式,移除應用程式的 或重新啟動裝置。在這種情況下 堆疊消失,而且無法還原處於儲存狀態的資訊。於 關閉使用者啟動的 UI 狀態 無法還原已儲存的狀態於 系統啟動的 情境為何?

設定

Fragment 1.2.0 開始 或其轉換依附元件 Activity 1.1.0 可以接受變更 SavedStateHandle,做為 ViewModel 的建構函式引數。

Kotlin

class SavedStateViewModel(private val state: SavedStateHandle) : ViewModel() { ... }

Java

public class SavedStateViewModel extends ViewModel {
    private SavedStateHandle state;

    public SavedStateViewModel(SavedStateHandle savedStateHandle) {
        state = savedStateHandle;
    }

    ...
}

接下來,無需額外設定,你就能擷取 ViewModel 的執行個體。預設的 ViewModel 工廠會為你的 ViewModel 提供適當的 SavedStateHandle

Kotlin

class MainFragment : Fragment() {
    val vm: SavedStateViewModel by viewModels()

    ...
}

Java

class MainFragment extends Fragment {
    private SavedStateViewModel vm;

    public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
        vm = new ViewModelProvider(this).get(SavedStateViewModel.class);

        ...


    }

    ...
}

提供自訂欄位時 ViewModelProvider.Factory敬上 例如,您可以擴充SavedStateHandle AbstractSavedStateViewModelFactory

使用 SavedStateHandle

SavedStateHandle 類別是鍵/值對應,可讓你透過 set()get() 方法,將資料寫入已儲存狀態並從中擷取資料。

使用 SavedStateHandle 時,查詢值會在整個程序終止期間保留,確保使用者在重新建立前後都可看到同一組經過篩選的資料,完全不需要任何活動或片段來手動儲存、還原,然後將該值傳回 ViewModel

透過鍵/值對應進行互動時,SavedStateHandle 還提供其他方法:

此外,您也可以使用SavedStateHandle 可觀測的資料容器。支援的類型清單如下:

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

LiveData

SavedStateHandle 擷取已包裝在 LiveData 可藉由 getLiveData()。 更新鍵值時,LiveData 會接收新的值。這個值通常是因使用者互動而設定的值,例如輸入查詢以篩選資料清單。接著,這個更新的值就能用於 轉換 LiveData

Kotlin

class SavedStateViewModel(private val savedStateHandle: SavedStateHandle) : ViewModel() {
    val filteredData: LiveData<List<String>> =
        savedStateHandle.getLiveData<String>("query").switchMap { query ->
        repository.getFilteredData(query)
    }

    fun setQuery(query: String) {
        savedStateHandle["query"] = query
    }
}

Java

public class SavedStateViewModel extends ViewModel {
    private SavedStateHandle savedStateHandle;
    public LiveData<List<String>> filteredData;
    public SavedStateViewModel(SavedStateHandle savedStateHandle) {
        this.savedStateHandle = savedStateHandle;
        LiveData<String> queryLiveData = savedStateHandle.getLiveData("query");
        filteredData = Transformations.switchMap(queryLiveData, query -> {
            return repository.getFilteredData(query);
        });
    }

    public void setQuery(String query) {
        savedStateHandle.set("query", query);
    }
}

StateFlow

SavedStateHandle 擷取已包裝在 StateFlow 可藉由 getStateFlow()。 更新鍵值時,StateFlow 會接收新的值。大多數 且您可以根據使用者互動 (例如輸入 查詢以篩選資料清單。接下來,您就可以轉換這個更新的值。 使用其他資料流運算子

Kotlin

class SavedStateViewModel(private val savedStateHandle: SavedStateHandle) : ViewModel() {
    val filteredData: StateFlow<List<String>> =
        savedStateHandle.getStateFlow<String>("query")
            .flatMapLatest { query ->
                repository.getFilteredData(query)
            }

    fun setQuery(query: String) {
        savedStateHandle["query"] = query
    }
}

Compose 的狀態支援功能 (實驗性質)

lifecycle-viewmodel-compose 構件提供實驗功能 saveable 可在 SavedStateHandle 和 Compose 之間互通的 API Saver,這樣您的所有State 可以透過 rememberSaveable 儲存 自訂 Saver 也可透過 SavedStateHandle 儲存。

Kotlin

class SavedStateViewModel(private val savedStateHandle: SavedStateHandle) : ViewModel() {

    var filteredData: List<String> by savedStateHandle.saveable {
        mutableStateOf(emptyList())
    }

    fun setQuery(query: String) {
        withMutableSnapshot {
            filteredData += query
        }
    }
}

支援的類型

存放於 SavedStateHandle 的資料會儲存並還原成 Bundle,和適用於活動或片段的其他 savedInstanceState 一起。

直接支援的類型

根據預設,針對與 Bundle 相同的資料類型,你可以在 SavedStateHandle 呼叫 set()get(),如下所示:

類型/類別支援 陣列支援
double double[]
int int[]
long long[]
String String[]
byte byte[]
char char[]
CharSequence CharSequence[]
float float[]
Parcelable Parcelable[]
Serializable Serializable[]
short short[]
SparseArray
Binder
Bundle
ArrayList
Size (only in API 21+)
SizeF (only in API 21+)

如果類別無法擴充上述清單中的項目,建議您新增 @Parcelize Kotlin 註解,使其成為 Parcelable 類別,或直接實作 Parcelable

儲存非 parcelable 類別

如果類別尚未實作 ParcelableSerializable,而且無法修改並實作其中一個介面,則無法直接將該類別的執行個體儲存至 SavedStateHandle

Lifecycle 2.3.0-alpha03 開始,SavedStateHandle 可讓您使用 setSavedStateProvider() 方法,以便提供自己的邏輯,將物件儲存及還原為 BundleSavedStateRegistry.SavedStateProvider 介面可定義單一 saveState() 方法,以傳回包含所要儲存狀態的 Bundle。當 SavedStateHandle 準備好儲存狀態時,會呼叫 saveState()SavedStateProvider 擷取 Bundle,並儲存相關鍵的 Bundle

舉例來說,假設應用程式透過 ACTION_IMAGE_CAPTURE 意圖要求相機應用程式提供圖片,並傳入暫存檔案讓相機儲存圖片。TempFileViewModel 會封裝暫存檔案的建立邏輯。

Kotlin

class TempFileViewModel : ViewModel() {
    private var tempFile: File? = null

    fun createOrGetTempFile(): File {
        return tempFile ?: File.createTempFile("temp", null).also {
            tempFile = it
        }
    }
}

Java

class TempFileViewModel extends ViewModel {
    private File tempFile = null;

    public TempFileViewModel() {
    }


    @NonNull
    public File createOrGetTempFile() {
        if (tempFile == null) {
            tempFile = File.createTempFile("temp", null);
        }
        return tempFile;
    }
}

為避免暫存檔案在活動程序終止時遺失並方便在稍後還原,TempFileViewModel 可以使用 SavedStateHandle 保留其資料。如要允許「TempFileViewModel」儲存資料,請實作 SavedStateProvider,並在以下位置的 SavedStateHandle 設為提供者: ViewModel

Kotlin

private fun File.saveTempFile() = bundleOf("path", absolutePath)

class TempFileViewModel(savedStateHandle: SavedStateHandle) : ViewModel() {
    private var tempFile: File? = null
    init {
        savedStateHandle.setSavedStateProvider("temp_file") { // saveState()
            if (tempFile != null) {
                tempFile.saveTempFile()
            } else {
                Bundle()
            }
        }
    }

    fun createOrGetTempFile(): File {
        return tempFile ?: File.createTempFile("temp", null).also {
            tempFile = it
        }
    }
}

Java

class TempFileViewModel extends ViewModel {
    private File tempFile = null;

    public TempFileViewModel(SavedStateHandle savedStateHandle) {
        savedStateHandle.setSavedStateProvider("temp_file",
            new TempFileSavedStateProvider());
    }
    @NonNull
    public File createOrGetTempFile() {
        if (tempFile == null) {
            tempFile = File.createTempFile("temp", null);
        }
        return tempFile;
    }

    private class TempFileSavedStateProvider implements SavedStateRegistry.SavedStateProvider {
        @NonNull
        @Override
        public Bundle saveState() {
            Bundle bundle = new Bundle();
            if (tempFile != null) {
                bundle.putString("path", tempFile.getAbsolutePath());
            }
            return bundle;
        }
    }
}

如要在使用者回訪時還原 File 資料,請擷取 temp_fileSavedStateHandle」設為 Bundle。這與包含絕對路徑的 saveTempFile() 所提供的 Bundle 相同。因此,絕對路徑就可用來建立新的 File 執行個體。

Kotlin

private fun File.saveTempFile() = bundleOf("path", absolutePath)

private fun Bundle.restoreTempFile() = if (containsKey("path")) {
    File(getString("path"))
} else {
    null
}

class TempFileViewModel(savedStateHandle: SavedStateHandle) : ViewModel() {
    private var tempFile: File? = null
    init {
        val tempFileBundle = savedStateHandle.get<Bundle>("temp_file")
        if (tempFileBundle != null) {
            tempFile = tempFileBundle.restoreTempFile()
        }
        savedStateHandle.setSavedStateProvider("temp_file") { // saveState()
            if (tempFile != null) {
                tempFile.saveTempFile()
            } else {
                Bundle()
            }
        }
    }

    fun createOrGetTempFile(): File {
      return tempFile ?: File.createTempFile("temp", null).also {
          tempFile = it
      }
    }
}

Java

class TempFileViewModel extends ViewModel {
    private File tempFile = null;

    public TempFileViewModel(SavedStateHandle savedStateHandle) {
        Bundle tempFileBundle = savedStateHandle.get("temp_file");
        if (tempFileBundle != null) {
            tempFile = TempFileSavedStateProvider.restoreTempFile(tempFileBundle);
        }
        savedStateHandle.setSavedStateProvider("temp_file", new TempFileSavedStateProvider());
    }

    @NonNull
    public File createOrGetTempFile() {
        if (tempFile == null) {
            tempFile = File.createTempFile("temp", null);
        }
        return tempFile;
    }

    private class TempFileSavedStateProvider implements SavedStateRegistry.SavedStateProvider {
        @NonNull
        @Override
        public Bundle saveState() {
            Bundle bundle = new Bundle();
            if (tempFile != null) {
                bundle.putString("path", tempFile.getAbsolutePath());
            }
            return bundle;
        }

        @Nullable
        private static File restoreTempFile(Bundle bundle) {
            if (bundle.containsKey("path") {
                return File(bundle.getString("path"));
            }
            return null;
        }
    }
}

測試中的 SavedStateHandle

如要測試以 SavedStateHandle 做為依附元件的 ViewModel,請建立 包含 SavedStateHandle 的新例項,並傳遞其所需的測試值 然後將其新增至您正在測試的 ViewModel 執行個體。

Kotlin

class MyViewModelTest {

    private lateinit var viewModel: MyViewModel

    @Before
    fun setup() {
        val savedState = SavedStateHandle(mapOf("someIdArg" to testId))
        viewModel = MyViewModel(savedState = savedState)
    }
}

其他資源

如要進一步瞭解 ViewModel 的「已儲存狀態」模組,請參閱 資源。

程式碼研究室