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())
Related
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!
I have created my NavGraph using the Kotlin DSL and everything is fine. But I'm struggling to pass a simple argument between destinations.
I'm folowing this Android Docs without success: https://developer.android.com/guide/navigation/navigation-kotlin-dsl#constants
Part of graph that adds the argument as the docs says:
fragment<RestaurantsTabsFragment>(
"${CampusSelectorDestinations.restaurantsTabsFragment}/" +
CampusSelectorArguments.campusId
) {
argument(CampusSelectorArguments.campusId) {
type = NavType.StringType
defaultValue = "test"
}
}
Code with the navigation action trying to pass a argument:
campusesAdapter.onCampusClick = { campusId ->
findNavController().navigate("${CampusSelectorDestinations.restaurantsTabsFragment}/" + campusId
}
Error I get:
IllegalArgumentException: Navigation destination that matches request NavDeepLinkRequest{ uri=android-app://androidx.navigation/restaurantsTabsFragment/jCkuLbzRHtW0CUzDFWYw } cannot be found in the navigation graph NavGraph
Can anyone help me? I can provide more information if needed
The pattern to pass the argumet route is wrong at the docs:
For luck, I've found this explanation inside a Navigation Lib class and that solved my problem (after 2 days struggling):
...
In addition to a direct Uri match, the following features are supported:
Uris without a scheme are assumed as http and https. For example, www.example.com will match http://www.example.com and https://www.example.com. Placeholders in the form of {placeholder_name} matches 1 or more characters. The String value of the placeholder will be available in the arguments Bundle with a key of the same name. For example, http://www.example.com/users/{id} will match http://www.example.com/users/4. The .* wildcard can be used to match 0 or more characters.
These Uris can be declared in your navigation XML files by adding one or more elements as a child to your destination.
...
Hope someone from Google see this and fixes the docs. (or explain if I'm wrong)
Just put your arguments into curved breaks and separate them by slash as it shown in the example below.
Define your destination with all required argument:
fragment<TransactionFragment>("${MainNavRoute.transaction}/{arg1}/{arg2}") {
argument("arg1") {
type = NavType.StringType
}
argument("arg2") {
type = NavType.LongType
}
Navigation to the destination:
findNavController().navigate("${MainNavRoute.transaction}/string_value/2")
Also, I have reported an issue to the tracker too. https://issuetracker.google.com/issues/221895357
I've created multiple graphs in separate files.
The first one is GraphA and it's includes GraphB. the startDestination of GraphB is FragmentB that has an argument named AnID
Now I want to pass the AnID from GraphA to GraphB (FragmentB)
Despite of the editor know about the argument but generated codes don't regard the argument:
val directionB = FragmentADirections.actionFragmentAToGraphB(/* HAS NOT ARGUMENT */)
findNavController().navigate(directionB)
How can I pass the argument to a nested graph?
I found this but it's not an official solution:
val direction = FragmentADirections.actionFragmentAToGraphB()
findNavController().navigate(direction.actionId, FragmentBArgs(anId).toBundle())
Update:
someone introduced another way by defining nested-graph arguments in action
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.
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.