Android - NavGraph without a startDestination - android

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.

Related

NavGraph with dynamic destinations - restore after process kill

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.

Android Nav graph check has deeplink without navHostFragment

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

How to kill all fragments created by a NavHostFragment?

I have 2 navigation files, and in my Activity, 2 fragments. One of the navigations is always shown inside one of the fragments, but I show the other one only when I need it.
The way they're drawn is the always showing fragment is inside a relativeLayout, and the other fragment is inside the same relativeLayout with it's visibility set as gone. When I need the second navigation, I set the visibility to visible and when I don't need it, I set it to gone again.Visually this works well, but what I want to accomplish is that when I don't want the second navigation, I want to completely kill it and redraw it the next time I need it.
What I've done so far was to get a hold of the NavHostFragment used to start the navigation, and when I dont need it anymore, call popBackStack() on it's navController, but it doesn't work:
val navHost: NavHostFragment? = null
fun createSecondNav() {
navHostLogin = NavHostFragment.create(R.navigation.navigation_second)
theFragment.visibility = View.VISIBLE
supportFragmentManager.beginTransaction()
.replace(R.id.theFragment, navHostLogin!!)
.commit()
}
fun killSecondNav() {
theFragment.visibility = View.GONE
navHostLogin?.navController?.popBackStack() // returns false
navHostLogin = null
}
So how can I completely kill the fragments created by the second navHost?
NavController maintains it's own back-stack, independent form the FragmentManager back-stack.
And popBackStack() without arguments only pops that back-stack once:
Attempts to pop the controller's back stack. Analogous to when the user presses the system Back button when the associated navigation host has focus.
While popBackStack(int destinationId, boolean inclusive) reads:
Attempts to pop the controller's back stack back to a specific destination.
destinationId int: The topmost destination to retain
inclusive boolean: Whether the given destination should also be popped.
So this should be:
navController.popBackStack(R.id.startDestination, true)
I'd wonder why even using two NavController, because one can set the graph at run-time with setGraph(NavGraph graph, Bundle startDestinationArgs):
Sets the navigation graph to the specified graph.
Any current navigation graph data (including back stack) will be replaced.

findNavController and isolated fragment

I launch a BottomSheetDialogFragment from the main fragment with the show function (navigation component can't start DialogFragments).
Now in the BottomSheetDialogFragment I have a button to move to a detail activity.
I have the BottomSheetDialogFragment defined in the graph (isolated) and it points to the detail activity.
But when I try to navigate, it can't find the navController. Is it possible to pass the navController to this isolated fragment?
MainFragment to Detail is working
DialogFragment to Detail is not working.
I tried:
- findNavController: navigation is not set
- activity.findNavController(...)
But when I try to navigate, it can't find the navController. That's how navigation-component work.
Is it possible to pass the navController to this isolated fragment? I hope you will not going with that, replacing navController is not the perfect solution for your case.
What to do?
You can have new nav.xml with new parent activity and the (isolated) fragment as child, and navigate from the BottomSheetDialogFragment to (isolated) fragment-activity.
Otherwise, I don't see any problems that prevent you from adding related fragments in one nav.xml.
Also you may need to obey for navigation-component contract, don't use show() function while using navigation-component, you may miss some advantages here!
You really don't need show function see:-
Android Activity as a dialog
Explanation:-
You can have a parent activity and set it's theme as Dialog, so all fragments (inside the nav.xml) will be dialogs..
I used this trick in one of my apps before.

OnDestinationChangeListener does not work for activity destinations

I am attaching an OnDestinationChangedListener to my NavController in my Activity.
It works fine for all fragment destinations. However when I navigate to an activity destination my listener is not notified of the change.
override fun onStart() {
super.onStart()
navFragment.findNavController().addOnDestinationChangedListener { controller, destination, arguments ->
Log.e("Navigation", "id: ${destination.id} label: ${destination.label}")
}
}
How can I listen destination changes for activity destinations as well?
Documentation says, that:
The Navigation component is designed for apps that have one main
activity with multiple fragment destinations. The main activity is
associated with a navigation graph and contains a NavHostFragment that
is responsible for swapping destinations as needed. In an app with
multiple activity destinations, each activity has its own navigation
graph.
So for now I think that you have to keep track on your activities by yourself. :-( Navigation graphs seem to be "encapsulated" inside exactly one activity.

Categories

Resources