O componente Navigation oferece uma linguagem de domínio específico (DSL) baseada em Kotlin que depende dos builders com segurança de tipos do Kotlin. Essa API permite que você componha o gráfico de forma declarativa no código Kotlin, em vez
de em um recurso XML. Isso pode ser útil se você quiser criar a navegação do app
dinamicamente. Por exemplo, o app pode fazer o download e armazenar em cache uma
configuração de navegação de um serviço da Web externo e, em seguida, usar essa
configuração para criar dinamicamente um gráfico de navegação na função
onCreate() da atividade.
Dependências
Para usar a DSL do Kotlin com fragmentos, adicione a seguinte dependência ao arquivo
build.gradle do app:
Groovy
dependencies { def nav_version = "2.10.2" api "androidx.navigation:navigation-fragment-ktx:$nav_version" }
Kotlin
dependencies { val nav_version = "2.10.2" api("androidx.navigation:navigation-fragment-ktx:$nav_version") }
Como criar um gráfico
Veja um exemplo básico com base no app Sunflower. Para este
exemplo, temos dois destinos: home e plant_detail. O destino home
está presente quando o usuário inicia o app pela primeira vez. Esse destino
mostra uma lista de plantas do jardim do usuário. Quando o usuário seleciona uma das
plantas, o app navega até o destino plant_detail.
A Figura 1 mostra esses destinos junto com os argumentos exigidos pelo destino plant_detail e uma ação, to_plant_detail, que o app usa para navegar de home para plant_detail.
home e plant_detail, além de uma ação que os
conecta.Como hospedar um gráfico de navegação DSL do Kotlin
Antes de criar o gráfico de navegação do seu app, você precisa de um local para
hospedá-lo. Este exemplo usa fragmentos, então hospeda o gráfico em um
NavHostFragment
dentro de uma
FragmentContainerView:
<!-- activity_garden.xml -->
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/nav_host"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true" />
</FrameLayout>
O atributo app:navGraph não está definido nesse exemplo. O gráfico
não está definido como um recurso na
pasta res/navigation. Portanto, ele precisa ser definido como parte do processo
onCreate() na atividade.
Em XML, uma ação combina um ID de destino com um ou mais argumentos. No entanto, ao usar a DSL de navegação, uma rota pode conter argumentos como parte dela. Isso significa que não há conceito de ações ao usar a DSL.
A próxima etapa é definir as rotas que serão usadas ao definir o gráfico.
Criar rotas para seu gráfico
Os gráficos de navegação baseados em XML são analisados como parte
do processo de build do Android. Uma constante numérica é criada para cada atributo id
definido no gráfico. Esses IDs estáticos gerados no tempo de build não estão
disponíveis durante a criação do gráfico de navegação no tempo de execução. Portanto, a DSL de navegação
usa tipos
serializáveis em vez de
IDs. Cada rota é representada por um tipo exclusivo.
Ao lidar com argumentos, eles são integrados ao tipo de rota. Isso permite que você tenha segurança de tipos para seus argumentos de navegação.
@Serializable data object Home
@Serializable data class Plant(val id: String)
Criar um gráfico com a DSL do NavGraphBuilder
Depois de definir as rotas, você pode criar o gráfico de navegação.
val navController = findNavController(R.id.nav_host_fragment)
navController.graph = navController.createGraph(
startDestination = Home
) {
fragment<HomeFragment, Home> {
label = resources.getString(R.string.home_title)
}
fragment<PlantDetailFragment, PlantDetail> {
label = resources.getString(R.string.plant_detail_title)
}
}
Nesse exemplo, dois destinos de fragmento são definidos usando a
função do builder
DSL fragment(). Essa função requer dois argumentos de tipo.
Primeiro, uma classe Fragment,
que fornece a interface para esse destino. Definir isso tem o mesmo efeito que
definir o atributo android:name em destinos de fragmento definidos
usando XML.
Segundo, a rota. Precisa ser um tipo serializável que se estende de Any. Ele
precisa conter todos os argumentos de navegação que serão usados por esse destino
e os tipos deles.
A função também aceita uma lambda opcional para outras configurações, por exemplo, o marcador de destino, bem como funções incorporadas do builder para ações, argumentos personalizados e links diretos.
Como navegar com o gráfico DSL do Kotlin
Por fim, é possível navegar de home para plant_detail usando chamadas
NavController.navigate():
private fun navigateToPlant(plantId: String) {
findNavController().navigate(route = PlantDetail(id = plantId))
}
No PlantDetailFragment, é possível acessar os argumentos de navegação acessando
o
NavBackStackEntry
atual
e chamando
toRoute
nele para acessar a instância de rota.
val plantDetailRoute = findNavController().getBackStackEntry<PlantDetail>().toRoute<PlantDetail>()
val plantId = plantDetailRoute.id
Se PlantDetailFragment estiver usando um ViewModel, acesse a instância de rota usando
SavedStateHandle.toRoute.
val plantDetailRoute = savedStateHandle.toRoute<PlantDetail>()
val plantId = plantDetailRoute.id
O restante deste guia descreve elementos comuns de gráficos de navegação, destinos e como usá-los ao criar seu gráfico.
Destinos
A DSL do Kotlin oferece suporte integrado a três tipos de destino:
Fragment, Activity e NavGraph, cada um com a própria
função de extensão inline disponível para criação e configuração do
destino.
Destinos de fragmento
A função DSL
fragment()
pode ser parametrizada com a classe de fragmento para a interface e o
tipo de rota usado para identificar exclusivamente esse destino, seguido por uma lambda
em que é possível fornecer configurações adicionais, conforme descrito na seção Como navegar
com o gráfico DSL do Kotlin.
fragment<MyFragment, MyRoute> {
label = getString(R.string.fragment_title)
// custom argument types, deepLinks
}
Destino da atividade
A função DSL activity() usa um parâmetro de tipo para a rota, mas não é parametrizada para nenhuma classe de atividade de implementação. Em vez disso, define um activityClass opcional em
uma lambda final. Essa flexibilidade permite definir um destino para uma atividade que precisa ser iniciada usando uma intent implícita, em que uma classe de atividade explícita não faria sentido. Assim como nos destinos de fragmento, você pode configurar um rótulo, argumentos personalizados e links diretos.
activity<MyRoute> {
label = getString(R.string.activity_title)
// custom argument types, deepLinks...
activityClass = MyActivity::class
}
Destino do gráfico de navegação
A função DSL navigation()
pode ser usada para criar um
gráfico de navegação aninhado. Essa função usa um parâmetro de tipo
para a rota a ser atribuída a esse gráfico. Ela também usa dois argumentos:
a rota do destino inicial do gráfico e uma lambda para configurá-lo
melhor. Os elementos válidos incluem outros destinos, tipos de argumentos personalizados, links diretos e um rótulo descritivo para o destino.
Esse rótulo pode ser útil para vincular o gráfico de navegação a componentes de interface usando
NavigationUI.
@Serializable data object HomeGraph
@Serializable data object Home
navigation<HomeGraph>(startDestination = Home) {
// label, other destinations, deep links
}
Compatibilidade com destinos personalizados
Se você estiver usando um novo tipo de destino
que não oferece suporte à DSL do Kotlin diretamente, adicione esses destinos à DSL
usando
addDestination():
// The NavigatorProvider is retrieved from the NavController
val customDestination = navigatorProvider[CustomNavigator::class].createDestination().apply {
route = Graph.CustomDestination.route
}
addDestination(customDestination)
Como alternativa, você também pode usar o operador unário de adição para adicionar um destino recém-construído diretamente ao gráfico:
// The NavigatorProvider is retrieved from the NavController
+navigatorProvider[CustomNavigator::class].createDestination().apply {
route = Graph.CustomDestination.route
}
Como fornecer argumentos de destino
Os argumentos de destino podem ser definidos como parte da classe de rota. Eles podem ser definidos da mesma forma que você faria para qualquer classe Kotlin. Os argumentos obrigatórios são definidos como tipos não anuláveis, e os argumentos opcionais são definidos com valores padrão.
O mecanismo subjacente para representar rotas e os argumentos delas é baseado em
strings. O uso de strings para modelar rotas permite que o estado de navegação seja armazenado e
restaurado do disco durante mudanças de
configuração e encerramento de processos
iniciados pelo sistema. Por esse motivo,
cada argumento de navegação precisa ser serializável, ou seja, precisa ter um
método que converta a representação na memória do valor do argumento em uma
String.
O plug-in de serialização do Kotlin
gera automaticamente métodos de serialização para tipos
básicos quando a
anotação @Serializable é adicionada a um objeto.
@Serializable
data class MyRoute(
val id: String,
val myList: List<Int>,
val optionalArg: String? = null
)
fragment<MyFragment, MyRoute>
Como fornecer tipos personalizados
Para tipos de argumento personalizados, é necessário fornecer uma classe NavType personalizada. Isso
permite controlar exatamente como seu tipo é analisado em uma rota ou link direto.
Por exemplo, uma rota usada para definir uma tela de pesquisa pode conter uma classe que representa os parâmetros de pesquisa:
@Serializable
data class SearchRoute(val parameters: SearchParameters)
@Serializable
@Parcelize
data class SearchParameters(
val searchQuery: String,
val filters: List<String>
)
Um NavType personalizado pode ser criado desta forma:
val SearchParametersType = object : NavType<SearchParameters>(
isNullableAllowed = false
) {
override fun put(bundle: Bundle, key: String, value: SearchParameters) {
bundle.putParcelable(key, value)
}
override fun get(bundle: Bundle, key: String): SearchParameters {
return bundle.getParcelable(key) as SearchParameters
}
override fun serializeAsValue(value: SearchParameters): String {
// Serialized values must always be Uri encoded
return Uri.encode(Json.encodeToString(value))
}
override fun parseValue(value: String): SearchParameters {
// Navigation takes care of decoding the string
// before passing it to parseValue()
return Json.decodeFromString<SearchParameters>(value)
}
}
Ele pode ser usado na DSL do Kotlin como qualquer outro tipo:
fragment<SearchFragment, SearchRoute>(
typeMap = mapOf(typeOf<SearchParameters>() to SearchParametersType)
) {
label = getString(R.string.plant_search_title)
}
Ao navegar até o destino, crie uma instância da rota:
val params = SearchParameters("rose", listOf("available"))
navController.navigate(route = SearchRoute(params))
O parâmetro pode ser recebido da rota no destino:
val searchRoute = navController().getBackStackEntry<SearchRoute>().toRoute<SearchRoute>()
val params = searchRoute.parameters
Links diretos
Os links diretos podem ser adicionados a qualquer destino, assim como em um gráfico de navegação baseado em XML. Todos os procedimentos definidos em Como criar um link direto para um destino se aplicam ao processo de criação de um link direto usando a DSL do Kotlin.
No entanto, ao criar um link direto implícito,
você não tem um recurso de navegação XML
que pode ser analisado para elementos <deepLink>. Dessa forma, não é possível colocar um elemento <nav-graph>
no arquivo AndroidManifest.xml. Em vez disso, adicione filtros de
intent à atividade manualmente. O filtro de intent fornecido precisa corresponder ao caminho de base, à ação e ao tipo MIME dos links diretos do app.
Os links diretos são adicionados a um destino chamando a função deepLink dentro
da lambda do destino. Ela aceita a rota como um tipo parametrizado e um
parâmetro basePath para o caminho de base do URL usado para o link direto.
Também é possível adicionar uma ação e um tipo MIME usando a
lambda final
deepLinkBuilder.
O exemplo a seguir cria um URI de link direto para o destino Home.
@Serializable data object Home
fragment<HomeFragment, Home>{
deepLink<Home>(basePath = "www.example.com/home"){
// Optionally, specify the action and/or mime type that this destination
// supports
action = "android.intent.action.MY_ACTION"
mimeType = "image/*"
}
}
Formato do URI
O formato do URI do link direto é gerado automaticamente com base nos campos do trajeto usando as seguintes regras:
- Os parâmetros obrigatórios são anexados como parâmetros de caminho (exemplo:
/{id}). - Os parâmetros com um valor padrão (parâmetros opcionais) são anexados como parâmetros de consulta (exemplo:
?name={name}). - As coleções são anexadas como parâmetros de consulta (exemplo:
?items={value1}&items={value2}) - A ordem dos parâmetros corresponde à ordem dos campos na rota.
Por exemplo, o seguinte tipo de rota:
@Serializable data class PlantDetail(
val id: String,
val name: String,
val colors: List<String>,
val latinName: String? = null,
)
tem um formato de URI gerado de:
basePath/{id}/{name}/?colors={color1}&colors={color2}&latinName={latinName}
Não há limite para o número de links diretos que podem ser adicionados. Sempre que você chama
deepLink(),
um novo link direto é anexado a uma lista mantida para esse destino.
Limitações
O plug-in Safe Args é
incompatível com a DSL do Kotlin, porque o plug-in procura arquivos de recursos XML para
gerar classes Directions e Arguments.