androidandroid-navigationandroid-navigation-graphandroid-bottomnavigationviewandroid-splashscreen

Android BottomNavigationView with Splash Screen Back Stack Issue


So I setup my BottomNavigationView (like here) using Navigation Component library and everything works fine, every tab can keep their back stacks. However, if I add a Splash screen (Fragment) and:

  1. Set it as start destination (popUpInclusive set to true already)
  2. Create action from SplashFragment to the first tab HomeFragment

then all the tabs no longer keep their back stacks, plus the navigation becomes weird:

Splash -> Home (first tab) -> Me (second tab) -> Home -> press back, it goes back to Me instead of exiting the app.

PS: I'm using single Activity pattern with single navigation graph.

enter image description here


Solution

  • Ok, thanks to @ianhanniballake, I post my final solution here, the key point is that the BottomNavigationView must be the start destination insead of other conditional destination like login or splash screen.


    Step 1. Create and Setup Splash Layout and Fragment

    Step 2. Setup MainViewModel (shared ViewModel)

    class MainViewModel : ViewModel() {
        private var _isFirstLaunch = true  //replace with the real condition in the future
    
        val isFirstLaunch: Boolean         //will be accessed by SplashFragment and HomeFragment
            get() = _isFirstLaunch
    
        fun updateIsFirstLaunch(isFirstLaunch: Boolean) {
            _isFirstLaunch = isFirstLaunch
        }
    }
    

    Step 3. Setup HomeFragment

    class HomeFragment : Fragment() {
        private lateinit var binding: FragmentHomeBinding
        private lateinit var mainViewModel: MainViewModel  //shared ViewModel
    
        override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
            binding = DataBindingUtil.inflate(inflater, R.layout.fragment_home, container, false)
            mainViewModel = ViewModelProvider(requireActivity()).get(MainViewModel::class.java)
    
            if (mainViewModel.isFirstLaunch) {
                findNavController().navigate(R.id.splashFragment)   //no need action, unless you want transition animation
            }
    
            binding.goButton.setOnClickListener {
                findNavController().navigate(R.id.action_homeFragment_to_home2Fragment)
            }
    
            return binding.root
        }
    }
    

    Step 4. Setup SplashFragment

    class SplashFragment : Fragment() {
        private lateinit var binding: FragmentSplashBinding
        private lateinit var mainViewModel: MainViewModel  //shared ViewModel
    
        override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
            binding = DataBindingUtil.inflate(inflater, R.layout.fragment_splash, container, false)
            mainViewModel = ViewModelProvider(requireActivity()).get(MainViewModel::class.java)
    
            binding.exitSplashButton.setOnClickListener {
                mainViewModel.updateIsFirstLaunch(false)  //update the condition
    
                findNavController().navigateUp()    //go back to HomeFragment
            } 
    
            requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner) {
                requireActivity().finish()
            }
    
            return binding.root
        }
    }
    

    Demo: https://youtu.be/AxiIsY6BtGg