@ianhanniballake made a good point why this is not entirely possible and why it works the way it works but there is actually a workaround. The workaround is not the most pleasant so keep this in mind. I've listed the drawbacks in it's own section below.
The theory
The workaround involves some "older" Android mechanisms, mechanisms that predate navigation controller (we didn't always have navigation controller). The workaround revolves around a few facts:
- All fragments live inside some
FragmentManager. Navigation controller isn't magic, it still uses FragmentManagers under the hood. In fact you can think of Navigation controller as a wrapper around FragmentManager.
- All fragments come with it's own little
FragmentManager. You may access it via childFragmentManager within the fragment. Any fragments launched in childFragmentManager are considered that fragment's children.
- When a fragment is moved to the "backstack", all of it's children move with it.
- When a fragment is restored, so are it's children.
With these four facts we can formulate a workaround.
The idea is if we show all DialogFragments on a fragment's childFragmentManager then we maintain the ability to navigate to other fragments without any dialog related issues. This is because when we navigate from say FragA to FragC, all of FragA's children is moved to the back stack. Since we launched the DialogFragment using childFragmentManager, the DialogFragment is automatically dismissed as well.
Now when the user moves back to our fragment (FragA), our DialogFragment is shown again because FragA's childFragmentManager is restored too. And our DialogFragment lives inside that childFragmentManager.
The implementation
Now that we know how we will workaround this issue, let's start implementing it.
For simplicity, let's reuse the example you have given. That is we will assume we have fragments FragA and FragC and dialog DialogB.
The first thing is that as nice as Navigation component is, if we want to do this, we cannot use it to launch our dialog. If you use safe args, you can continue to reap it's benefits though since technically safe args isn't tied to Navigation component. Here's an example of launching Dialog B:
// inside FragA
fun onLaunchBClick() {
val parentFragment = parentFragment ?: return
DialogB()
.apply {
// we can still use safe args
arguments = DialogBArgs(myArg1, myArg2).toBundle()
}
.show(parentFragment?.childFragmentManager, "DialogB")
}
Now we can have DialogB launch FragC, but there's a catch. Because we are using childFragmentManager, navigation controller doesn't actually see DialogB. This means that to the navigation controller, we are launching FragC from FragA. This can create an issue here if there are multiple edges to DialogB in the nav graph. The workaround to this is to make all of DialogB's directions global. This is ultimately the downside to this workaround. In this case we can declare a global action to FragC and launch it via
// inside DialogB
fun onLaunchCClick() {
val direction = NavMainDirections.actionGlobalFragC()
findNavController().navigate(direction)
}
The downsides
So there are some obvious downsides to this approach. The biggest one is all fragments the dialog can navigate to should be declared as global actions. The only outlier being if the dialog has exactly 1 edge. If the dialog only has a single edge and it is unlikely a new edge will ever be added, you can technically just add actions to it's only parent fragment instead.
As an example if DialogC can launch FragmentC and FragmentD and DialogC can be launched from FragmentA and FragmentZ (2 edges) then DialogC must use global actions to launch FragmentC or FragmentD.
The other downside is we can no longer use Navigation controller for launching dialog fragments that need to launch other non-dialog fragments. This downside is milder since we can at least still use safe args.
The final downside is that performance might be slightly worse. Consider an example where we have fragment FragA launch DialogB launch FragC. Now if the user taps back, FragA will be restored. But since DialogB is FragA's child, DialogB will also be restored. This means that an extra fragment will need to be loaded and restored, reducing the performance of the back action. In practice this cost should be small as long as your fragment isn't saving a huge amount of state and as long as each fragment does not have too many children.