Navegação: navegação condicional
Conteudo
Introdução
No artigo anterior, usei a NavigationUI
, implementei a navegação inferior no aplicativo e também adicionei um SelectionFragment
para habilitar ou desabilitar o rastreamento do café. No entanto, independentemente de desabilitarmos ou habilitarmos o rastreador de café, os usuários ainda podem navegar até o fragmento CoffeeList
, o que não parece muito certo.
Neste artigo, corrigirei isso adicionando navegação condicional e orientando nossos usuários a fazer uma seleção ao iniciar o aplicativo pela primeira vez. Vou usar a API do Datastore para persistir a seleção do usuário e usá-la para decidir a exibição do destino da coffeeList
na navegação inferior.
Preparando o aplicativo para navegação condicional
Aqui está uma rápida revisão das alterações que fiz desde o último artigo.
- Primeiro, adicionei um
UserPreferencesRepository
que usa a API Datastore para persistir a seleção do usuário. - Para acessar o repositório, fiz algumas alterações em ViewModelFactories e como
DonutListViewModel
eSelectionViewModel
são construídos.
Se você quiser revisar as alterações, pode verificar o repositório aqui. Você também pode verificar o código deste repositório se quiser acompanhar!
O aplicativo agora pode estar em 3 estados diferentes.
DONUT_ONLY
, o que significa que o usuário desativou a funcionalidade de rastreamento de caféDONUT_AND_COFFEE
, o que significa que o usuário deseja acompanhar o consumo de Donut e CaféNOT_SELECTED
, o que significa que o usuário ainda não fez uma seleção e possivelmente está executando o aplicativo pela primeira vez ou talvez ele apenas esteja com dificuldade para se decidir
Implementando Navegação Condicional
Vou começar a implementar a navegação condicional no SelectionFragment. Primeiro, obtenho uma instância de selectionViewModel para obter acesso ao armazenamento de dados. Em seguida, observo a seleção do usuário e a utilizo para restaurar o status da caixa de seleção. Para manter a seleção do usuário, atualizarei o estado quando a caixa de seleção for clicada chamando saveCoffeeTrackerSelection()
.
val selectionViewModel: SelectionViewModel by viewModels { SelectionViewModelFactory( UserPreferencesRepository.getInstance(requireContext()) ) } selectionViewModel.checkCoffeeTrackerEnabled().observe( viewLifecycleOwner ) { selection -> if (selection == UserPrefRepository.Selection.DONUT_AND_COFFEE){ binding.checkBox.isChecked = true } } binding.button.setOnClickListener { button -> val coffeeSelected = binding.checkBox.isChecked selectionViewModel.saveCoffeeTrackerSelection(coffeeSelected) //...
Agora é hora de atualizar as guias inferiores com a seleção do usuário. Se o usuário optar por desabilitar o rastreamento do café, a única opção deixada nas guias inferiores será a donutList, o que significa que podemos remover com segurança as guias inferiores. Em MainActivity
, adiciono um observador e atualizo a visibilidade das guias inferiores. Para fazer isso, vou adicionar um observador e atualizar a visibilidade do BottomNavigation
de acordo com a seleção do usuário.
private fun setupMenu( selection: UserPreferencesRepository.Selection ) { val bottomNav = findViewById<BottomNavigationView>(R.id.bottom_nav_view) bottomNav.isVisible = when (selection) { UserPreferencesRepository.Selection.DONUT_AND_COFFEE -> true else -> false } }
Em onCreate():
val selectionViewModel: SelectionViewModel by viewModels { SelectionViewModelFactory( UserPreferencesRepository.getInstance(this) ) } selectionViewModel.checkCoffeeTrackerEnabled().observe(this) { s -> setupMenu(s) }
Executando o aplicativo neste estado, você verá que ativar ou desativar o rastreador de café adiciona ou remove as guias inferiores do aplicativo. Isso é ótimo, mas não seria bom se enviássemos automaticamente o usuário para fazer uma seleção quando executasse o aplicativo pela primeira vez.
DonutList
é o fragmento padrão e nosso destino inicial, o que significa que o aplicativo sempre começa com a DonutList
, eu verifico se o usuário fez uma seleção anteriormente e aciono a navegação para SelectionFragment
se não o fez.
donutListViewModel.isFirstRun().observe(viewLifecycleOwner) { s -> if (s == UserPreferencesRepository.Selection.NOT_SELECTED) { val navController = findNavController() navController.navigate( DonutListDirections.actionDonutListToSelectionFragment() ) } }
Antes de testar isso, desinstalo o aplicativo do dispositivo para ter certeza de que não há preferências salvas de minhas execuções anteriores. Agora, quando executo o aplicativo, ele me leva ao selectionFragment.
Os lançamentos posteriores do aplicativo lembrarão da seleção que fiz e me levarão ao destino inicial correto.
E é isso! Adicionamos navegação condicional ao aplicativo rastreador de rosca. Mas como podemos testar esse fluxo? Excluir o aplicativo ou os dados do aplicativo todas as vezes antes de executar o teste não é o ideal. É aqui que o teste vem para resgatar!
Teste de navegação
Eu crio um novo teste chamado OneTimeFlowTest
em androidTestFolder. Em seguida, crio um teste chamado testFirstRun()
e anoto-o com @Test. Agora posso começar a implementar o teste. Primeiro, crio um TestNavHostController()
usando o applicationContext
. Também defino o nav_graph
do aplicativo para a instância testNavigationController
que acabei de criar.
@Test fun testFirstRun() { // Create a mock NavController val mockNavController = TestNavHostController( ApplicationProvider.getApplicationContext() ) mockNavController.setGraph(R.navigation.nav_graph) //... }
O mockNavigationController
está pronto para uso. É hora de criar o cenário. Para fazer isso, eu inicio o aplicativo com o fragmento DonutList
e defino a instância mockNavigationController
que criei antes. Em seguida, verifique se o aplicativo navega automaticamente para o selectionFragment
conforme o esperado.
val scenario = launchFragmentInContainer { DonutList().also { fragment -> fragment.viewLifecycleOwnerLiveData.observeForever{ viewLifecycleOwner -> if (viewLifecycleOwner != null){ Navigation.setViewNavController( fragment.requireView(), mockNavController ) } } } } scenario.onFragment { assertThat( mockNavController.currentDestination?.id ).isEqualTo(R.id.selectionFragment) }
Agora eu executo o teste e espero o resultado … e o teste passa com louvor! ou bem pode ser apenas verde.
Resumo
Neste artigo, adicionei navegação condicional ao aplicativo DonutTracker e também adicionei um teste para verificar se o fluxo funciona! Você pode verificar o código da solução aqui.
Com a navegação condicional, o aplicativo rastreador de rosca acionará o fluxo único para levar os usuários ao fragmento de seleção quando eles iniciarem o aplicativo pela primeira vez. Se o usuário optar por desabilitar o rastreador de café, o aplicativo remove a coffeeList do menu de navegação.
Com isso, a funcionalidade do rastreador de café está completa! Nos próximos artigos, aprenderemos como usar gráficos aninhados e modularizar este aplicativo.