Salvar e gerenciar o estado de navegação

As seções a seguir descrevem estratégias para salvar a pilha de retorno e armazenar o estado associado às entradas na pilha de retorno.

Salvar a pilha de retorno

É fundamental 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, para uma boa experiência do usuário. Na Navegação 3, você é o proprietário da pilha de retorno. Portanto, não há diretrizes rígidas sobre como criar ou salvar essa pilha. No entanto, a Navigation 3 oferece um método conveniente que fornece uma backstack salva: rememberNavBackStack.

Usar rememberNavBackStack

A função combinável rememberNavBackStack foi projetada para criar uma pilha de retorno que persiste em mudanças de configuração e interrupção do processo.

Para que o rememberNavBackStack funcione corretamente, cada chave na sua pilha de retorno precisa obedecer a requisitos específicos:

  • Implemente a interface NavKey: cada chave na pilha de retorno precisa implementar a interface NavKey. Isso 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, suas principais classes e objetos 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)
}

Alternativa: armazenar em um ViewModel

Outra abordagem para gerenciar a pilha de retorno é armazená-la em um ViewModel. Para persistir dados em caso de interrupção do processo ao usar um ViewModel ou qualquer outro armazenamento personalizado, é necessário:

  • Verifique se as chaves são serializáveis: assim como com rememberNavBackStack, as chaves de navegação precisam ser serializáveis.
  • Processar serialização e desserialização manualmente: você é responsável por salvar manualmente a representação serializada de cada chave e desserializar do armazenamento permanente (por exemplo, SharedPreferences, um banco de dados ou um arquivo) quando o app está indo para segundo plano ou sendo restaurado.

Definindo o escopo de ViewModels a NavEntrys

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

No entanto, talvez você queira restringir um ViewModel a um NavEntry específico (ou seja, uma tela ou destino específico) na pilha de retorno, em vez de todo o Activity. Isso garante que o estado do ViewModel seja mantido apenas enquanto esse NavEntry específico fizer parte da pilha de retorno e seja limpo quando o NavEntry for removido.

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 um NavEntry (por exemplo, usando viewModel() no Compose), ele é automaticamente definido como escopo para a chave desse NavEntry específico na pilha de retorno. Isso significa que o ViewModel é criado quando o NavEntry é adicionado à pilha de retorno e limpo quando é removido.

Para usar NavEntryDecorator e 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 rememberSaveableStateHolderNavEntryDecorator() à lista de entryDecorators ao criar um NavDisplay.
  3. Adicione outros decoradores ao seu NavDisplay.

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