Zapisywanie stanu nawigacji i zarządzanie nim

W kolejnych sekcjach opisujemy strategie zapisywania listy wstecznej i przechowywania stanu powiązanego z elementami na tej liście.

Zapisywanie listy wstecznej

Zapewnienie, że stan nawigacji aplikacji jest zachowywany w różnych zdarzeniach cyklu życia, w tym w przypadku zmian konfiguracji i śmierci procesu, ma kluczowe znaczenie dla wygody użytkowników. W Navigation 3 masz pełną kontrolę nad stosem wstecznym, więc nie ma ścisłych wytycznych dotyczących jego tworzenia ani zapisywania. Nawigacja 3 udostępnia jednak wygodną metodę, która zapewnia możliwość zapisania stosu wstecznego: rememberNavBackStack.

Używaj klawisza rememberNavBackStack

Funkcja typu „composable” rememberNavBackStack służy do tworzenia stosu wstecznego, który zachowuje trwałość w przypadku zmian konfiguracji i śmierci procesu.

Aby funkcja rememberNavBackStack działała prawidłowo, każdy klucz w stosie wstecznym musi spełniać określone wymagania:

  • Zaimplementuj interfejs NavKey: każdy klucz na stosie wstecznym musi implementować interfejs NavKey. Jest to interfejs znacznika, który sygnalizuje bibliotece, że klucz można zapisać.
  • Mieć adnotację @Serializable: oprócz wdrożenia NavKey kluczowe klasy i obiekty muszą być oznaczone adnotacją @Serializable.

Poniższy fragment kodu pokazuje prawidłową implementację elementu rememberNavBackStack:

@Serializable
data object Home : NavKey

@Composable
fun NavBackStack() {
    val backStack = rememberNavBackStack(Home)
}

Zapamiętaj stos wsteczny z podtypami NavKey

Funkcja typu „composable” rememberNavBackStack zwraca NavBackStack<NavKey>. Jeśli aplikacja definiuje własny podtyp NavKey, z którego dziedziczą wszystkie jej klucze, możesz zachować to typowanie, implementując niestandardową funkcję remember w ten sposób:

@Serializable
sealed interface MyAppNavKey : NavKey

@Serializable
data object ScreenA: MyAppNavKey

@Serializable
data class ScreenB(val id: String): MyAppNavKey

@Composable
fun rememberMyAppNavBackStack(vararg elements: MyAppNavKey): NavBackStack<MyAppNavKey> {
    return rememberSerializable(serializer = serializer()) {
        NavBackStack(*elements)
    }
}

@Composable
fun MyApp() {
    // defaultNavBackStack is NavBackStack<NavKey>
    val defaultNavBackStack = rememberNavBackStack(ScreenA)
    // myAppNavBackStack is NavBackStack<MyAppNavKey>
    val myAppNavBackStack = rememberMyAppNavBackStack(ScreenA)
}

Więcej przykładów, w tym informacji o tym, jak obsługiwać otwarty polimorfizm, znajdziesz w NavBackStackSamples.

Alternatywa: przechowywanie w ViewModel

Innym sposobem zarządzania stosem wstecznym jest przechowywanie go w ViewModel. Aby zapewnić trwałość po śmierci procesu podczas korzystania z atrybutu ViewModel lub dowolnego innego niestandardowego miejsca na dane, musisz:

  • Upewnij się, że klucze można serializować: podobnie jak w przypadku rememberNavBackStack, klucze nawigacyjne muszą być serializowalne.
  • Ręczne obsługiwanie serializacji i deserializacji: musisz ręcznie zapisywać w pamięci trwałej (np. SharedPreferences, w bazie danych lub w pliku) zserializowaną reprezentację każdego klucza i deserializować ją z tej pamięci, gdy aplikacja przechodzi w tło lub jest przywracana.

Zakres od ViewModel s do NavEntry s

ViewModels służą do zachowywania stanu interfejsu w przypadku zmian konfiguracji, takich jak obracanie ekranu. Domyślnie ViewModels są ograniczone do najbliższego ViewModelStoreOwner, którym jest zwykle Activity lub Fragment.

Możesz jednak ograniczyć zakres ViewModel do konkretnego NavEntry (czyli konkretnego ekranu lub miejsca docelowego) na stosie wstecznym, a nie do całego Activity. Dzięki temu stan elementu ViewModel jest zachowywany tylko wtedy, gdy dany element NavEntry jest częścią stosu wstecznego, i jest czyszczony, gdy element NavEntry zostanie usunięty ze stosu.

Biblioteka dodatku androidx.lifecycle:lifecycle-viewmodel-navigation3 udostępnia NavEntryDecorator, który to ułatwia. Ten dekorator udostępnia ViewModelStoreOwner dla każdego NavEntry. Gdy utworzysz ViewModel w treści NavEntry (np. za pomocą viewModel() w funkcji Compose), zostanie ono automatycznie ograniczone do klucza tego konkretnego elementu NavEntry na stosie wstecznym. Oznacza to, że element ViewModel jest tworzony, gdy element NavEntry jest dodawany do stosu wstecznego, i usuwany, gdy jest z niego usuwany.

Aby użyć NavEntryDecorator do określania zakresu ViewModelNavEntry, wykonaj te czynności:

  1. Dodaj zależność androidx.lifecycle:lifecycle-viewmodel-navigation3 do pliku app/build.gradle.kts.
  2. Dodaj domyślny adres rememberSaveableStateHolderNavEntryDecorator() do listy entryDecorators podczas tworzenia NavDisplay.
  3. Dodaj rememberViewModelStoreNavEntryDecorator() do listy entryDecorators.

NavDisplay(
    entryDecorators = listOf(
        // Add the default decorators for managing scenes and saving state
        rememberSaveableStateHolderNavEntryDecorator(),
        // Then add the view model store decorator
        rememberViewModelStoreNavEntryDecorator()
    ),
    backStack = backStack,
    entryProvider = entryProvider { },
)