Entender e implementar os conceitos básicos

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".

Modelagem do 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 no topo da pilha. Quando eles voltam desse conteúdo, ele é removido da pilha e o conteúdo anterior é exibido. Em termos de navegação, essa pilha geralmente é chamada de pilha de retorno porque representa o conteúdo a que o usuário pode voltar.

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 pilha de retorno muda com os eventos de navegação do usuário.

Criar uma pilha de retorno

Na navegação 3, a pilha de retorno não contém conteúdo. Em vez disso, ele contém referências a 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 os seguintes benefícios:

  • É simples navegar pressionando as chaves na backstack.
  • Desde que as chaves sejam serializáveis, a pilha de retorno pode ser salva no armazenamento permanente, 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 depois e continuar de onde pararam com o mesmo conteúdo sendo mostrado. Consulte Salvar sua pilha de retorno para mais informações.

Um conceito importante da API Navigation 3 é que você é o proprietário da backstack. A biblioteca:

  • Espera que sua pilha de retorno seja um List<T> com estado de snapshot, em que T é o tipo do keys da pilha de retorno. Você pode usar Any ou fornecer suas próprias chaves com tipagem 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 pilha de retorno e reflete o estado dela na interface usando um NavDisplay.

O exemplo a seguir mostra como criar chaves e uma pilha de retorno e modificar a pilha de retorno 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 no Navigation 3 usando NavEntry, que é uma classe que contém uma função combinável. Ele representa um destino, ou seja, uma única parte de conteúdo que o usuário pode navegar para frente e para trás.

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 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. 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 entradas. Essa é uma função que aceita um key e retorna um NavEntry para esse key. Normalmente, ele é definido como um parâmetro lambda ao criar um NavDisplay.

Há duas maneiras de criar um provedor de entradas: 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 entradas 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 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 do builder entryProvider para isso. Ele também inclui o comportamento de fallback padrão (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 pilha de retorno

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

  • Sua pilha de retorno precisa ser do tipo SnapshotStateList<T>, em que T é o tipo das chaves da pilha de retorno. É um List observável para que ele aciona a recomposição de NavDisplay quando muda.
  • Um entryProvider para converter as chaves na sua pilha de retorno em objetos NavEntry.
  • Se quiser, forneça uma lambda ao parâmetro onBack. Isso é 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 pilha de retorno em um layout de painel único. A gravação a seguir mostra o app em execução:

Comportamento padrão do `NavDisplay` com dois destinos.
Figura 2. NavDisplay comportamento padrão 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 pilha de retorno em resposta às interações do usuário.

  2. A mudança no estado da pilha de retorno 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 superior da pilha de retorno em um layout de painel único. Quando a chave principal na pilha de retorno muda, o NavDisplay usa essa chave para solicitar o conteúdo correspondente do provedor de entradas.

  3. O provedor de entradas fornece conteúdo. O provedor de entradas é 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.