Search code examples
androidandroid-dialogfragmentandroid-architecture-componentsbottom-sheetandroid-architecture-navigation

Use bottomsheet's action to open another dialogfragment without crashing android navigation component


I have Fragment A, BottomSheetDialogFragment B, and DialogFragment C. I want to be able to choose an action from B, dismiss B to go back to A, then use the navigation livedata to navigate from A to C.

A and B shares the same activityViewModel. My intention is that when the action button in B is clicked, it will dismiss B, modify a flag in the activityViewModel, and A will observe change to the flag and open C subsequently. However, I get an error and I think that it think I'm navigating from B to C (instead of A to C):

java.lang.IllegalArgumentException: navigation destination ...action_FragmentA_to_DialogFragmentC is unknown to this NavController

I believe the exception is due to program thinks the action is instead from B to C.

class DetailBottomSheet : BottomSheetDialogFragment() {

    @Inject
    lateinit var viewModelFactory: ViewModelProvider.Factory
    private val detailViewModel by activityViewModels<DetailViewModel>() { viewModelFactory }

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {

        binding.bottomSheetAction.setOnClickListener {
            dismiss()
            detailViewModel.setBottomSheetDialogType(1)
        }
    }
}

class DetailFragment : Fragment() {

    @Inject
    lateinit var viewModelFactory: ViewModelProvider.Factory
    private val detailViewModel by activityViewModels<DetailViewModel>() { viewModelFactory }

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        detailViewModel.bottomSheetDialogType.observe(viewLifecycleOwner, Observer {
            when (it) {
                1 -> detailViewModel.navigateToDialogFragmentC(args.id)
                else -> {}
            }
        })

        detailViewModel.navigateToDialogFragmentC.observe(
            viewLifecycleOwner,
            WrapperEventObserver {
                Thread.sleep(500) // thought this would allow wait time for bottomsheet to dismiss, but doesn't make a difference
                val action =
                    DetailFragmentDirections.actionFragmentAToDialogFragmentC(it)
                findNavController().navigate(action)
            })
    }
}

What fixes are available to me navigate properly in this situation?


Solution

  • Calling dismiss() on any DialogFragment asynchronously updates the NavController state - this means that from NavController's perspective, you are still on DetailBottomSheet when your bottomSheetDialogType Observer fires.

    You should instead use findNavController().popBackStack(), which will synchronously update the NavController's state (effectively moving you back to A) and calling dismiss() itself to hide the bottom sheet dialog.

    binding.bottomSheetAction.setOnClickListener {
        findNavController().popBackStack()
        detailViewModel.setBottomSheetDialogType(1)
    }