使用NavController配置底部导航时返回键清空ViewModel的解决方法
问题描述
导航组件版本:
implementation("androidx.navigation:navigation-fragment-ktx:2.8.0") implementation("androidx.navigation:navigation-ui-ktx:2.8.0")
在Fragment中使用FragmentContainerView和BottomNavigationView,配置代码如下:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { val navView: BottomNavigationView = binding.bottomNav val navHostFragment = (childFragmentManager.findFragmentById( R.id.navHostFragment ) as NavHostFragment) val navController = navHostFragment.navController navView.setupWithNavController(navController) }
遇到的问题:
- 标签页切换(如home -> profile -> home)时,ProfileViewModel的
onCleared()不触发,ViewModel保留 - 选中profile标签页时按返回键,ProfileViewModel的
onCleared()会触发 - 尝试添加
OnBackPressedCallback重写返回逻辑,但handleOnBackPressed()未被调用:
requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner, onBackPressedCallback)
需求:让NavController在返回键按下时不再清除ViewModel。
解决方案
1. 问题根源
按下返回键时,NavController会弹出当前标签页对应的Fragment并销毁,导致关联ViewModel触发onCleared();而标签页切换时,Fragment默认被隐藏而非销毁,因此ViewModel得以保留。你之前的回调未生效,是因为NavController自身的返回键回调优先级更高。
2. 有效拦截返回键的方法
方法一:高优先级回调拦截,替换返回行为
直接注册优先级更高的返回键回调,修改返回时的逻辑,避免销毁当前Fragment:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { // 原有导航配置 val navView: BottomNavigationView = binding.bottomNav val navHostFragment = (childFragmentManager.findFragmentById(R.id.navHostFragment) as NavHostFragment) val navController = navHostFragment.navController navView.setupWithNavController(navController) // 自定义返回键回调 val backCallback = object : OnBackPressedCallback(true) { override fun handleOnBackPressed() { val homeItemId = navView.menu.findItem(R.id.homeFragment).itemId // 替换为你的主页菜单项ID val currentDestId = navController.currentDestination?.id if (currentDestId == homeItemId) { // 当前在主页,直接退出应用 requireActivity().finish() } else { // 非主页时切换回主页,不销毁当前Fragment navController.navigate(homeItemId) } } } // 绑定到Activity生命周期,确保优先级高于NavController的回调 requireActivity().onBackPressedDispatcher.addCallback(requireActivity(), backCallback) }
方法二:手动控制导航行为,避免回退栈销毁Fragment
替代setupWithNavController,手动处理标签选中事件,确保Fragment不会被添加到回退栈,同时拦截返回键:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { val navView: BottomNavigationView = binding.bottomNav val navHostFragment = (childFragmentManager.findFragmentById(R.id.navHostFragment) as NavHostFragment) val navController = navHostFragment.navController // 手动处理标签选中逻辑,不添加到回退栈 navView.setOnItemSelectedListener { item -> val navOptions = NavOptions.Builder() .setLaunchSingleTop(true) // 重用已存在的Fragment实例 .setPopUpTo(navController.graph.startDestinationId, false) .build() navController.navigate(item.itemId, null, navOptions) true } // 拦截返回键逻辑同方法一 val backCallback = object : OnBackPressedCallback(true) { override fun handleOnBackPressed() { val homeItemId = navView.menu.findItem(R.id.homeFragment).itemId if (navController.currentDestination?.id != homeItemId) { navController.navigate(homeItemId) } else { requireActivity().finish() } } } requireActivity().onBackPressedDispatcher.addCallback(requireActivity(), backCallback) }
3. 确认ViewModel绑定方式
确保ViewModel是通过Fragment的viewModels()绑定,保证生命周期与Fragment绑定:
private val profileViewModel: ProfileViewModel by viewModels()
内容的提问来源于stack exchange,提问作者mohammad fakhraee
相关产品推荐
相关产品推荐

