A Navigation component fornece uma linguagem específica de domínio, ou DSL, baseada em
Kotlin, que depende dos builders com
segurança de tipos do Kotlin.
Essa API permite compor seu gráfico de maneira declarativa no código Kotlin, em vez de dentro de um recurso XML. Isso pode ser útil se você quer criar a navegação do seu 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 usar essa configuração para criar dinamicamente um gráfico de navegação na função onCreate()
da sua atividade.
Dependências
Para usar a DSL do Kotlin, adicione a seguinte dependência ao arquivo build.gradle
do app:
Groovy
dependencies { def nav_version = "2.7.7" api "androidx.navigation:navigation-fragment-ktx:$nav_version" }
Kotlin
dependencies { val nav_version = "2.7.7" api("androidx.navigation:navigation-fragment-ktx:$nav_version") }
Como criar um gráfico
Vamos começar com um exemplo básico com base no
app Sunflower (link em inglês). Neste
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 exibe uma lista de plantas do jardim do usuário. Quando o usuário seleciona uma das plantas, o app navega para 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
.
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 algumas constantes que serão usadas ao definir o gráfico.
Criar constantes 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 strings de rota em vez de IDs. Cada rota é representada por
uma string exclusiva, e é recomendável defini-las como constantes para reduzir
o risco de bugs relacionados a erros de digitação.
Ao lidar com argumentos, eles são integrados à string de rota. Criar essa lógica na rota pode, mais uma vez, reduzir o risco de bugs relacionadas a erros de digitação.
object nav_routes {
const val home = "home"
const val plant_detail = "plant_detail"
}
object nav_arguments {
const val plant_id = "plant_id"
const val plant_name = "plant_name"
}
Criar um gráfico com a DSL do NavGraphBuilder
Depois de definir as constantes, você pode criar o gráfico de navegação.
val navController = findNavController(R.id.nav_host_fragment)
navController.graph = navController.createGraph(
startDestination = nav_routes.home
) {
fragment<HomeFragment>(nav_routes.home) {
label = resources.getString(R.string.home_title)
}
fragment<PlantDetailFragment>("${nav_routes.plant_detail}/{${nav_arguments.plant_id}}") {
label = resources.getString(R.string.plant_detail_title)
argument(nav_arguments.plant_id) {
type = NavType.StringType
}
}
}
Nesse exemplo, a lambda final define dois destinos de fragmento usando a
função do builder
DSL fragment()
. Essa função requer uma string de rota para o destino
que é recebida das constantes. 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 e links diretos.
A classe Fragment
que
gerencia a interface de cada destino é transmitida como um tipo parametrizado entre
colchetes (<>
). Isso tem o mesmo efeito de definir o atributo android:name
em destinos de fragmento definidos usando XML.
Como navegar com o gráfico DSL do Kotlin
Por fim, é possível navegar de home
para plant_detail
usando chamadas
NavController.navigate()
padrão:
private fun navigateToPlant(plantId: String) {
findNavController().navigate("${nav_routes.plant_detail}/$plantId")
}
No PlantDetailFragment
, é possível extrair o valor do argumento conforme mostrado
neste exemplo:
val plantId: String? = arguments?.getString(nav_arguments.plant_id)
Confira detalhes sobre como fornecer argumentos ao navegar na seção fornecer argumentos de destino.
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 para a classe de fragmento de implementação e usa uma
string de rota exclusiva para levar a esse destino, seguida 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<FragmentDestination>(nav_routes.route_name) {
label = getString(R.string.fragment_title)
// arguments, deepLinks
}
Destino da atividade
A função DSL activity()
usa uma string de rota exclusiva para atribuir a esse destino, 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 nos destinos de fragmento,
você pode configurar um rótulo, argumentos e links diretos.
activity(nav_routes.route_name) {
label = getString(R.string.activity_title)
// arguments, deepLinks...
activityClass = ActivityDestination::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.
Ela usa três argumentos: uma rota para
atribuir ao gráfico, a rota do destino inicial do gráfico e uma
lambda para configurá-lo melhor. Os elementos válidos incluem outros destinos,
argumentos, 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 a
NavigationUI
navigation("route_to_this_graph", nav_routes.home) {
// label, other destinations, deep links
}
Oferecer suporte a destinos personalizados
Se você estiver usando um
novo tipo de destino que não
oferece suporte à DSL do Kotlin diretamente, você pode adicionar 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
Qualquer destino pode definir argumentos opcionais ou obrigatórios. As ações
podem ser definidas usando a
função argument()
no NavDestinationBuilder
, que é a classe de base para todos
os tipos de builder de destino. Essa função usa o nome do argumento como uma string
e uma lambda usada para criar e configurar um
NavArgument
.
Dentro da lambda, você pode especificar os tipos de dados do argumento, um valor padrão, se aplicável, e se eles são ou não anuláveis.
fragment<PlantDetailFragment>("${nav_routes.plant_detail}/{${nav_arguments.plant_id}}") {
label = getString(R.string.plant_details_title)
argument(nav_arguments.plant_id) {
type = NavType.StringType
defaultValue = getString(R.string.default_plant_id)
nullable = true // default false
}
}
Se um defaultValue
for fornecido, o tipo poderá ser inferido. Se um defaultValue
e um type
forem fornecidos, os tipos precisarão ser correspondentes. Consulte a documentação de referência
NavType para acessar uma
lista completa dos tipos de argumento disponíveis.
Como fornecer tipos personalizados
Alguns tipos, como
ParcelableType
e
SerializableType
,
não oferecem suporte à análise de valores das strings usadas por rotas ou links diretos.
Isso ocorre porque eles não dependem de reflexão no tempo de execução. Ao fornecer uma classe
NavType
personalizada, é possível controlar exatamente como seu tipo é analisado em uma rota ou
link direto. Isso permite usar a
serialização do Kotlin (link em inglês) ou outras
bibliotecas para fornecer codificação e decodificação sem reflexão do seu tipo personalizado.
Por exemplo, uma classe de dados que representa os parâmetros de pesquisa transmitidos para a
tela de pesquisa pode implementar Serializable
(para oferecer suporte à
codificação/decodificação) e Parcelize
(para oferecer suporte ao salvamento e restauração
de uma Bundle
):
@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>(nav_routes.plant_search) {
label = getString(R.string.plant_search_title)
argument(nav_arguments.search_parameters) {
type = SearchParametersType
defaultValue = SearchParameters("cactus", emptyList())
}
}
O NavType
encapsula a gravação e a leitura de cada campo, o que
significa que o NavType
também precisa ser usado ao navegar até o
destino para garantir que os formatos correspondam:
val params = SearchParameters("rose", listOf("available"))
val searchArgument = SearchParametersType.serializeAsValue(params)
navController.navigate("${nav_routes.plant_search}/$searchArgument")
O parâmetro pode ser recebido dos argumentos no destino:
val params: SearchParameters? = arguments?.getParcelable(nav_arguments.search_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 explícito 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 padrão do URL de base, à ação e
ao tipo MIME dos links diretos do app.
É possível fornecer um deeplink
mais específico para cada destino de link direto
individual usando a
função
deepLink()
da DSL. Essa função aceita um NavDeepLink
que contém uma String
que representa o padrão de URI, umaString
para as ações da intent e uma String
para o mimeType.
Por exemplo:
deepLink {
uriPattern = "http://www.example.com/plants/"
action = "android.intent.action.MY_ACTION"
mimeType = "image/*"
}
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.
Confira um cenário de link direto implícito mais complexo que também define parâmetros baseados em caminho e consulta:
val baseUri = "http://www.example.com/plants"
fragment<PlantDetailFragment>(nav_routes.plant_detail) {
label = getString(R.string.plant_details_title)
deepLink(navDeepLink {
uriPattern = "${baseUri}/{id}"
})
deepLink(navDeepLink {
uriPattern = "${baseUri}/{id}?name={plant_name}"
})
}
A interpolação de strings pode ser usada para simplificar a definição.
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
.
Saiba mais
Confira a página Segurança de tipos de navegação para saber como fornecer segurança de tipos para os códigos da DSL do Kotlin e da navegação do Compose.