A navegação descreve a forma como os usuários se movem pelo app. Eles interagem com elementos da interface, geralmente tocando ou clicando neles, e o app responde mostrando um novo conteúdo. Se o usuário quiser voltar ao conteúdo anterior, ele usa o gesto de volta ou toca no botão "Voltar".
Como modelar o estado de navegação
Uma maneira conveniente de modelar esse comportamento é com uma pilha de conteúdo. Conforme o usuário navega para frente em direção a um novo conteúdo, ele é enviado para o topo da pilha. Quando o usuário volta desse conteúdo, ele é retirado da pilha e o conteúdo anterior é exibido. Em termos de navegação, essa pilha geralmente é chamada de backstack porque representa o conteúdo que o usuário pode acessar novamente.
Criar uma backstack
Na Navigation 3, a backstack não contém conteúdo. Em vez disso, ele contém referências ao conteúdo, conhecidas como chaves. As chaves podem ser de qualquer tipo, mas geralmente são classes de dados simples e serializáveis. O uso de referências em vez de conteúdo tem os seguintes benefícios:
- É simples navegar enviando chaves para a backstack.
- Desde que as chaves sejam serializáveis, a backstack pode ser salva no armazenamento permanente, o que permite que ela sobreviva a mudanças de configuração e ao encerramento do processo. Isso é importante porque os usuários esperam sair do app, voltar mais tarde e continuar de onde pararam, com o mesmo conteúdo sendo mostrado. Consulte Salvar a backstack para mais informações.
Um conceito importante da API Navigation 3 é que você é o proprietário da backstack. A biblioteca:
- Espera que o backstack seja um
List<T>com suporte de estado de snapshot, em queTé o tipo do backstackkeys. Você pode usarAnyou fornecer suas próprias chaves mais fortemente tipadas. Quando você vê os termos "push" ou "pop", a implementação subjacente é adicionar ou remover itens do final de uma lista. - Observa sua backstack e reflete o estado dela na interface usando um
NavDisplay.
O exemplo a seguir mostra como criar chaves e uma backstack e como modificar a backstack em resposta a eventos de navegação do usuário:
// Define keys that will identify content data object ProductList data class ProductDetail(val id: String) @Composable fun MyApp() { // Create a back stack, specifying the key the app should start with val backStack = remember { mutableStateListOf<Any>(ProductList) } // Supply your back stack to a NavDisplay so it can reflect changes in the UI // ...more on this below... // Push a key onto the back stack (navigate forward), the navigation library will reflect the change in state backStack.add(ProductDetail(id = "ABC")) // Pop a key off the back stack (navigate back), the navigation library will reflect the change in state backStack.removeLastOrNull() }
Resolver chaves para conteúdo
O conteúdo é modelado na Navigation 3 usando NavEntry, que é uma classe
que contém uma função combinável. Ele representa um destino, ou seja, um único conteúdo
que o usuário pode acessar e sair.
Um NavEntry também pode conter metadados, que são informações sobre o conteúdo. Esses
metadados podem ser lidos por objetos de contêiner, como NavDisplay, para ajudá-los
a decidir como mostrar o conteúdo do NavEntry. Por exemplo, os metadados podem ser
usados para substituir as animações padrão de um NavEntry específico. NavEntry
metadata é um mapa de chaves String para valores Any, oferecendo armazenamento de dados
versátil.
Para converter um key em um NavEntry, crie um provedor de entrada. Essa é uma
função que aceita um key e retorna um NavEntry para esse key. Ele geralmente é definido como um parâmetro lambda ao criar um NavDisplay.
Há duas maneiras de criar um provedor de entrada: criando uma função lambda
diretamente ou usando a DSL entryProvider.
Criar uma função de provedor de entrada diretamente
Normalmente, você cria uma função de provedor de entrada usando uma instrução when, com
uma ramificação para cada uma das suas chaves.
entryProvider = { key -> when (key) { is ProductList -> NavEntry(key) { Text("Product List") } is ProductDetail -> NavEntry( key, metadata = mapOf("extraDataKey" to "extraDataValue") ) { Text("Product ${key.id} ") } else -> { NavEntry(Unit) { Text(text = "Invalid Key: $it") } } } }
Usar a DSL do entryProvider
A DSL entryProvider pode simplificar sua função lambda, evitando a necessidade
de testar cada um dos tipos de chave e criar um NavEntry para cada um.
Use a função de builder entryProvider para isso. Ele também inclui um comportamento de fallback padrão (gerar um erro) se a chave não for encontrada.
entryProvider = entryProvider { entry<ProductList> { Text("Product List") } entry<ProductDetail>( metadata = mapOf("extraDataKey" to "extraDataValue") ) { key -> Text("Product ${key.id} ") } }
Observe o seguinte no snippet:
entryé usado para definir umNavEntrycom o tipo e o conteúdo combinável especificados.entryaceita um parâmetrometadatapara definirNavEntry.metadata.
Mostrar a backstack
A backstack representa o estado de navegação do app. Sempre que a backstack
mudar, a interface do app precisará refletir o novo estado dela. No Navigation 3, um
NavDisplay observa sua backstack e atualiza a interface de acordo. Crie-o
com os seguintes parâmetros:
- Seu backstack, que precisa ser do tipo
SnapshotStateList<T>, em queTé o tipo das chaves do backstack. É umListobservável para que ele acione a recomposição deNavDisplayquando mudar. - Uma
entryProviderpara converter as chaves na backstack em objetosNavEntry. - Se quiser, forneça uma lambda para o parâmetro
onBack. Ele é chamado quando o usuário aciona um evento de retorno.
O exemplo a seguir mostra como criar um NavDisplay.
data object Home data class Product(val id: String) @Composable fun NavExample() { val backStack = remember { mutableStateListOf<Any>(Home) } NavDisplay( backStack = backStack, onBack = { backStack.removeLastOrNull() }, entryProvider = { key -> when (key) { is Home -> NavEntry(key) { ContentGreen("Welcome to Nav3") { Button(onClick = { backStack.add(Product("123")) }) { Text("Click to navigate") } } } is Product -> NavEntry(key) { ContentBlue("Product ${key.id} ") } else -> NavEntry(Unit) { Text("Unknown route") } } } ) }
Por padrão, o NavDisplay mostra o NavEntry de cima na backstack em um
layout de painel único. A gravação a seguir mostra esse app em execução:
NavDisplay: comportamento padrão com dois
destinos.Ciclo de vida do destino
O NavDisplay usa LifecycleOwners personalizados para limitar o estado do ciclo de vida de um
NavEntry com base nas restrições de nível de Scene e de
entrada.
Para mais informações sobre ciclos de vida no Compose, consulte Ciclo de vida no Jetpack Compose.
Restrições de ciclo de vida no nível da cena
O NavDisplay gerencia o ciclo de vida das Scenes ativas. Os limites no nível da cena são
determinados da seguinte forma:
Para cenas sem sobreposição:
RESUMED: permitido apenas quando a transição de cena é concluída e não há cenas de sobreposição ativas sendo mostradas sobre ela.STARTED: limitado aSTARTEDdurante as transições de cena, como ao navegar para frente ou para trás ou quando é coberto por uma sobreposição.
Para cenas de sobreposição, como caixas de diálogo ou páginas inferiores:
RESUMED: permitido apenas para a cena de sobreposição mais alta e ativa no momento.STARTED: limitado aSTARTEDpara qualquer cena de sobreposição subjacente que esteja coberta por uma sobreposição mais recente.
Estado do ciclo de vida básico
A biblioteca gerencia o estado máximo do ciclo de vida de cada NavEntry
com base na presença dele na backstack:
RESUMED: se a entrada estiver presente na backstack atual, o ciclo de vida dela poderá ir atéRESUMED(sujeito ao limite do nível da cena).CREATED: se a entrada não estiver mais na backstack, como quando ela foi removida, mas ainda está sendo renderizada na tela durante a animação, a biblioteca limita estritamente o ciclo de vida dela aCREATED. Esse limite garante que as entradas em segundo plano ou de saída parem de executar o trabalho ativo, como coletar fluxos ou iniciar corrotinas vinculadas aos estadosRESUMEDouSTARTEDenquanto concluem as transições de saída.
Como eles se combinam
Por exemplo, o estado final do ciclo de vida de um NavEntry é resolvido da seguinte maneira:
| Cenário | Limite no nível da cena | Limite de nível básico | Limite efetivo |
|---|---|---|---|
| Entrada ativa, tela definida (sem transições ou sobreposições) | RESUMED |
RESUMED |
RESUMED |
| Entrada ativa, durante a transição (navegando para ou de) | STARTED |
RESUMED |
STARTED |
| Entrada ativa, coberta por uma sobreposição (por exemplo, uma caixa de diálogo está aberta) | STARTED |
RESUMED |
STARTED |
| Entrada removida, animação de saída | STARTED ou RESUMED |
CREATED |
CREATED |
Como tudo funciona em conjunto
O diagrama a seguir mostra como os dados fluem entre os vários objetos na Navigation 3:
Os eventos de navegação iniciam as mudanças. As chaves são adicionadas ou removidas da backstack em resposta às interações do usuário.
A mudança no estado da backstack aciona a recuperação de conteúdo. O
NavDisplay(um elemento combinável que renderiza uma backstack) observa a backstack. Na configuração padrão, ele mostra a entrada de backstack mais alta em um layout de painel único. Quando a chave superior na backstack muda, oNavDisplayusa essa chave para solicitar o conteúdo correspondente do provedor de entrada.O provedor de entrada fornece conteúdo. O provedor de entrada é uma função que resolve uma chave para um
NavEntry. Ao receber uma chave doNavDisplay, o provedor de entrada fornece oNavEntryassociado, que contém a chave e o conteúdo.O conteúdo é exibido. O
NavDisplayrecebe oNavEntrye mostra o conteúdo.