Categories
How To Studio

Navigation: Conditional Navigation

Murat Jenner

This is the second article in this navigation series. If you want to see this content instead of reading it, check out the video below.

Conditional navigation

In the previous article NavigationUI, Implemented bottom navigation in the app, and more SelectionFragment Enables or disables coffee tracking.However, regardless of whether the coffee tracker is disabled or enabled, users will continue to CoffeeList Fragments that appear to be incorrect.

This article fixes this by adding conditional navigation and telling the user to select it the first time they launch the app.I use Datastore API Holds the user’s selection and uses it to determine the display coffeeList Bottom navigation destination.

Here is a brief review of Change I made it from the previous article.

If you want to see the changes, you can check the repository Here.. You can also check out the code from this repository if you want to follow!

The app is now in three different states.

  • DONUT_ONLY, This means that the user has disabled the coffee tracking feature
  • DONUT_AND_COFFEE, This means that the user wants to track the consumption of both donuts and coffee
  • NOT_SELECTED This means that the user hasn’t made a selection yet and may be running the app for the first time. Or maybe they are having a hard time making their decision 🤷

Start implementing conditional navigation within the SelectionFragment. First, get an instance of selectionViewModel and access the datastore. Then observe the user’s selection and use it to restore the status of the checkbox.To keep the user’s selection, update the state when the checkbox is clicked by calling: 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)

//...

Then update the tabs at the bottom with your choice.If the user chooses to disable coffee tracking, the only option that remains in the bottom tab is donutList This means that you can safely remove the bottom tab.To MainActivity, Add an observer and update the tab display at the bottom.To do this, add an observer and update the visibility of BottomNavigation Depending on the user’s choice.

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
}
}

For onCreate ():

val selectionViewModel: SelectionViewModel by viewModels {
SelectionViewModelFactory(
UserPreferencesRepository.getInstance(this)
)
}
selectionViewModel.checkCoffeeTrackerEnabled().observe(this) { s ->
setupMenu(s)
}

If you run the app in this state, enabling or disabling the coffee tracker will add or remove tabs at the bottom of the app. That’s great, but I don’t want to be able to automatically send selections to users the first time they run the app.

DonutList Is the default fragment and the app always starts DonutList, Check if the user has previously made a selection and trigger navigation SelectionFragment If not.

donutListViewModel.isFirstRun().observe(viewLifecycleOwner) { s ->
if (s == UserPreferencesRepository.Selection.NOT_SELECTED) {
val navController = findNavController()
navController.navigate(
DonutListDirections.actionDonutListToSelectionFragment()
)
}
}

Before testing this, uninstall the app from your device to make sure there are no settings saved in previous runs. When you run the app, selectionFragment.. When I launch the app later, it remembers the choices I made and takes me to the correct starting point.

that’s all! Added conditional navigation to the Donut Tracker app. But how can I test this flow? It is not ideal to delete the app or app data every time before running the test. Tests are useful here!

Create a new test called OneTimeFlowTest With androidTestFolder.Next, create a test called testFirstRun(), And annotate @Test.. You can now start implementing your test. First, TestNavHostController() using applicationContext..I also set nav_graph From the app testNavigationController The instance you just created.

@Test
fun testFirstRun() {
// Create a mock NavController
val mockNavController = TestNavHostController(
ApplicationProvider.getApplicationContext()
)
mockNavController.setGraph(R.navigation.nav_graph) //...
}

The· mockNavigationController Ready to use. It’s time to create a scenario.For that, I launch the app DonutList Fragment and set mockNavigationController Previously created instance.Then check if the app moves automatically selectionFragment As expected.

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)
}

Now I run the test and wait for the result … and the test passes in the flying colors! Or maybe it’s just green..

Navigation test

In this article, I’ve added conditional navigation to the DonutTracker app and also added tests to verify that the flow works.You can check the solution code Here..

With conditional navigation, the donut tracker app triggers a one-time flow to go to the selected fragment the first time the user launches the app.If the user chooses to disable the coffee tracker, the app will coffeeList From the navigation menu.

This completes the coffee tracker function! In future articles, you’ll learn how to modularize this app with nested graphs.

Source

Leave a Reply

Your email address will not be published. Required fields are marked *