使用导航组件: 条件导航 | MAD Skills
这是第二个关于导航 (Navigation) 的 MAD Skills 系列,本文是导航组件系列的第二篇文章,如果您想回顾过去发布的内容,请参考下面链接查看:
△ 条件导航
概述
条件导航 (Conditional navigation) 指的是在为应用设计导航时,您可能需要基于条件逻辑将用户转到某一个目的地而非另一个。例如,用户可能会跟随深层链接前往一个需要用户登录的目的地,或者您可能会在游戏中针对玩家的输赢提供不同的目的地。
在上一篇文章中,我使用 NavigationUI 实现了应用的底部导航,并增加了 SelectionFragment 来启用或禁用咖啡记录功能。然而,无论我们禁用或启用咖啡记录器,用户都可以导航到 CoffeeList Fragment 页面,这看起来不太符合逻辑。
在本文中,我将通过添加条件导航来修复这个问题,并且当用户首次启用应用时指导我们的用户做出选择。我将使用 Datastore API 来保存用户的选择,并据此决定是否在底部导航中展示 coffeeList 目的地。
Datastore API
https://developer.android.google.cn/topic/libraries/architecture/datastore
在应用中使用条件导航的准备工作
首先,我添加了 UserPreferencesRepository,它使用 DataStore API 来保存用户的选择;
为了访问该 Repository,我在各 ViewModel 工厂类中也做出了些许改变,并且修改了 DonutListViewModel 和 SelectionViewModel 的构造方式。
修改
https://github.com/google-developer-training/android-demos/tree/starter/DonutTracker/ConditionalNavigationUserPreferencesRepository
https://github.com/google-developer-training/android-demos/blob/starter/DonutTracker/ConditionalNavigation/app/src/main/java/com/android/samples/donuttracker/UserPreferencesRepository.ktDonutListViewModel
https://github.com/google-developer-training/android-demos/blob/starter/DonutTracker/ConditionalNavigation/app/src/main/java/com/android/samples/donuttracker/donut/DonutListViewModel.ktSelectionViewModel
https://github.com/google-developer-training/android-demos/blob/starter/DonutTracker/ConditionalNavigation/app/src/main/java/com/android/samples/donuttracker/setup/SelectionViewModel.kt
请查阅该仓库查看具体的修改内容
https://github.com/google-developer-training/android-demos/tree/starter/DonutTracker/ConditionalNavigation
DONUT_ONLY: 意味着用户禁用了咖啡记录功能
DONUT_AND_COFFEE: 意味着用户想同时记录甜甜圈和咖啡的消费情况
NOT_SELECTED: 意味着用户还没有做出选择而且有可能是第一次启动应用,或者用户也许很难做出决定🤷
实现条件导航
我将在 SelectionFragment 中开始实现条件导航。首先我获取了 SelectionViewModel 的一个实例,因此我可以通过它访问 DataStore。然后,我观察 (Observe) 了用户的选择并以此来恢复复选框的状态。为了保存用户的选择,我将在复选框被点击时调用 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)
//...
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
}
}
在 onCreate() 中:
val selectionViewModel: SelectionViewModel by viewModels {
SelectionViewModelFactory(
UserPreferencesRepository.getInstance(this)
)
}
selectionViewModel.checkCoffeeTrackerEnabled().observe(this) { s ->
setupMenu(s)
}
donutListViewModel.isFirstRun().observe(viewLifecycleOwner) { s ->
if (s == UserPreferencesRepository.Selection.NOT_SELECTED) {
val navController = findNavController()
navController.navigate(
DonutListDirections.actionDonutListToSelectionFragment()
)
}
}
测试导航
@Test
fun testFirstRun() {
// 创建模拟的 NavController
val mockNavController = TestNavHostController(
ApplicationProvider.getApplicationContext()
)
mockNavController.setGraph(R.navigation.nav_graph)
//...
}
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)
}
小结
https://github.com/google-developer-training/android-demos/tree/main/DonutTracker/ConditionalNavigation
推荐阅读