Android Nav graph check has deeplink without navHostFragment - android

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

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 - NavGraph without a startDestination

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.

Kotlin opening new fragment in nav drawer example

I'm relatively new with Kotlin and I'm trying to extend the navigation drawer example by adding a button into my fragment that can open another fragment directly without having to select it from the navigation drawer.
Here is my button listener in my fragment's onCreateView function:
addButton.setOnClickListener()
{
Toast.makeText(this.context,"ButtonPressed",Toast.LENGTH_SHORT).show()
this.activity.supportFragmentManager.beginTransaction().replace(R.id.mobile_navigation, R.layout.fragment2).commit()
}
I'm not sure I completely understand how to approach this. The button works fine and calls the toast, but I can't get it to change the fragment.
Any help would be appreciated.
the template has a mobile_navigation.xml file with a "navigation" element.
The Navigation Architecture components is used by default in recent android studio versions, so the navController is the responsible for fragment transaction instead of doing that manually by the supportFragmentManager
addButton.setOnClickListener() {
Toast.makeText(this.context,"ButtonPressed",Toast.LENGTH_SHORT).show()
val navHostFragment = requireActivity().supportFragmentManager
.primaryNavigationFragment as NavHostFragment
navHostFragment.navController.navigate(R.layout.fragment2)
}
Also, make sure that R.layout.fragment2 is the fragment id of the destination fragment in the R.id.mobile_navigation.xml

NavDeepLinkBuilder: deeplink to startDestination

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

Navigation Architecture Component - Activities

I've been following the docs from Navigation Architecture Component to understand how this new navigation system works.
To go/back from one screen to another you need a component which implements NavHost interface.
The NavHost is an empty view whereupon destinations are swapped in and
out as a user navigates through your app.
But, it seems that currently only Fragments implement NavHost
The Navigation Architecture Component’s default NavHost implementation is NavHostFragment.
So, my questions are:
Even if I have a very simple screen which can be implemented with an Activity, in order to work with this new navigation system, a Fragment needs to be hosted containing the actual view?
Will Activity implement NavHost interface in a near future?
--UPDATED--
Based on ianhanniballake's answer, I understand that every activity contains its own navigation graph. But if I want to go from one activity to another using the nav component (replacing "old" startActivity call), I can use activity destinations. What is activity destinations is not clear to me because the docs for migration don't go into any detail:
Separate Activities can then be linked by adding activity destinations to the navigation graph, replacing existing usages of startActivity() throughout the code base.
Is there any benefit on using ActivityNavigator instead of startActivity?
What is the proper way to go from activities when using the nav component?
The navigation graph only exists within a single activity. As per the Migrate to Navigation guide, <activity> destinations can be used to start an Activity from within the navigation graph, but once that second activity is started, it is totally separate from the original navigation graph (it could have its own graph or just be a simple activity).
You can add an Activity destination to your navigation graph via the visual editor (by hitting the + button and then selecting an activity in your project) or by manually adding the XML:
<activity
android:id="#+id/secondActivity"
android:name="com.example.SecondActivity" />
Then, you can navigate to that activity (i.e., start the activity) by using it just like any other destination:
Navigation.findNavController(view).navigate(R.id.secondActivity);
I managed to navigate from one activity to another without hosting a Fragment by using ActivityNavigator.
ActivityNavigator(this)
.createDestination()
.setIntent(Intent(this, SecondActivity::class.java))
.navigate(null, null)
I also managed to navigate from one activity to another without hosting a Fragment by using ActivityNavigator.
Kotlin:
val activityNavigator = ActivityNavigator( context!!)
activityNavigator.navigate(
activityNavigator.createDestination().setIntent(
Intent(
context!!,
SecondActivity::class.java
)
), null, null, null
)
Java:
ActivityNavigator activityNavigator = new ActivityNavigator(this);
activityNavigator.navigate(activityNavigator.createDestination().setIntent(new Intent(this, SecondActivity.class)), null, null, null);
nav_graph.xml
<fragment android:id="#+id/fragment"
android:name="com.codingwithmitch.navigationcomponentsexample.SampleFragment"
android:label="fragment_sample"
tools:layout="#layout/fragment_sample">
<action
android:id="#+id/action_confirmationFragment_to_secondActivity"
app:destination="#id/secondActivity" />
</fragment>
<activity
android:id="#+id/secondActivity"
android:name="com.codingwithmitch.navigationcomponentsexample.SecondActivity"
android:label="activity_second"
tools:layout="#layout/activity_second" />
Kotlin:
lateinit var navController: NavController
navController = Navigation.findNavController(view)
navController!!.navigate(R.id.action_confirmationFragment_to_secondActivity)

Categories

Resources