Search code examples
javaandroidandroid-dialogfragmentandroid-architecture-navigation

Show DialogFragment in navBackStackEntry observer result of another DialogFragment using NavigationComponent


This is onViewCreated of My Fragment:

@Override
public void onViewCreated(@NonNull View view, @Nullable @org.jetbrains.annotations.Nullable Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);

    NavHostFragment navHostFragment = (NavHostFragment) requireActivity().getSupportFragmentManager().findFragmentById(R.id.container);

    binding.button.setOnClickListener(v -> {
        if (navHostFragment != null) {
            NavController navController0 = navHostFragment.getNavController();

            navController0.navigate(MyFragmentDirections.actionMyFragmentToMessageDialog(
                    false,
                    R.drawable.ic_success,
                    "My Message 1",
                    R.string.yes,
                    R.string.no,
                    R.drawable.circle_yellow
            ));
        }
    });

    if (navHostFragment != null) {
        NavController navController1 = navHostFragment.getNavController();
        NavBackStackEntry navBackStackEntry = navController1.getCurrentBackStackEntry();
        if (navBackStackEntry != null) {
            SavedStateHandle savedStateHandle = navBackStackEntry.getSavedStateHandle();
            MutableLiveData<String> liveData = savedStateHandle.getLiveData(Const.DIALOG_BTN_DATA);
            liveData.observe(getViewLifecycleOwner(), btnData -> {
                //            savedStateHandle.remove(Const.DIALOG_BTN_DATA);

                if (btnData.equals("btn_right")) {
                    Log.i(TAG, "Dialog Btn Clicked -----> btn_right");
                    NavController navController2 = navHostFragment.getNavController();
                    navController2.navigate(MyFragmentDirections.actionMyFragmentToMessageDialog(
                            false,
                            R.drawable.ic_success,
                            "My Message 2",
                            R.string.yes,
                            R.string.no,
                            R.drawable.circle_red
                    ));

                } else {
                    // Do Some Stuff
                }
            });
        }
    }
}

And this is the button onClickListener of Dialog:

binding.btnRight.setOnClickListener(v -> {
    NavHostFragment navHostFragment = (NavHostFragment) requireActivity().getSupportFragmentManager().findFragmentById(R.id.container);
    if (navHostFragment != null) {
        NavController navController = navHostFragment.getNavController();
        NavBackStackEntry navBackStackEntry = navController.getPreviousBackStackEntry();
        if (navBackStackEntry != null) {
            SavedStateHandle savedStateHandle = navBackStackEntry.getSavedStateHandle();
            savedStateHandle.set(Const.DIALOG_BTN_DATA, "btn_right");
        }
        navController.navigateUp();
    }
});

I need to show DialogFragment when another DialogFragment result received, but app crashes in statement navController2.navigate(MyFragmentDirections.actionMyFragmentToMessageDialog... and I get the below error:

java.lang.IllegalArgumentException: Navigation action/destination com.example.app:id/action_myFragment_to_messageDialog cannot be found from the current destination

Can anyone help me to solve the problem and show DialogFragment in navBackStackEntry observer result of another DialogFragment using NavigationComponent?


Solution

  • Your issue is not related to usage of navController. You can find its definition in Navigation Component documents.

    So I avoid to describe them and want to focus on your code.

    Action X to Y means you want navigate to Y only from X. In other words the last entry/destination in back-stack must be X.

    1.

    In your case when MyFragment is shown, the back-stack is like:

    [ MyFragment ]
    

    and when you call getSavedStateHandle() and start observing its savedState means you're obsrving MyFragment's savedState.

    2.

    When you want to launch dialog with "My Message 1" in button click listener, back-stack is still:

    [ MyFragment ]
    

    and after launching the dialog will be like:

    [ MyFragment, MessageDialog ]
    

    So when you getPreviousBackStackEntry in dialog, it refers to MyFragment and successfully set(Const.DIALOG_BTN_DATA, "btn_right") on MyFragment's savedState.

    At this time the observer in MyFragment will be triggered and try to launch dialog via actionMyFragmentToMessageDialog with "My Message 2".

    This action is from MyFragment to MessageDialog but the latest entry in back-stack is MessageDialog (Not MyFragment). BOOOM

    and navController.navigateUp() will never invoke to pop dialog :D


    show DialogFragment in navBackStackEntry observer result of another DialogFragment using NavigationComponent?

    Your issue comes from restriction of local actions. So you should navigate by destination-ids or deeplinks or better one is GlobalActions that provide safe-arg facilities.

    I suggest global actions in this situation:

    <action
        android:id="@+id/action_global_messageDialog"
        app:destination="@id/messageDialog" />
    
    navController2.navigate(
        MyGraphDirections.actionGlobalMessageDialog(
            false,
            R.drawable.ic_success,
            "My Message 2"
            R.string.yes,
            R.string.no,
            R.drawable.circle_red
        ));