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. À medida que o usuário navega para frente até um novo conteúdo, ele é colocado na parte de cima da pilha. Quando ele volta desse conteúdo, ele é removido da pilha e o conteúdo anterior é mostrado. Em termos de navegação, essa pilha geralmente é chamada de backstack porque representa o conteúdo para o qual o usuário pode voltar.
Criar uma backstack
Na Navigation 3, a backstack não contém conteúdo. Em vez disso, ela contém referências ao conteúdo, conhecidas como chaves. As chaves podem ser de qualquer tipo, mas geralmente são classes de dados serializáveis simples. O uso de referências em vez de conteúdo tem os seguintes benefícios:
- É simples navegar enviando chaves para a backstack.
- Enquanto as chaves forem serializáveis, a backstack poderá ser salva no armazenamento persistente, permitindo que ela sobreviva a mudanças de configuração e à interrupção 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 na API Navigation 3 é que você é o proprietário da backstack. A biblioteca:
- Espera que a backstack seja uma
List<T>com suporte ao estado de snapshot, em queTé o tipo daskeysda backstack. Você pode usarAnyou fornecer suas próprias chaves com tipo mais forte. Quando você vê os termos "push" ou "pop", a implementação subjacente é adicionar ou remover itens do final de uma lista. - Observa a 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. Ela representa um destino , uma única parte
do conteúdo para a qual o usuário pode navegar para frente e para trás.
Um NavEntry também pode conter metadados, informações sobre o conteúdo. Esses metadados podem ser lidos por objetos de contêiner, como NavDisplay, para ajudar 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. Os metadata do NavEntry são um mapa de chaves String para valores Any, fornecendo armazenamento de dados versátil.
Para converter uma key em um NavEntry, crie um provedor de entrada. Essa é uma função que aceita uma key e retorna um NavEntry para essa key. Geralmente, ela é definida 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 entryProvider DSL.
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 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 entryProvider
A DSL entryProvider pode simplificar sua função lambda, evitando a necessidade de testar cada um dos tipos de chave e construir um NavEntry para cada um deles.
Use a função de builder entryProvider para isso. Ela também inclui o 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 seu app. Sempre que a backstack muda, a interface do app precisa refletir o novo estado da backstack. Na Navigation 3, um NavDisplay observa a backstack e atualiza a interface de acordo com ela. Construa-o com os seguintes parâmetros:
- Sua backstack: precisa ser do tipo
SnapshotStateList<T>, em queTé o tipo das chaves da backstack. É umaListobservável para que acione a recomposição deNavDisplayquando mudar. - Um
entryProviderpara converter as chaves na backstack em objetosNavEntry. - Opcionalmente, forneça um lambda ao 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 mais alto 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
NavDisplay usa personalizados LifecycleOwners para limitar o estado do ciclo de vida de um
NavEntry com base em restrições no nível da cena e da 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
NavDisplay gerencia o ciclo de vida das Scenes ativas. Os limites no nível da cena são determinados da seguinte maneira:
Para cenas não sobrepostas:
RESUMED: permitido apenas quando a transição de cena for concluída e não houver cenas de sobreposição ativas sendo mostradas na parte de cima.STARTED: limitado aSTARTEDdurante as transições de cena, como ao navegar para frente ou para trás, ou quando estiver 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 cenas de sobreposição subjacentes cobertas por uma sobreposição mais recente.
Estado do ciclo de vida no nível da entrada
A biblioteca gerencia o estado máximo do ciclo de vida de cada NavEntry individual com base na presença dele na backstack:
RESUMED: se a entrada estiver presente na backstack atual, o ciclo de vida dela poderá chegar aRESUMED(sujeito ao limite no 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 o ciclo de vida dela aCREATED. Esse limite garante que as entradas em segundo plano ou de saída parem de executar trabalhos ativos, como coletar fluxos ou iniciar corrotinas vinculadas a estadosRESUMEDouSTARTED, enquanto terminam 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 no nível da entrada | 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 mudanças. As chaves são adicionadas ou removidas da backstack em resposta a 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 da 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 é mostrado. O
NavDisplayrecebe oNavEntrye mostra o conteúdo.