I have a multi module project that uses Navigation Component to navigate between Fragments. This means that in order to get to another module, I have to launch a DeepLink using findNavController().navigate(NavDeepLinkRequest, NavOptions). When it comes time to log out of the application, I need to pop the back stack inclusive to a Destination in another module that is not visible to that module. How do I achieve this?
I had exactly the same issue and I believe Google doesn't support it by default using Navigation Component (which is sad). However I managed to do it in a bit hacky way (but it works) using old friend getIdentifier.
In your Fragment you may have a navigation request like this:
val navigateToOtherModuleRequest = NavDeepLinkRequest.Builder
.fromUri("yourapp://othermoduledestination".toUri())
.build()
Then get the resource id you need to pop-up to using getIdentifier() (it can be Fragment id or in my case nav graph id):
val homeNavGraphResourceId = resources.getIdentifier(
"home_nav_graph",
"id",
requireContext().packageName
)
Define navigationOptions like this:
val navigationOptions = NavOptions.Builder()
.setPopUpTo(
destinationId = homeNavGraphResourceId,
inclusive = true
)
.build()
And navigate using
findNavController().navigate(
navigateToOtherModuleRequest,
navigationOptions
)
Hope this will help!
Related
I'm working on navigation component architecture and I face problem in how to set start destination after I authenticate user since I have two type of users :admin and user and I made my app contains 4 activities
loginActivity ,signUpActivity userMainActivity : that use navigation component and contain all fragments related to user
and adminMainActivity :that use navigation component and contain all fragments related to admin
I'm not able to figure out how to handle this and start user or admin activity after login by set default
should I put user and admin in separate navigation graph or what to in this case
you can set start destination programmatically, just like:
val navHostFragment = nav_host_fragment as NavHostFragment
val graphInflater = navHostFragment.navController.navInflater
navGraph = graphInflater.inflate(R.navigation.nav_graph)
navController = navHostFragment.navController
val destination = if (intent.getBooleanExtra(IS_ADMIN,false))
R.id.adminhomeFragment
else R.id.userhomeFragment
navGraph.startDestination = destination
navController.graph = navGraph
For debugging purposes I need to know which Fragments (I need the class names like MyCoolFragment) are on the back stack and in which order they are on the back stack. How can I do that when using the Navigation component?
I hoped for something like this:
findNavController().backStack.forEach {
// print it.toString()
}
but when I try to use this, Android Studio tells me
So, how can I watch what's on the back stack? I am currently working with 2.3.0-alpha04, in case it matters.
For debugging purposes, you can ignore the lint error and list non-NavGraph destinations on the back stack.
val breadcrumb = navController
.backStack
.map {
it.destination
}
.filterNot {
it is NavGraph
}
.joinToString(" > ") {
it.displayName.split('/')[1]
}
// e.g. first_fragment > second_fragment > third_fragment
That isn't available at runtime. It is only possible when using the TestNavHostController class as part of the navigation-testing artifact as a way of verifying your back stack as part of a test.
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.
I want to inject a new destination to the current nav graph.
I notice NavGraph has a method void addDestination(#NonNull NavDestination node) but I can't find a proper way to create a NavDestination and navigate to it using navController.navigate(R.id.new_dest_id).
I've got two working ways:
navController.graph.addDestination(ActivityNavigator(this).createDestination().apply {
id = R.id.new_dest
setComponentName(ComponentName(context, NewActivity::class.java))
// or setIntent
})
or this
navController.graph.addDestination(
navController.navigatorProvider.getNavigator(ActivityNavigator::class.java)
.createDestination().apply {
id = R.id.new_dest
setComponentName(ComponentName(context, NewActivity::class.java))
}
)
There is also a DSL builder ActivityNavigatorDestinationBuilder.
Fragment is similar. Change ActivityNavigator to FragmentNavigator and use different setters.
I also made a mistake when I added a destination in one graph and tried to navigate to the new destination in another graph. of course that never work.
Did you try to pass the ID of your newly created Destination in navigate(), like navigate(myDes.getId())
Looking for examples or anything similar that takes Swift (iOS) code like this:
let navController = UINavigationController(rootViewController: initialView)
and sets it up in Kotlin, via the new Navigation component. I've referenced the following examples, but it's not making complete sense to me:
val myNavHostController: NavHostFragment = nav_host_fragment as NavHostFragment
val inflater = myNavHostController.navController.navInflator
val graph = inflater.inflate(R.layout.nav_graph)
myNavHostController.navController.graph = graph
and
val finalHost = NavHostFragment.create(R.navigation.example_graph)
supportFragmentManager.beginTransaction()
.replace(R.id.nav_host, finalHost)
.setPrimaryNavigationFragment(finalHost) // this is the equivalent to app:defaultNavHost="true"
.commit()
It appears the Android examples I'm coming across, still require an Activity/Fragment to already be established in the XML file created by the navigation component ... but, what if I want this functionality to be dynamic? What if I need to set the 'host' activity for the nav component based on data passed in? I am in need of the ability to do this all via code, which is what the Swift line is doing (setting the 'initialView' UIViewController, as the 'host', which has the navigation controller embedded in it). None of it is done via storyboarding, which seems to be what Android wants me to do regardless...
I am certain the issue is me not fully understanding how this works in Android, which is what I really would like to learn.