Moduł Zapisano stan dla ViewModel Zawiera Android Jetpack.

Jak wspomniano w: Zapisuję stany interfejsu, Obiekty ViewModel, które mogą obsługiwać zmian konfiguracji, więc nie trzeba martwić się o stan w rotacjach. ani w innych przypadkach. Jeśli jednak musisz obsłużyć proces zainicjowany przez system śmiało, może warto użyć interfejsu API SavedStateHandle jako aplikacji zapasowej.

Stan interfejsu jest zwykle przechowywany lub odwołuje się do obiektów ViewModel, ale nie aktywności, dlatego korzystanie z onSaveInstanceState() lub rememberSaveable wymaga widać, że moduł zapisywania stanu które zrobią za Ciebie.

Podczas korzystania z tego modułu obiekty ViewModel otrzymują odpowiedź SavedStateHandle obiekt za pomocą jego konstruktora. Ten obiekt to mapa klucz-wartość, która umożliwia do zapisywania i pobierania obiektów w stanie i z powrotem. Wartości te utrzymują się po zatrzymaniu procesu przez system i pozostają dostępne przez ten sam obiekt.

Zapisany stan jest powiązany ze stosem zadań. Jeśli stos zadań zniknie, zapisane również nie jest brany pod uwagę. Może się tak zdarzyć, gdy wymuszane jest zatrzymanie aplikacji, co powoduje usunięcie wybierając aplikację z menu ostatnich lub zrestartuj urządzenie. W takich przypadkach zadanie te dane znikną i nie będzie można przywrócić zapisanych informacji. W Odrzucenie stanu interfejsu inicjowanego przez użytkownika zapisane scenariusze nie zostaną przywrócone. W zainicjowane przez system a w przypadku niektórych scenariuszy.

Konfiguracja

Rozpoczyna się od Fragment 1.2.0 lub jej przejściowej zależności Aktywności 1.1.0, możesz zaakceptować SavedStateHandle jako argumentu konstruktora w ViewModel.

Kotlin

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

Java

public class SavedStateViewModel extends ViewModel {
    private SavedStateHandle state;

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

    ...
}

Następnie możesz pobrać instancję ViewModel bez dodatkowych konfiguracji. Domyślna fabryka elementu ViewModel zapewnia odpowiednie SavedStateHandle na urządzenie ViewModel.

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);

        ...


    }

    ...
}

W przypadku parametru niestandardowego ViewModelProvider.Factory możesz włączyć korzystanie z usługi SavedStateHandle, rozszerzając AbstractSavedStateViewModelFactory

Praca z SavedStateHandle

Klasa SavedStateHandle to mapa klucz-wartość, która umożliwia zapisywanie pobierania danych do i z zapisanego stanu za pomocą funkcji set(). i get() .

Gdy metoda SavedStateHandle powoduje, że wartość zapytania jest zachowywana po zakończeniu procesu, Zapewnia, że użytkownik widzi ten sam zestaw filtrowanych danych przed i po. odtwarzania bez konieczności ręcznego zapisywania, przywracania i przekazać ją z powrotem do funkcji ViewModel.

SavedStateHandle udostępnia też inne metody interakcji, których możesz się spodziewać z mapą par klucz-wartość:

Możesz też pobierać wartości z SavedStateHandle za pomocą obserwowalnego właściciela danych. Lista obsługiwanych typów to:

.

Dane aktywne

Pobieranie wartości z zakresu SavedStateHandle, które są zawarte w pliku LiveData dostrzegalne przy użyciu getLiveData() Po zaktualizowaniu wartości klucza LiveData otrzymuje nową wartość. Większość często wartość jest ustawiana w zależności od interakcji użytkownika, takich jak wpisanie zapytania, filtrować listę danych. Zaktualizowanej wartości można później użyć do: przekształcić element 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

Pobieranie wartości z zakresu SavedStateHandle, które są zawarte w pliku StateFlow dostrzegalne przy użyciu getStateFlow() Po zaktualizowaniu wartości klucza StateFlow otrzyma nową wartość. Większość często możesz określić wartość w zależności od interakcji użytkownika, takich jak wpisanie aby przefiltrować listę danych. Następnie możesz przekształcić tę zaktualizowaną wartość. przy użyciu innych operatorów przepływu.

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
    }
}

Obsługa stanu eksperymentalnego tworzenia wiadomości

Artefakt lifecycle-viewmodel-compose udostępnia funkcję eksperymentalną saveable. Interfejsy API umożliwiające współdziałanie SavedStateHandle i Compose Saver, dzięki którym każdy State można zapisać przez rememberSaveable z niestandardową wartością Saver można też zapisać w 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
        }
    }
}

Obsługiwane typy

Dane przechowywane w usłudze SavedStateHandle są zapisywane i przywracane jako Bundle, wraz z pozostałymi savedInstanceState dla danej aktywności lub fragmentu.

Typy obsługiwane bezpośrednio

Domyślnie możesz dzwonić pod numery set() i get() w SavedStateHandle w celu tych samych typów danych co Bundle, jak pokazano poniżej:

Obsługa typu/klasy Obsługa tablic
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+)

Jeśli klasa nie obejmuje żadnej z powyższych list, rozważ utworzenie klasy, do której można dołączyć, dodając @Parcelize Adnotacja lub implementacja Kotlin bezpośrednio Parcelable.

Zapisywanie klas, których nie można podzielić na pakiet

Jeśli klasa nie zawiera metody Parcelable lub Serializable i nie można jej modyfikacji w celu wdrożenia jednego z tych interfejsów, nie można bezpośrednio zapisać instancję tej klasy w kolekcji SavedStateHandle.

Rozpoczyna się od Cykl życia 2.3.0-alfa03, SavedStateHandle umożliwia zapisanie dowolnego obiektu przez podanie własnych do zapisywania i przywracania obiektu jako Bundle za pomocą setSavedStateProvider() . SavedStateRegistry.SavedStateProvider to interfejs, który określa jeden saveState() zwracającą Bundle zawierającą stan, który chcesz zapisać. Kiedy SavedStateHandle jest gotowy do zapisania stanu; wywołuje polecenie saveState() aby pobrać Bundle z SavedStateProvider i zapisać Bundle dla powiązanego klucza.

Zobacz przykład aplikacji, która wysyła żądanie obrazu z aparatu za pomocą ACTION_IMAGE_CAPTURE przez przesłanie pliku tymczasowego zawierającego miejsce, w którym kamera . TempFileViewModel zawiera logikę utworzenia tymczasowy plik.

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;
    }
}

Aby mieć pewność, że plik tymczasowy nie zostanie utracony po zatrzymaniu procesu aktywności i później ją przywrócona, TempFileViewModel może używać: SavedStateHandle do zachowanie danych. Aby umożliwić aplikacji TempFileViewModel zapisywanie danych, zaimplementuj SavedStateProvider i ustaw ją jako dostawcę w 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;
        }
    }
}

Aby przywrócić dane File, gdy użytkownik powraca, pobierz temp_file Bundle z SavedStateHandle. To ten sam produkt typu Bundle, który udostępnia użytkownik: saveTempFile(), która zawiera ścieżkę bezwzględną. Ścieżka bezwzględna może wtedy mogą zostać użyte do utworzenia nowego wystąpienia 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 w testach

Aby przetestować zależność ViewModel, która wymaga SavedStateHandle, utwórz nowe wystąpienie SavedStateHandle z wymaganymi wartościami testowymi oraz zdanymi go do testowanej instancji ViewModel.

Kotlin

class MyViewModelTest {

    private lateinit var viewModel: MyViewModel

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

Dodatkowe materiały

Więcej informacji o module Zapisane stany (ViewModel) znajdziesz tutaj: poniższe zasoby.

Ćwiczenia z programowania

. .