Salvar e gerenciar o estado de navegação

As seções a seguir descrevem estratégias para salvar a backstack e armazenar o estado associado às entradas nela.

Salvar a backstack

Garantir que o estado de navegação do app persista em vários eventos do ciclo de vida, incluindo mudanças de configuração e encerramento do processo, é fundamental para uma boa experiência do usuário. Na navegação 3, você é o proprietário da backstack. Portanto, não há diretrizes rigorosas sobre como criar ou salvar. No entanto, a navegação 3 oferece um método de conveniência que fornece uma backstack que pode ser salva: rememberNavBackStack.

Usar rememberNavBackStack

A função combinável rememberNavBackStack foi projetada para criar uma backstack que persista em mudanças de configuração e encerramento do processo.

Para que rememberNavBackStack funcione corretamente, cada chave na backstack precisa obedecer a requisitos específicos:

  • Implementar a interface NavKey: cada chave na backstack precisa implementar a interface NavKey. Ela funciona como uma interface de marcador que sinaliza para a biblioteca que a chave pode ser salva.
  • Ter a anotação @Serializable: além de implementar NavKey, as classes e os objetos de chave precisam ser marcados com a anotação @Serializable.

O snippet a seguir mostra uma implementação correta de rememberNavBackStack:

@Serializable
data object Home : NavKey

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

Lembrar uma backstack com subtipos de NavKey

A função combinável rememberNavBackStack retorna um NavBackStack<NavKey>. Se o app definir o próprio subtipo de NavKey do qual todas as chaves herdam, você poderá preservar essa digitação implementando uma função de memorização personalizada, da seguinte maneira:

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

Para mais exemplos, incluindo como processar o polimorfismo aberto, consulte NavBackStackSamples.

Alternativa: armazenar em um ViewModel

Outra abordagem para gerenciar a backstack é armazená-la em um ViewModel. Para persistência durante o encerramento do processo ao usar um ViewModel ou qualquer outro armazenamento personalizado, você precisa:

  • Garantir que as chaves sejam serializáveis: assim como com rememberNavBackStack, as chaves de navegação precisam ser serializáveis.
  • Processar a serialização e a desserialização manualmente: você é responsável por salvar manualmente a representação serializada de cada chave e desserializá-la do armazenamento persistente (por exemplo, SharedPreferences, um banco de dados ou um arquivo) quando o app está sendo colocado em segundo plano ou sendo restaurado.

Definir o escopo de ViewModels para NavEntrys

Os ViewModels são usados para manter o estado relacionado à interface em mudanças de configuração, como rotações de tela. Por padrão, os ViewModels são definidos como o ViewModelStoreOwner mais próximo, que normalmente é sua Activity ou Fragment.

No entanto, talvez você queira definir o escopo de um ViewModel para uma NavEntry específica (ou seja, uma tela ou destino específico) na backstack, em vez de toda a Activity. Isso garante que o estado do ViewModel seja mantido apenas enquanto essa NavEntry específica fizer parte da backstack e seja limpo quando a NavEntry for removida.

A biblioteca complementar androidx.lifecycle:lifecycle-viewmodel-navigation3 fornece um NavEntryDecorator que facilita isso. Esse decorador fornece um ViewModelStoreOwner para cada NavEntry. Quando você cria um ViewModel dentro do conteúdo de uma NavEntry (por exemplo, usando viewModel() no Compose), ele é automaticamente definido como a chave dessa NavEntry específica na backstack. Isso significa que o ViewModel é criado quando a NavEntry é adicionada à backstack e limpa quando é removida.

Para usar NavEntryDecorator para definir o escopo de ViewModels para NavEntrys, siga estas etapas:

  1. Adicione a dependência androidx.lifecycle:lifecycle-viewmodel-navigation3 ao arquivo app/build.gradle.kts.
  2. Adicione o padrão rememberSaveableStateHolderNavEntryDecorator() à lista de entryDecorators ao construir um NavDisplay.
  3. Adicione rememberViewModelStoreNavEntryDecorator() à lista de 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 { },
)