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 de ciclo de vida, incluindo mudanças de configuração e encerramento do processo, é crucial para uma boa experiência do usuário. No Navigation 3, você é responsável pela backstack, então não há diretrizes estritas sobre como criar ou salvar. No entanto, o Navigation 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 persiste em mudanças de configuração e encerramentos de processos.

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

  • Implementar a interface NavKey: cada chave na backstack 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, 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 remember 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 lidar com polimorfismo aberto, consulte NavBackStackSamples.

Alternativa: armazenamento 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, é necessário:

  • Verifique se as chaves são serializáveis: assim como em 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 no armazenamento persistente (por exemplo, SharedPreferences, um banco de dados ou um arquivo) e desserializá-la quando o app estiver em segundo plano ou sendo restaurado.

Como definir o escopo de instâncias de ViewModel para objetos NavEntry

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

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

A biblioteca de complementos androidx.lifecycle:lifecycle-viewmodel-navigation3 oferece 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 para a chave desse NavEntry específico na backstack. Isso significa que o ViewModel é criado quando o NavEntry é adicionado à backstack e limpo quando ele é removido.

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 rememberSaveableStateHolderNavEntryDecorator() padrão à lista de entryDecorators ao criar 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 { },
)