Boas-vindas ao Curso de Jetpack Compose: Navigation II. Me chamo Alex Felipe e serei o seu instrutor ao longo deste curso.
Alex é um homem negro de pele clara, com cabelos raspados e possui barba rente ao rosto. Seus olhos são castanho-escuros. Está vestindo uma camisa preta lisa, usa brincos pequenos e redondos. Está sentado em uma cadeira gamer preta, com um microfone mais à frente do instrutor. Ao fundo, há uma parede na cor branca com uma luz gradiente azul do lado esquerdo e roxa do lado direito.
Caso você não tenha conhecimento do que é o Navigation, principalmente fazemos isso com Jetpack Compose, é importante que você conclua os cursos sobre esse conteúdo aqui na Alura. Os links desses cursos estão na página inicial deste curso, em "Pré-requisitos".
Vamos visualizar o projeto que usaremos como base, acessível no GitHub do próprio projeto do curso inicial de Navigation.
O nome do projeto é Panucci. A ideia é simular um aplicativo de restaurante, que exibe a lista de produtos, os destaques do dia, as bebidas, o menu principal, a tela de pedidos, de detalhes do produto e assim por diante. Tudo isso é feito no primeiro curso!
A partir deste curso de Type Safety, aprenderemos novas técnicas e também mais uma funcionalidade para esse projeto. Então vamos conhecer essas novidades!
Iniciaremos pela parte do projeto. Se pegarmos o projeto do Panucci, ele vai ter uma abordagem diferente quando acessamos a tela de detalhes do produto.
Por exemplo, a partir da tela "Destaques do dia" foi configurada uma falha na busca desse produto. Vamos verificar como o nosso aplicativo vai reagir.
Ao clicarmos no primeiro produto da tela de destaques, observe que conseguimos visualizar uma tela de carregamento tentando carregar os detalhes do produto (essa será uma das funcionalidades que vamos implementar). Mas recebemos a mensagem "Falha ao buscar o produto", com um botão abaixo "Tentar buscar novamente" e outro "Voltar".
Nas outras telas não temos esse problema (propositalmente), como, por exemplo, na tela de Menu. Clicando em "Menu" na parte inferior da tela e depois no primeiro cartão, observe que exibe somente o carregamento. Aprenderemos como implementar isso dentro do nosso projeto.
Sobre a parte técnica, envolvendo o código, faremos uma introdução do que é Type Safety. Uma técnica bem importante quando trabalhamos com o Navigation utilizando o Jetpack Compose, porque será a partir dele que vamos organizar melhor o código e mais segurança na navegação (seja ela simples, com argumentos, com opções de navegação).
Após configurarmos todo o Type Safety, aprenderemos também como conectamos tudo o que fazemos com o Navigation com os componentes de gerenciamento de estado. Como em why state e view model.
Dessa forma, fechamos o fluxo do nosso aplicativo com uma abordagem comum em qualquer aplicativo com Jetpack Compose.
É um curso importante para quem está aprendendo e quer conhecer cada vez mais o Jetpack Compose. Espero que você tenha gostado da proposta do curso, e conto com a sua presença na primeira aula.
Vamos lá?
A configuração de navegação de telas foi construída a partir dos três principais componentes do Navigation, sendo eles: NavHost, navController e o grafo de navegação.
A partir dos grafos, determinamos os destinos que desejamos configurar do aplicativo para exibir cada tela. Dessa forma, definimos qual vai ser a tela, se vai ter alguma configuração de navegação, e assim por diante.
Como resultado, temos um aplicativo que consegue exibir uma tela inicial, navegar em uma tela de menu, de bebidas, de detalhes do produto, etc.
Em outras palavras: o nosso aplicativo está configurado de forma correta, mas a partir desse momento que temos um grafo de navegação e começa a expandir um pouco mais, é um ótimo momento para identificarmos novas técnicas para melhorar a forma como trabalhamos com o Navigation.
Iniciaremos um novo tema, chamado "Type Safety" (em português, "Digitação segura"). Vamos entender esse conceito e o motivo que o faz ser importante de implementarmos no nosso código, e quais os benefícios.
Na documentação, temos um tópico específico falando sobre o Type Safety em "Segurança de tipo na navegação do Compose". Logo no início do parágrafo temos a seguinte afirmação: "o código nesta página (que usamos para configurar o nosso app) não tem segurança na digitação (o que chamamos de type safety)".
Como identificamos isso? Seguindo a leitura do parágrafo da documentação, temos a seguinte informação: "você pode chamar a função "navigate()" com rotas inexistentes ou argumentos incorretos".
Isso é um problema, porque lembrando que quando fazíamos uma navegação para uma rota inexistente ou incorreta, o nosso aplicativo quebrava. Esse é o comportamento padrão dentro do Navigation.
Esse é um dos motivos que nos levaram a usar as constantes, para evitarmos esse problema. Se teclarmos "Ctrl + B" somos redirecionados para o arquivo "AppDestination".
AppDestination
//código omitido
sealed class AppDestination(val route: String) {
object Highlight : AppDestination("highlight")
object Menu : AppDestination("menu")
object Drinks : AppDestination("drinks")
object ProductDetails : AppDestination("productDetails")
object Checkout : AppDestination("checkout")
}
//código omitido
Essas constantes determinam cada destino que configuramos. Aprendemos que isso ajuda a evitar o problema mencionado. Porém, da forma que escrevemos não garante totalmente, ou seja, quando fizermos a nossa configuração mais complexa (expandindo o aplicativo) não teremos a garantia que vamos manter os resultados esperados.
Desse modo, podemos correr esses riscos. Portanto, o Type Safety auxilia na melhoria e simplificação do código escrito. Vamos entender as técnicas para fornecer essa segurança a mais quando configuramos o Navigation.
Na documentação, vamos clicar em "documentação de segurança de tipo na navegação". Seremos redirecionados para a página de Segurança de tipo na DSL do Kotlin e no Navigation Compose.
Na documentação do Type Safety, temos diversas práticas que aplicaremos ao longo do curso. Mas analisando o título, o que seria esse Kotlin DSL? A sigla "DSL" é de Domain Specific Language (em português, "Linguagem Específica de Domínio").
Quando vemos esse tipo de abreviação (DSL), o que podemos interpretar dessa informação? Todo tipo de DSL é uma linguagem da programação feita para resolver problemas específicos. Por exemplo, o SQL que é uma linguagem para realizar consultas às informações do banco de dados.
E a DSL foi criada para resolver esse problema em específico. No caso do Kotlin DSL do Type Safety, será uma forma que configuraremos o código específico para resolver esse problema da configuração de navegação. Por isso que vem com a sigla DSL.
Entendendo tudo isso, vamos compreender o que vai mudar ao que aplicamos atualmente. Como aplicamos no momento não é tão problemático assim, dado que temos as constantes e está tudo funcionando conforme o esperado.
Mas a partir de agora entenderemos os destaques para termos o ponto de partida para aplicarmos o Type Safety. No primeiro parágrafo da documentação, temos: "é necessário mapear cada tela do app ou gráfico de navegação para um arquivo de navegação por módulo".
Ou seja, em cada tela do aplicativo que é feita a configuração de destino, precisamos separar em arquivos distintos.
MainActivity
// código omitido
composable(AppDestination.Highlight.route) {
HighlightsListScreen(
products = sampleProducts,
onNavigateToDetails = { product ->
navController.navigate(
"${AppDestination.ProductDetails.route}/${product.id}"
)
},
onNavigateToCheckout = {
navController.navigate(AppDestination.Checkout.route)
},
)
}
// código omitido
A ideia é a seguinte: quando temos a configuração composable, determinando que é a tela de destaques (temos o composable da tela de destaques, com as configurações para navegarmos), o ideal é que todo o trecho de código anterior seja transferido para um arquivo específico.
E dentro desse arquivo, vamos aplicar as configurações envolvendo técnicas para melhorar a segurança de digitação. Podemos analisar alguns exemplos na documentação, descendo a página, temos:
Exemplo retirado da documentação do Type Safety
// ConversationScreen.kt
@Composable
internal fun ConversationScreen(
uiState: ConversationUiState,
onPinConversation: () -> Unit,
onNavigateToParticipantList: (conversationId: String) -> Unit
) { ... }
Essa é a Tela de conversa, que mostra um composable assim como já fizemos. Descendo um pouco mais, temos o seguinte exemplo na documentação:
Exemplo retirado da documentação do Type Safety
// ConversationNavigation.kt
private const val conversationIdArg = "conversationId"
// Adds conversation screen to `this` NavGraphBuilder
fun NavGraphBuilder.conversationScreen(
// Navigation events are exposed to the caller to be handled at a higher level
onNavigateToParticipantList: (conversationId: String) -> Unit
) {
composable("conversation/{$conversationIdArg}") {
// The ViewModel as a screen level state holder produces the screen
// UI state and handles business logic for the ConversationScreen
val viewModel: ConversationViewModel = hiltViewModel()
val uiState = viewModel.uiState.collectAsStateWithLifecycle()
ConversationScreen(
uiState,
::viewModel.pinConversation,
onNavigateToParticipantList
)
}
}
Aqui é onde determina como é feita essa configuração para esse composable. Observe que ao invés de usar somente o NavHost dentro da MainActivity, teremos um arquivo para a tela criada (no caso do exemplo, o arquivo seria o ConversationNavigation
).
E nesta tela teremos uma função de extensão (fun NavGraphBuilder.conversationScreen()
) que determina que ao invés do composable, agora é uma tela. Dentro dessa tela, temos as informações encapsuladas (composable("conversation/{$conversationIdArg}"){…}
).
Note que o nosso objetivo será analisar o que temos atualmente no código, e aplicar uma refatoração que fará mais sentido no momento que usarmos o Navigation no Compose.
Na sequência, iniciaremos a aplicação do Type Safety!
Agora que conhecemos o Type Safety, o próximo passo será iniciar a refatoração para aplicar as boas práticas e as técnicas que vimos na documentação. Vamos começar?
No projeto, vamos em "app > src > main > java > br.com.alura.panucci > MainActivity.kt". O objetivo inicial é justamente separar os destinos, para que eles tenham arquivos exclusivos, assim como vimos na documentação do Type Safety.
Trecho do código selecionado pelo instrutor
// código omitido
composable(AppDestination.Highlight.route) {
HighlightsListScreen(
products = sampleProducts,
onNavigateToDetails = { product ->
navController.navigate(
"${AppDestination.ProductDetails.route}/${product.id}"
)
},
onNavigateToCheckout = {
navController.navigate(AppDestination.Checkout.route)
},
)
}
// código omitido
Porém, antes de iniciarmos essa migração é importante avaliarmos um ponto que já deixamos de configuração sobre o nosso NavHost. Observe que esse NavHost possui toda configuração Navigation, sendo um pouco extensa e complexa.
O detalhe que precisamos entender é que esse tipo de configuração faz sentido. Criamos um arquivo exclusivo, ou um próprio composable que vai determinar que é o nosso próprio NavHost (para separarmos esse código e deixá-lo mais organizado).
Primeiro, antes de realizarmos a migração de cada arquivo específico para cada destino, migraremos o nosso NavHost.
Agora criaremos um composable para o NavHost dentro do pacote navigation
(em "app > src > main > java > br.com.alura.panucci > navigation"). Para isso, clicamos com o botão direito no pacote "navigation" e escolhemos "Kotlin File".
No pop-up seguinte, deixamos "File" marcado e digitamos o nome "PanucciNavHost". Usamos o nome do aplicativo como prefixo. Ao teclarmos "Enter", seremos redirecionados para o arquivo que acabamos de gerar.
PanucciNavHost.kt
package br.com.alura.panucci.navigation
Podemos incluir a anotação "@Composable" que vai ter uma função PanucciNavHost
.
PanucciNavHost.kt
package br.com.alura.panucci.navigation
import androidx.compose.runtime.Composable
@Composable
fun PanucciNavHost() {
}
Dentro do "PanucciNavHost()", vamos pegar o trecho de código do NavHost no arquivo MainActivity
. Na linha 83, podemos clicar em "NavHost" para selecionar todo código, ou podemos usar uma técnica do Android Studio chamada seleção estendida.
Podemos habilitar isso, no Windows, usando o atalho "Ctrl + W". Observe que ao teclarmos uma vez, ele seleciona a palavra "NavHost", se teclarmos novamente "Ctrl + W" é selecionada a palavra e tudo o que está dentro seus parênteses (seus argumentos).
Clicando mais uma vez as teclas "Ctrl + W", é selecionado o corpo todo, referente ao escopo. Após selecionar, teclamos "Ctrl + X" para recortarmos o que está selecionado e voltar ao arquivo "PannucciNavHost" para colar dentro da função.
Trecho do código para copiarmos no arquivo "MainActivity"
NavHost(
navController = navController,
startDestination = AppDestination.Highlight.route
) {
composable(AppDestination.Highlight.route) {
HighlightsListScreen(
products = sampleProducts,
onNavigateToDetails = { product ->
navController.navigate(
"${AppDestination.ProductDetails.route}/${product.id}"
)
},
onNavigateToCheckout = {
navController.navigate(AppDestination.Checkout.route)
},
)
}
composable(AppDestination.Menu.route) {
MenuListScreen(
products = sampleProducts,
onNavigateToDetails = { product ->
navController.navigate(
"${AppDestination.ProductDetails.route}/${product.id}"
)
},
)
}
composable(AppDestination.Drinks.route) {
DrinksListScreen(
products = sampleProducts,
onNavigateToDetails = { product ->
navController.navigate(
"${AppDestination.ProductDetails.route}/${product.id}"
)
},
)
}
composable(
"${AppDestination.ProductDetails.route}/{productId}"
) { backStackEntry ->
val id = backStackEntry.arguments?.getString("productId")
sampleProducts.find {
it.id == id
}?.let { product ->
ProductDetailsScreen(
product = product,
onNavigateToCheckout = {
navController.navigate(AppDestination.Checkout.route)
},
)
} ?: LaunchedEffect(Unit) {
navController.navigateUp()
}
}
composable(AppDestination.Checkout.route) {
CheckoutScreen(
products = sampleProducts,
onPopBackStack = {
navController.navigateUp()
},
)
}
}
PanucciNavHost.kt
package br.com.alura.panucci.navigation
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import br.com.alura.panucci.sampledata.sampleProducts
import br.com.alura.panucci.ui.screens.*
@Composable
fun PanucciNavHost() {
NavHost(
navController = navController,
startDestination = AppDestination.Highlight.route
) {
composable(AppDestination.Highlight.route) {
HighlightsListScreen(
products = sampleProducts,
onNavigateToDetails = { product ->
navController.navigate(
"${AppDestination.ProductDetails.route}/${product.id}"
)
},
onNavigateToCheckout = {
navController.navigate(AppDestination.Checkout.route)
},
)
}
composable(AppDestination.Menu.route) {
MenuListScreen(
products = sampleProducts,
onNavigateToDetails = { product ->
navController.navigate(
"${AppDestination.ProductDetails.route}/${product.id}"
)
},
)
}
composable(AppDestination.Drinks.route) {
DrinksListScreen(
products = sampleProducts,
onNavigateToDetails = { product ->
navController.navigate(
"${AppDestination.ProductDetails.route}/${product.id}"
)
},
)
}
composable(
"${AppDestination.ProductDetails.route}/{productId}"
) { backStackEntry ->
val id = backStackEntry.arguments?.getString("productId")
sampleProducts.find {
it.id == id
}?.let { product ->
ProductDetailsScreen(
product = product,
onNavigateToCheckout = {
navController.navigate(AppDestination.Checkout.route)
},
)
} ?: LaunchedEffect(Unit) {
navController.navigateUp()
}
}
composable(AppDestination.Checkout.route) {
CheckoutScreen(
products = sampleProducts,
onPopBackStack = {
navController.navigateUp()
},
)
}
}
}
Observe que ele está com problemas para identificar o NavController (que está escrito na cor vermelha). Isso acontece porque ainda não temos o NavController.
Por isso, podemos clicar em "NavController", teclar "Alt + Enter" e clicar na opção "Create parameter 'navController'". Ao escolhermos essa possibilidade, será aberta uma janela indicando o que será criado.
No caso, ele vai criar um parâmetro do tipo NavHostController, basta clicarmos no botão "Refactor" no canto inferior direito.
PanucciNavHost.kt
package br.com.alura.panucci.navigation
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import br.com.alura.panucci.sampledata.sampleProducts
import br.com.alura.panucci.ui.screens.*
@Composable
fun PanucciNavHost(navController: NavHostController) {
NavHost(
navController = navController,
startDestination = AppDestination.Highlight.route
)
//código omitido
Pronto! Conseguimos criar o composable que contempla todo código de configuração do Navigation.
Voltando ao arquivo "MainActivity", dentro do escopo do "PanucciApp" chamaremos o "PanucciNavHost".
Trecho do código do "PanucciApp" no arquivo "MainActivity"
//código omitido
PanucciApp(
bottomAppBarItemSelected = selectedItem,
onBottomAppBarItemSelectedChange = {
val route = it.destination.route
navController.navigate(route) {
launchSingleTop = true
popUpTo(route)
}
},
onFabClick = {
navController.navigate(AppDestination.Checkout.route)
},
isShowTopBar = containsInBottomAppBarItems,
isShowBottomBar = containsInBottomAppBarItems,
isShowFab = isShowFab
) {
}
//código omitido
Vamos inserir após o abrir chaves, escrevemos "PanucciNavHost(navController = navController)". Assim, o nosso trecho de código ficará:
MainActivity
//código omitido
PanucciApp(
bottomAppBarItemSelected = selectedItem,
onBottomAppBarItemSelectedChange = {
val route = it.destination.route
navController.navigate(route) {
launchSingleTop = true
popUpTo(route)
}
},
onFabClick = {
navController.navigate(AppDestination.Checkout.route)
},
isShowTopBar = containsInBottomAppBarItems,
isShowBottomBar = containsInBottomAppBarItems,
isShowFab = isShowFab
) {
PanucciNavHost(navController = navController)
}
//código omitido
Com essa configuração feita, podemos iniciar a migração de cada destino para arquivos específicos (arquivo "PanucciNavHost").
Voltando a documentação , verificando o padrão em "Dividir o gráfico de navegação", podemos usar o exemplo "Conversation" da própria documentação.
Trecho do código de exemplo da documentação:
// ConversationScreen.kt
@Composable
internal fun ConversationScreen(
uiState: ConversationUiState,
onPinConversation: () -> Unit,
onNavigateToParticipantList: (conversationId: String) -> Unit
) { ... }
Observe que o "ConversationScreen" é um composable, assim como já temos nas nossas telas. Descendo a documentação para o próximo exemplo, note que ele criou um arquivo ConversationNavigation
.
Trecho do código de exemplo da documentação:
// ConversationNavigation.kt
private const val conversationIdArg = "conversationId"
// Adds conversation screen to `this` NavGraphBuilder
fun NavGraphBuilder.conversationScreen(
// Navigation events are exposed to the caller to be handled at a higher level
onNavigateToParticipantList: (conversationId: String) -> Unit
) {
composable("conversation/{$conversationIdArg}") {
// The ViewModel as a screen level state holder produces the screen
// UI state and handles business logic for the ConversationScreen
val viewModel: ConversationViewModel = hiltViewModel()
val uiState = viewModel.uiState.collectAsStateWithLifecycle()
ConversationScreen(
uiState,
::viewModel.pinConversation,
onNavigateToParticipantList
)
}
}
Então se temos o nosso highlightsList, teremos o "highlightsList.navigation". Se tivermos o menu, temos o "menu.navigation" e assim por diante. É justamente isso que precisamos fazer!
Note que as funções que vamos separar são funções estendidas do NavGraphBuilder
. O que é esse NavGraphBuilder? É a referência a que temos acesso quando chamamos o NavHost. Neste, temos como último parâmetro (no caso o builder) que vai ter um tipo de função que será a extensão de NavGraphBuilder.
Logo, precisamos dele para chamar o composable responsáveis por configurar o destino. Por isso precisa ser uma função de extensão. Antes de criar esse arquivo, uma das técnicas que podemos usar é selecionar cada grafo no arquivo "PanucciNavHost" e usar o atalho de extrair métodos (Ctrl + Alt + M).
Grafo selecionado pelo instrutor no arquivo "PanucciNavHost"
//código omitido
composable(AppDestination.Highlight.route) {
HighlightsListScreen(
products = sampleProducts,
onNavigateToDetails = { product ->
navController.navigate(
"${AppDestination.ProductDetails.route}/${product.id}"
)
},
onNavigateToCheckout = {
navController.navigate(AppDestination.Checkout.route)
},
)
}
//código omitido
Após teclarmos, ele já aciona de forma automática uma configuração de função de extensão (expressão lambda) no nosso NavGraphBuilder
. No campo "visibility" deixaremos como "public" (em português, "público") e não privado, porque outras pessoas precisam acessar. E no campo "Nome" digitamos "highlightsListScreen". Logo após, clicamos no botão "Ok".
PanucciNavHost
//código omitido
NavHost(
navController = navController,
startDestination = AppDestination.Highlight.route
)
highlightsListScreen(navController)
composable(AppDestination.Menu.route) {
MenuListScreen(
products = sampleProducts,
onNavigateToDetails = { product ->
navController.navigate(
"${AppDestination.ProductDetails.route}/${product.id}"
)
},
)
}
}
//código omitido
Agora, sim, estamos com o padrão que vimos do nosso Type Safety. Porém, há alguns detalhes que precisamos nos atentar ao extrair dessa forma.
Descendo o código, observe que ele coloca como se fosse o composable. Por padrão, ele aplica essa estrutura e não temos muito o que alterar quando estamos no escopo do composable.
PanucciNavHost
// código omitido
@Composable
fun NavGraphBuilder.highlightsListScreen(navController: NavHostController) {
composable(AppDestination.Highlight.route) {
HighlightsListScreen(
products = sampleProducts,
onNavigationToDetails = { product ->
navController.navigate(
"${AppDestination.ProductDetails.route}/${product.id}"
)
},
onNavigateToCheckout = {
navController.navigate(AppDestination.Checkout.route)
},
)
}
}
Mas dentro dessa expressão lambda, já não estamos mais num escopo de composable. Por isso, precisamos remover. Essa é a desvantagem de aplicar essa técnica, mas ele já adiantou bastante coisa para nós.
O que falta é criarmos o arquivo desse nosso destino, faremos isso para os outros também. Mas agora faremos para essa função de extensão que fizemos inicial para a tela de destaques.
Do lado esquerdo, clicamos com o botão direito no pacote navigation
e escolhemos a opção "Kotlin Class/File". Nomearemos de "HighlightsListNavigation" e teclamos "Enter". Seremos redirecionados para o arquivo:
HighlightsListNavigation
package br.com.alura.panucci.navigation
Com esse arquivo criado, voltamos ao "PanucciNavHost" e recortamos do código o trecho com a função NavGraphBuilder
(nossa função de extensão) para colarmos em "HighlightsListNavigation".
HighlightsListNavigation
package br.com.alura.panucci.navigation
import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavHostController
import androidx.navigation.compose.composable
import br.com.alura.panucci.sampledata.sampleProducts
import br.com.alura.panucci.ui.screens.HighlightsListScreen
fun NavGraphBuilder.highlightsListScreen(navController: NavHostController) {
composable(AppDestination.Highligh.route) {
HighlightsListScreen(
products = sampleProducts,
onNavigateToDetails = { product ->
navController.navigate(
"${AppDestination.ProductDetails.route}/${product.id}"
)
},
onNavigateToCheckout = {
navController.navigate(AppDestination.Checkout.route)
},
)
}
}
O primeiro passo está feito, agora faremos isso para os outros composables. Observe no arquivo "PanucciNavHost" que com essa configuração, fica claro que estamos trabalhando com a nossa HighlightsListScreen
.
Para fazermos isso com os de mais, selecionamos o menu:
PanucciNavHost
// código omitido
composable(AppDestination.Menu.route) {
MenuListScreen(
products = sampleProducts,
onNavigationToDetails = { product ->
navController.navigate(
"${AppDestination.ProductDetails.route}/${product.id}"
)
},
)
}
Com esse trecho selecionado, teclamos "Ctrl + Alt + M". No campo "Nome" digitamos "menuScreen". O código ficará:
PanucciNavHost
//código omitido
NavHost(
navController = navController,
startDestination = AppDestination.Highlight.route
) {
highlightsListScreen(navController)
menuScreen(navController)
composable(AppDestination.Drinks.route) {
DrinksListScreen(
products = sampleProducts,
onNavigateToDetails = { product ->
navController.navigate(
"${AppDestination.ProductDetails.route}/${product.id}"
)
},
)
}
//código omitido
Agora selecionamos todo o composable de "Drinks" e teclamos "Ctrl + Alt + M". Nomearemos de "drinksScreen" e clicamos no botão "Ok".
PanucciNavHost
//código omitido
NavHost(
navController = navController,
startDestination = AppDestination.Highlight.route
) {
highlightsListScreen(navController)
menuScreen(navController)
drinksScreen(navController)
//código omitido
Logo após, faremos o mesmo processo para "ProductDetails".
PanucciNavHost
//código omitido
composable(
"${AppDestination.ProductDetails.route}/{productId}"
) { backStackEntry ->
val id = backStackEntry.arguments?.getString("productId")
sampleProducts.find {
it.id == id
}?.let { product ->
ProductDetailsScreen(
product = product,
onNavigateToCheckout = {
navController.navigate(AppDestination.Checkout.route)
},
)
} ?: LaunchedEffect(Unit) {
navController.navigateUp()
}
}
//código omitido
Selecionamos, teclamos "Ctrl + Alt + M" e nomeamos de "productDetailsScreen". Assim, o trecho do código ficará:
PanucciNavHost
//código omitido
NavHost(
navController = navController,
startDestination = AppDestination.Highlight.route
) {
highlightsListScreen(navController)
menuScreen(navController)
drinksScreen(navController)
productDetailsScreen(navController)
//código omitido
Mais uma vez, mas agora em "Checkout". Selecionamos o seguinte trecho do código, teclamos "Ctrl + Alt + M" e nomeamos de "checkoutScreen".
PanucciNavHost
//código omitido
composable(AppDestination.Checkout.route) {
CheckoutScreen(
products = sampleProducts,
onPopBackStack = {
navController.navigateUp()
},
)
}
//código omitido
Com isso, temos todos os nossos métodos! Agora precisamos colocar os arquivos para testar.
PanucciNavHost
package br.com.alura.panucci.navigation
import androidx.navigation.compose.composable
import br.com.alura.panucci.sampledata.sampleProducts
import br.com.alura.panucci.ui.screens.*
@Composable
fun PanucciNavHost(navController: NavHostController) {
NavHost(
navController = navController,
startDestination = AppDestination.Highlight.route
) {
highlightsListScreen(navController)
menuScreen(navController)
drinksScreen(navController)
productDetailsScreen(navController)
checkoutScreen(navController)
}
}
@Composable
private fun NavGraphBuilder.checkoutScreen(navController: NavHostController) {
composable(AppDestination.Checkout.route) {
CheckoutScreen(
products = sampleProducts,
onPopBackStack = {
navController.navigateUp()
},
)
}
}
//código omitido
Nos composables do arquivo, podemos remover as anotações "@Composable" e o "private". Assim, ficamos com:
PanucciNavHost
// código omitido
fun NavGraphBuilder.checkoutScreen(navController: NavHostController) {
composable(AppDestination.Checkout.route) {
CheckoutScreen(
products = sampleProducts,
onPopBackStack = {
navController.navigateUp()
},
)
}
}
// código omitido
Iniciaremos por esse checkoutScreen
. Criamos um arquivo para ele selecionando o pacote "navigation" do lado esquerdo e nomeando o arquivo Kotlin de "CheckoutNavigation". Após teclarmos "Enter", somos redirecionados para o arquivo:
CheckoutNavigation
package br.com.alura.panucci.navigation
Depois de criar o arquivo "CheckoutNavigation", copiamos a função de extensão do arquivo "PanucciNavHost" e colamos nele.
CheckoutNavigation
package br.com.alura.panucci.navigation
import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavHostController
import androidx.navigation.compose.composable
import br.com.alura.panucci.sampledata.sampleProducts
import br.com.alura.panucci.ui.screens.CheckoutScreen
fun NavGraphBuilder.checkoutScreen(navController: NavHostController) {
composable(AppDestination.Checkout.route) {
CheckoutScreen(
products = sampleProducts,
onPopBackStack = {
navController.navigateUp()
},
)
}
}
Faremos o mesmo para tela de Menu. Vamos criar um arquivo dentro do pacote navigation
nomeado "MenuNavigation".
MenuNavigation
package br.com.alura.panucci.navigation
Agora vamos ao arquivo "PanucciNavHost" e copiamos a função de extensão de Menu.
PanucciNavHost
//código omitido
fun NavGraphBuilder.menuScreen(navController: NavHostController) {
composable(AppDestination.Menu.route) {
MenuListScreen(
products = sampleProducts,
onNavigateToDetails = { product ->
navController.navigate(
"${AppDestination.ProductDetails.route}/${product.id}"
)
},
)
}
}
//código omitido
MenuNavigation
package br.com.alura.panucci.navigation
import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavHostController
import androidx.navigation.compose.composable
import br.com.alura.panucci.sampledata.sampleProducts
import br.com.alura.panucci.ui.screens.MenuListScreen
fun NavGraphBuilder.menuScreen(navController: NavHostController) {
composable(AppDestination.Menu.route) {
MenuListScreen(
products = sampleProducts,
onNavigateToDetails = { product ->
navController.navigate(
"${AppDestination.ProductDetails.route}/${product.id}"
)
},
)
}
}
Perfeito! Agora faremos para "Drinks", criando um arquivo chamado "DrinksNavigation" dentro do pacote navigation
. Pegamos o "drinksScreen" no arquivo "PanucciNavHost" e colamos dentro do arquivo que acabamos de criar.
DrinksNavigation
package br.com.alura.panucci.navigation
import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavHostController
import androidx.navigation.compose.composable
import br.com.alura.panucci.sampledata.sampleProducts
import br.com.alura.panucci.ui.screens.DrinksListScreen
fun NavGraphBuilder.drinksScreen(navController: NavHostController) {
composable(AppDestination.Drinks.route) {
DrinksListScreen(
products = sampleProducts,
onNavigateToDetails = { product ->
navController.navigate( "${AppDestination.ProductDetails.route}/${product.id}"
)
},
)
}
}
Até o momento, temos os seguintes arquivos dentro do pacote navigation:
Todo esse processo que estamos fazendo, se fosse com um grafo enorme, ficaria muito mais complexo de fazer. Portanto, é interessante entendermos esses exemplos menores e começarmos a compreender o valor dessa configuração.
Ficou faltando apenas os detalhes dos produtos!
Do lado esquerdo, criaremos o arquivo "ProductDetailsNavigation" e faremos o mesmo processo que todos os anteriores. Ficaremos com o seguinte arquivo:
ProductDetailsNavigation
package br.com.alura.panucci.navigation`
import androidx.compose.runtime.LaunchedEffect
import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavHostController
import androidx.navigation.compose.composable
import br.com.alura.panucci.sampledata.sampleProducts
import br.com.alura.panucci.ui.screens.ProductDetailsScreen
fun NavGraphBuilder.productDetailsScreen(navController: NavHostController) {
composable(
"${AppDestination.ProductDetails.route}/{productId}"
) { backStackEntry ->
val id = backStackEntry.arguments?.getString("productId")
sampleProducts.find {
it.id == id
}?.let { product ->
ProductDetailsScreen(
product = product,
onNavigateToCheckout = {
navController.navigate(AppDestination.Checkout.route)
},
)
} ?: LaunchedEffect(Unit) {
navController.navigateUp()
}
}
}
Temos todos os nossos arquivos! Agora vamos verificar como ficou o arquivo "PanucciNavHost".
PanucciNavHost
package br.com.alura.panucci.navigation
import androidx.compose.runtime.Composable
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
@Composable
fun PanucciNavHost(navController: NavHostController) {
NavHost(
navController = navController,
startDestination = highlightsListRoute
) {
highlightsListScreen(navController)
menuScreen(navController)
drinksScreen(navController)
productDetailsScreen(navController)
checkoutScreen(navController)
}
}
Observe que a leitura ficou mais simplificada. Temos uma lista de menu, destaques, bebidas, detalhes do produto e a tela do pedido. Ou seja, temos um grande impacto ao aplicarmos essa primeira configuração.
Podemos usar o atalho "Ctrl + Alt + O", para remover os imports
desnecessários. Agora é necessário testarmos para analisar se o nosso app ainda funciona.
O app rodou, acessaremos todas as telas, de Menu, Destaques, Bebidas, Detalhes do produto e Pedidos. Isso significa que ele está funcionando, mas iniciamos a primeira configuração para aplicar as boas práticas e técnicas do Type Safety.
O curso Jetpack Compose: Navigation com Type Safety possui 142 minutos de vídeos, em um total de 48 atividades. Gostou? Conheça nossos outros cursos de Android em Mobile, ou leia nossos artigos de Mobile.
Matricule-se e comece a estudar com a gente hoje! Conheça outros tópicos abordados durante o curso:
Impulsione a sua carreira com os melhores cursos e faça parte da maior comunidade tech.
1 ano de Alura
Assine o PLUS e garanta:
Formações com mais de 1500 cursos atualizados e novos lançamentos semanais, em Programação, Inteligência Artificial, Front-end, UX & Design, Data Science, Mobile, DevOps e Inovação & Gestão.
A cada curso ou formação concluído, um novo certificado para turbinar seu currículo e LinkedIn.
No Discord, você tem acesso a eventos exclusivos, grupos de estudos e mentorias com especialistas de diferentes áreas.
Faça parte da maior comunidade Dev do país e crie conexões com mais de 120 mil pessoas no Discord.
Acesso ilimitado ao catálogo de Imersões da Alura para praticar conhecimentos em diferentes áreas.
Explore um universo de possibilidades na palma da sua mão. Baixe as aulas para assistir offline, onde e quando quiser.
Acelere o seu aprendizado com a IA da Alura e prepare-se para o mercado internacional.
1 ano de Alura
Todos os benefícios do PLUS e mais vantagens exclusivas:
Luri é nossa inteligência artificial que tira dúvidas, dá exemplos práticos, corrige exercícios e ajuda a mergulhar ainda mais durante as aulas. Você pode conversar com a Luri até 100 mensagens por semana.
Aprenda um novo idioma e expanda seus horizontes profissionais. Cursos de Inglês, Espanhol e Inglês para Devs, 100% focado em tecnologia.
Transforme a sua jornada com benefícios exclusivos e evolua ainda mais na sua carreira.
1 ano de Alura
Todos os benefícios do PRO e mais vantagens exclusivas:
Mensagens ilimitadas para estudar com a Luri, a IA da Alura, disponível 24hs para tirar suas dúvidas, dar exemplos práticos, corrigir exercícios e impulsionar seus estudos.
Envie imagens para a Luri e ela te ajuda a solucionar problemas, identificar erros, esclarecer gráficos, analisar design e muito mais.
Escolha os ebooks da Casa do Código, a editora da Alura, que apoiarão a sua jornada de aprendizado para sempre.