What we try to achieve is when a user taps a notification, he/she should be redirected to a certain fragment. That destination fragment is marked as startDestination in the graph (it has its own .xml file).
To create the PendinIntent for this, we use the NavDeepLinkBuilder like this:
// `this` represents the notification service.
NavDeepLinkBuilder(this)
.setGraph(R.navigation.destination_graph)
.setDestination(R.id.start_destination)
.createPendingIntent()
where R.id.start_destination is the id of the fragment specified in app:startDestination attribute of the root navigation tag in the R.navigation.destination_graph a.k.a. the start destination of that graph.
If we try to use this, we get the following error. If we use any other fragment other than the one marked as startDestination, it works seamlessly.
Our graph is this.
What could be the actual case and what's a workaround?
EDIT: If the graph is just like the above one, but it's missing its android:id attribute and it's not included in the BottomNavigationView, the error changes to Caused by: java.lang.IllegalStateException: unknown destination during deep link: 0 (line 3)
EDIT2: The above scenario, but the graph has an ID this time and is not used in any BottomNavigationView. The error becomes Caused by: java.lang.IllegalStateException: unknown destination during deep link: test.ourapp.app:id/messages
Related
This is the setup:
View-based Android UI
Using androidx.navigation library (tested with versions 2.4.1 and 2.5.0-beta01)
Activity, consisting of a bottom bar and a NavHostFragment
What's special is that the navigation structure is defined by the server. The top-level destinations are available during Activity.onCreate(). Other detail screens (=children of the top-level destinations) are only known when performing more server calls while navigating down in the hierarchy.
This is an excerpt of the body of Activity.onCreate():
val navController = findNavController(R.id.navigationHostFragment)
val topLevelDestinations = getTopLevelDestinations()
// some additional destinations are defined statically in navigation.xml (e.g. settings)
val staticNavGraph = navInflater.inflate(R.navigation.navigation)
graph = staticNavGraph.apply {
setStartDestination(topLevelDestinations.first())
addDynamicDestinations(staticNavGraph, topLevelDestinations)
}
This code works initially. The NavGraph contains the top-level destinations.
Some top-level destinations offer navigation to children elements. These destinations are added to the NavGraph in a just-in-time manner, i.e. just before navigate() is called.
When the user navigated to a detail screen, the app process is killed and the app is re-opened, then onCreate() is called again and the app crashes during setGraph()/graph = with the following error:
java.lang.IllegalStateException: Restoring the Navigation back stack failed: destination -1178236840 cannot be found from the current destination Destination(0x1a356ec2) ...
at androidx.navigation.NavController.onGraphCreated(NavController.kt:1128)
at androidx.navigation.NavController.setGraph(NavController.kt:1086)
at androidx.navigation.NavController.setGraph(NavController.kt:100)
To solve this I'd be fine with either of these options:
Find a way, the entire NavGraph is saved persistently and restored
Prevent NavController from trying to recover the last destination, but show the initial start destination again.
Regarding 2. I tried calling navController.popBackStack(startDestination, false) and navController.clearBackStack(startDestination) before setGraph() is called but this doesn't seem to have the desired effect.
How can I have a navGraph without a startDestination?
I have a BottomSheet fragment container. When some events are triggered I want to expand this BottomSheet and load a fragment to its fragment container. Until one of those events are triggered I don't want the fragment container to have any fragment loaded. I want it to be empty.
But if don't supply a valid startDestination to the navGraph the app crashes with the exception:
IllegalStateException: no start destination defined
Is this possible? What would be the best way to handle this?
EDIT
I know I can supply a startDestination programmatically. But this is not a graceful approach. It introduces clutter code that needs to be executed when the first fragment load needs to happen and it gets worse when that fragment needs arguments.
The goal is to skip supplying a startDestination altogether.
First solution:
Create empty fragment and set it as startDestination in graph
Second solution:
Wait until event triggered and open BottomSheet with startDestination argument. Read argument of BottomSheet and change start destination
navGraph.setStartDestination(R.id.fragment2)
Set a default startDestination in your navgraph. For me, for example, it is the destination I usually want to start with (R.id.trips_fragment)
Then, in my activity onCreate:
private fun setupMainNavigationGraph() {
val navController = findNavHostFragment().navController
val mainNavigationGraph = navController.navInflater.inflate(R.navigation.main_graph)
mainNavigationGraph.setStartDestination(intent.getIntExtra(START_DESTINATION_INTENT_EXTRA, R.id.trips_fragment))
navController.graph = mainNavigationGraph
}
This way, if I want to use a custom start destination, I pass the start destination ID as an Intent to the activity, and the graph is loaded with the overridden start destination.
Note, the line here
intent.getIntExtra(START_DESTINATION_INTENT_EXTRA, R.id.trips_fragment)
R.id.trips_fragment is the default value.
I use this for integration tests to override the start destination.
I've multi-modules setup with Navigation Component with multiple navigation graphs which all have been included in the main app's navigation graph file. But when I try to navigate to one of the children within another module navigation graph this error is being thrown.
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.example.app, PID: 26724
java.lang.IllegalArgumentException: Navigation action/destination com.example.app:id/singleTranslationHadithDetailDialog cannot be found from the currentdestination Destination(com.example.app:id/duaDetailListFragment) label=DuaDetailListFragment class=com.example.app.ui.fragments.dua.DuaDetailListFragment
at androidx.navigation.NavController.navigate(NavController.kt:1216)
at androidx.navigation.NavController.navigate(NavController.kt:1149)
at androidx.navigation.NavController.navigate(NavController.kt:1131)
As per official documenentation:
Nested graphs have their own start destination and don’t expose their child destinations separately.
I'm trying the DeepLink solution (and some place I already have DeepLink implemented), but the problem is the targeted destination (action/destination com.example.app:id/singleTranslationHadithDetailDialog) I'm trying to go has params with an array of integer, How can add integer[] in DeepLink Uri path parameter like following:
<argument
android:name="references"
app:argType="integer[]"
app:nullable="false" />
Now, what should I do? How can I overcome this situation?
Thanks.
I need to check if an nav graph has deeplink and NavGraph provides hasDeeplink() method that I want to use. But I need this information outside activity or fragment. Is there any way to init NavGraph with navigation id resource outside fragment/activity?
I tried some options to get NavGraph instance but every ends with an exception.
Thanks
I need to set the start destination of a navigation graph programmatically depending on a condition. I have Fragment0, and also Fragment1, Fragment2, ... FragmentN all extending Fragment0, and being all of them (including Fragment0) able to be that wished start destination.
I already know there is the method from NavGraph called
setStartDestination(R.id.nav_fragment_X);
but it is not useful for me because, if I am not wrong, it requires to have all these fragment0...N nodes declared in the xml navigation file.
So, what I would like to, is to have just one fragment node in the xml file and be able to set, programmatically, the Class (a fragment in my case) that in normal cases you can indicate via the design/text tabs of android studio and is displayed like this:
android:name="com.android.fragments.FragmentX"
I finally ended up with the answer:
First you get your startDestination graph by, for instance, executing:
NavDestination startDestNode = navGraph.findNode(R.id.start_dest);
And, later, you update the class name by getting use of:
((FragmentNavigator.Destination) startDestNode).setClassName("Class name you wish");
In conclusion, the method setClassName(String className) of the class FragmentNavigator.Destination allows you to indicate the fragment class that will be displayed associated with the destination.