Entender e implementar os conceitos básicos

A navegação descreve a maneira como os usuários se movem pelo app. Eles interagem com elementos da interface, geralmente tocando ou clicando neles, e o app responde exibindo um novo conteúdo. Se o usuário quiser voltar ao conteúdo anterior, ele vai usar o gesto de volta ou tocar no botão de volta.

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 para um novo conteúdo, ele é empurrado para a parte de cima da pilha. Quando eles voltam para esse 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.

Um botão de ação do teclado de software (um ícone de marca de seleção) circulado em vermelho.
Figura 1. Diagrama mostrando como a backstack muda com os eventos de navegação do usuário.

Criar uma backstack

Na navegação 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. Usar referências em vez de conteúdo tem as seguintes vantagens:

  • É simples navegar pressionando teclas na backstack.
  • Contanto que as chaves sejam serializáveis, a backstack pode ser salva no armazenamento persistente, permitindo que ela sobreviva a mudanças de configuração e à morte do processo. Isso é importante porque os usuários esperam sair do app, voltar a ele mais tarde e continuar de onde pararam com o mesmo conteúdo exibido. 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 um List<T> com suporte a estado de snapshot, em que T é o tipo de keys da backstack. Você pode usar Any ou fornecer suas próprias chaves com tipos mais fortes. 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 modificar a backstack em resposta aos 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, um único conteúdo que o usuário pode navegar e voltar.

Um NavEntry também pode conter metadados, ou seja, informações sobre o conteúdo. Esses metadados podem ser lidos por objetos de contêiner, como NavDisplay, para ajudar a decidir como exibir 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, fornecendo armazenamento de dados versátil.

Para converter um key em um NavEntry, crie um entryProvider. 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 uma NavDisplay.

Há duas maneiras de criar um entryProvider: criando uma função lambda diretamente ou usando a DSL entryProvider.

Criar uma função entryProvider diretamente

Normalmente, você cria uma função entryProvider 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 uma delas. Para isso, use a função do builder entryProvider. Ele também inclui o comportamento padrão de substituto (gerando 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 um NavEntry com o tipo e o conteúdo combinável especificados.
  • entry aceita um parâmetro metadata para definir NavEntry.metadata

Mostrar a backstack

A backstack representa o estado de navegação do app. Sempre que a backstack mudar, a interface do app vai refletir o novo estado da backstack. No Navigation 3, um NavDisplay observa a backstack e atualiza a interface de acordo com ela. Crie com os seguintes parâmetros:

  • Sua backstack: precisa ser do tipo SnapshotStateList<T>, em que T é o tipo das chaves da backstack. Ele é um List observável para que acione a recomposição de NavDisplay quando ele mudar.
  • Um entryProvider para converter as chaves da backstack em NavEntrys.
  • Opcionalmente, forneça uma lambda para o parâmetro onBack. Isso é chamado quando o usuário aciona um evento de retorno.

O exemplo a seguir mostra como criar uma 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, a NavDisplay mostra a NavEntry mais alta na backstack em um layout de painel único. A gravação a seguir mostra o app em execução:

Comportamento padrão de `NavDisplay` com dois
destinos.
Figura 2. Comportamento padrão de NavDisplay com dois destinos.

Para resumir

O diagrama a seguir mostra como os dados fluem entre os vários objetos na Navegação 3:

Uma visualização de como os dados fluem entre os vários objetos na navegação 3.
Figura 3. Diagrama mostrando como os dados fluem por vários objetos na navegação 3.
  1. Os eventos de navegação iniciam mudanças. As chaves são adicionadas ou removidas da backstack em resposta às interações do usuário.

  2. 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 único layout de painel. Quando a chave superior na backstack muda, o NavDisplay usa essa chave para solicitar o conteúdo correspondente do provedor de entrada.

  3. 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 do NavDisplay, o provedor de entrada fornece o NavEntry associado, que contém a chave e o conteúdo.

  4. O conteúdo é exibido. O NavDisplay recebe o NavEntry e mostra o conteúdo.