I've come across an interesting problem with trying to accomplish dynamic or conditional navigation with the Jetpack Navigation library.
The goal I have in mind is to be able to continue using the nav_graph.xml
to manage the overall navigation graph, but simultaneously allow for conditional navigation based on some factors.
I have included some code below that shows where my solution is headed. The problem is that it inherently requires a lot of maintenance for future conditional logic to work.
I really want the navigateToDashboard
function in the example to be able to be executed with either no parameters, or parameters that rarely change. For instance, instead of passing NavDirections
, maybe passing some identifier that let's the navigateToDashboard
function know which NavDirections
to return.
Code for the class managing the conditional logic.
class DynamicNavImpl(private val featureFlagService: FeatureFlagService) : DynamicNav {
override fun navigateToDashboard(navDirectionsMap: Map<Int, NavDirections>): NavDirections {
val destinationIdRes = if (featureFlagService.isDashboardV2Enabled()) {
R.id.dashboardV2Fragment
} else {
R.id.dashboardFragment
}
return navDirectionsMap[destinationIdRes] ?: handleNavDirectionsException(destinationIdRes)
}
private fun handleNavDirectionsException(destinationIdRes: Int): Nothing {
throw IllegalStateException("Destination $destinationIdRes does not have an accompanying set of NavDirections. Are you sure you added NavDirections for it?")
}
}
Call site examples
navigate(
dynamicNav.navigateToDashboard(
mapOf(
Pair(R.id.dashboardFragment, PhoneVerificationFragmentDirections.phoneVerificationToDashboard()),
Pair(R.id.dashboardV2Fragment, PhoneVerificationFragmentDirections.phoneVerificationToDashboardV2())
)
)
)
navigate(
dynamicNav.navigateToDashboard(
mapOf(
Pair(R.id.dashboardFragment, EmailLoginFragmentDirections.emailLoginToDashboard()),
Pair(R.id.dashboardV2Fragment, EmailLoginFragmentDirections.emailLoginToDashboardV2())
)
)
)
Looking at the call site, you could see how this could be problematic. If I ever want to add a new potential destination, let's say dashboardV3Fragment
, then I'd have to go to each call site and add another Pair
.
This almost defeats the purpose of having the DynamicNavImpl
class. So this is where I am stuck. I want to be able to encapsulate the various variables involved in deciding what destination to go to, but it seems with how NavDirections
are implemented, I'm not able to.
I went between a few different approaches, and I landed on something that still doesn't feel ideal, but works for my use case.
I completely abandoned the idea of using a central dynamic navigation manager. Instead, I decided on having a "redirect" or "container" Fragment
that decides what Fragment
to show.
So here's the new code inside of the DashboardRedirectFragment
childFragmentManager.beginTransaction().replace(
R.id.dashboard_placeholder,
if (featureFlagService.isDashboardV2Enabled()) {
DashboardV2Fragment.newInstance()
} else {
DashboardFragment.newInstance()
}
).commit()
The way I'm using this is by registering a new destination in my nav graph called dashboardRedirectFragment
, and anything in the graph that needs access to the dashboard use the dashboardRedirectFragment
destination.
This fully encapsulates the dynamic navigation logic in the redirect Fragment
, and allows me to continue using my nav graph as expected.