Android fragment navigation: how to navigate to an arbitrary fragment programmatically - android

I am learning to use Android fragment navigation.
Now I can follow the sample to use ID to navigate to a specific target fragment defined in the navigation graph. Like following:
<fragment android:id="#+id/a"
android:name="com.example.myapplication.FragmentA"
android:label="a"
tools:layout="#layout/a">
<action android:id="#+id/action_a_to_b"
app:destination="#id/b"
app:enterAnim="#anim/nav_default_enter_anim"
app:exitAnim="#anim/nav_default_exit_anim"
app:popEnterAnim="#anim/nav_default_pop_enter_anim"
app:popExitAnim="#anim/nav_default_pop_exit_anim"/>
</fragment>
viewTransactionsButton.setOnClickListener { view ->
view.findNavController().navigate(R.id.viewTransactionsAction)
}
However, I have a special use case that I want to programmatically navigate to a destination fragment, which is not defined in the navi graph. How can I achieve it like following:
itemView.setOnclickListener { view ->
val targetFragmentClass = getTargetFragmentClass(view)
//How can I findNavController().navigate() to created targetFragmentClass instance?
}
I checked the document of navigation controller, all versions of its navigate() method need some resource ID which means I have to define the action or destination in the XML. But I want something like the legacy fragment manger APIs, which I can instantiate any fragment class and create a transaction to apply it.
So, my questions is:
Can I use navigation component to navigate to any arbitrary fragment purely programmatically?
If I have to use fragment manager/transaction to deal with this case, can I make it work with the navigation controller together?
Thanks a lot.

Related

How can I distinguish between multiple instances of the same fragment in Android navigation component?

I've created a navigation graph that includes a fragment that includes an action to navigate to another instance of itself for the purposes of setting up a multiple choice question survey like this:
<fragment
android:id="#+id/multipleChoiceFragment"
android:name="com.example.MultipleChoiceFragment"
android:label="MultipleChoiceFragment"
tools:layout="#layout/fragment_multiple_choice_question">
<action
android:id="#+id/action_multipleChoiceFragment_to_multipleChoiceFragment"
app:destination="#id/multipleChoiceFragment" />
<argument
android:name="questionId"
app:argType="string"
app:nullable="false" />
</fragment>
and I have a parent fragment implementation, SurveyFragment, that is responsible for navigating through the instances of MultipleChoiceFragment via the NavController and setting different questionId values for each MultipleChoiceFragment depending on the answers given to previous MultipleChoiceFragment instances.
I can't seem to find a way to have the parent fragment, SurveyFragment, tell which MultipleChoiceFragment instance is currently visible. I am using a single ViewModel for all instances of MultipleChoiceFragment and SurveyFragment, but if I can I'd like all the navigation logic to be contained entirely within SurveyFragment and not split between all the different classes. I'd like to be able to use the questionId value to distinguish between instances. How can I do that?
I'd like to have something setup in the SurveyFragment that looks something like this:
navController.addOnDestinationChangedListener { _, destination, _ ->
when (destination.questionId) {
// do stuff depending on what question has been navigated to
}
}

open a fragment in a different level of hierarchy with backstack in Android Navigation component

I am using Navigation Component in my project and :
I need to open a fragment in a different level of hierarchy, so that the back stack is created properly too
in my nav_graph.xml there is a hierarchy like this:
HomeFragment -> CollocationFragment -> ChapterFragment --[selfNavigate]--> ChapterFragment -> PlayerFragment
as you see one of my fragments navigates to itself and send an arguments each time like this:
<fragment
android:id="#+id/ChapterFragment ">
<action
android:id="#+id/action_chapterFragment_self"
app:destination="#id/ChapterFragment" />
<argument
android:name="chapterID"
app:argType="Long" />
</fragment>
in a nutshell, I need to open PlayerFragment from HomeFragment with an appropriate backstack (the hierarchy mentioned above) .
HomeFragment ---[my back stack]---> PlayerFragment
I know that it seems NavDeepLinkBuilder creates the backstack itself but I have no idea how to create a custom backstack by using Navigation component for my fragments in this case.
eventually I find a way...it's kind of a workaround but it works like a charm:
so, just call navigate function in your desire order to make the hierarchy backstack:
fun openPlayerFromHome(){
findNavController.navigate(R.id.HomeFragment)
findNavController.navigate(R.id.CollocationFragment)
findNavController.navigate(R.id.ChapterFragment,bundleOf("chapterID",id))
findNavController.navigate(R.id.PlayerFragment)
}

Android Navigation Graph, Nested Graph Issue

I hope you're all doing well.
I have a problem about navigation graphs. You can see the structure in the image below;
So we have 2 different navigation graphs; navigation_A and navigation_B.
I need to navigate to Fragment Y and Fragment Z from a fragment in nested graph which is navigation_B.
The problem is;
If I use <include> for navigation_B, I can not use it as NavHost.
If I use different navigation graph and add it as nested graph like above, I can not use navigation destination for Fragment Y or Fragment Z.
I need to use navigation_B as NavHost, also i need to be able to navigate to Fragment Y and Fragment Z. How can I achieve that?
Thank you!
Your navigation graph structure breaks the recommended navigation pattern!
But for your needs, I have a way to workaround.
Method 1:
First, FragmentY and FragmentZ need to have their own deeplinks.
<fragment
android:id="#+id/fragY"
android:name=".FragmentY"
tools:layout="#layout/frag_y">
<deepLink
android:id="#+id/"
app:uri="any-app://internal_navigation_frag_y" />
</fragment>
<fragment
android:id="#+id/fragZ"
android:name=".FragmentZ"
tools:layout="#layout/frag_z">
<deepLink
android:id="#+id/dlink_frag_z"
app:uri="any-app://internal_navigation_frag_z" />
</fragment>
Then, Inside a Fragment that lives in navigation_b. Let's call it FragmentB
// navigate to FramgmentY
val deeplinkY = Uri.parse("any-app://internal_navigation_frag_y")
findNavController().navigate(deeplinkY)
// navigate to FramgmentZ
val deeplinkZ = Uri.parse("any-app://internal_navigation_frag_z")
findNavController().navigate(deeplinkZ)
You can store the string any-app://internal_navigation_frag_y and any-app://internal_navigation_frag_z in string.xml file though.
Check this out: https://developer.android.com/guide/navigation/navigation-navigate#uri
Method 2:
Inside the nested graph navigation_B, define 2 global actions that point to fragmentY and fragmentZ. Since navigation_B is a NavHost, so it will know about FragmentY and FragmnentZ
Check this: https://developer.android.com/guide/navigation/navigation-global-action
When you use nested navigation graph the inner graph should not know anything about the outside graph. In this case graph_B should not know about the existence of a FragmentY or FragmentZ. What you should do is deliver a result from your graph_B to whomever launched it. Then at that point decide whether to go FragmentY or FragmentZ.

Passing an argument between two fragments each of them in a different Navigation Graph

Using Android Navigation Component. I have two Navigation Graphs (each of them with their own flow), lets say navigation graphs A and B. I navigate successfully from A to B, but I can't manage to pass an argument from the last Fragment included in graph A to the start Fragment that belongs to graph B.
I am able to pass arguments between fragments that belong to the same graph, but the function to set the arguments is not been generated when navigating between to navigation graphs.
I am trying to accomplish that using safeargs.
Here is the navigation graphs code:
Navigation Graph A:
<navigation android:id="#+id/nav_graph_a"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
app:startDestination="#id/fragment1">
<fragment
android:id="#+id/fragment1"
android:name="com.mypackage.fragments.Fragment1"
android:label="Fragment1">
<action
android:id="#+id/action_fragment1_to_fragment2"
app:destination="#id/fragment2"/>
</fragment>
<fragment
android:id="#+id/fragment2"
android:name="com.mypackage.fragments.Fragment2"
android:label="Fragment2">
<argument
android:name="thisArgumentAlwaysArrive"
android:defaultValue="null"
app:argType="string"/>
<action
android:id="#+id/action_fragment2_to_nav_graph_b"
app:destination="#id/nav_graph_b"/>
</fragment>
<include app:graph="#navigation/nav_graph_b"/>
Navigation Graph B:
<navigation android:id="#+id/nav_graph_b"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
app:startDestination="#id/fragment3">
<fragment
android:id="#+id/fragment3"
android:name="com.mypackage.fragments.Fragment3"
android:label="Fragment3">
<argument
android:name="thisArgumentNeverArrive"
app:argType="string"/>
</fragment>
Any idea on how to accomplish this?
As stated in the documentation:
Note: Safe Args do not support cross-module navigation, as there is no direct action to the destination. In the previous example, although a Directions class would be generated for the target destination in settings, you aren't able to access the generated class from the classpath of the list module.
But you can use a bundle instead of nav_args like this:
From
FristFragmentDirections.actionFirstFragmentToSecondFragment(your_parameter).let { action ->
findNavController().navigate(action)
}
To
FristFragmentDirections.actionFirstFragmentToSecondFragment().let { action ->
findNavController().navigate(
action.actionId,
Bundle().apply { putString("parameter_key", your_parameter) }
)
}
Safe Args is the plugin for Gradle that creates the classes ClassArgs for the destinations.
The default controller provides a way to navigate to another NavGraph, in this case there is an implementation of a Navigator called NavGraphNavigator.
This navigator provides you with a navigate(NavGraph destination,Bundle args) function that passes the arguments to the start fragment.
So, the best way I have found to do what you are trying to achieve is to use the generated class to generate a bundle with the needed arguments.
var myBundle = YourFragmentIdInNavGraphArgs.Builder(var args..).toBundle()
and then use the default NavController to navigate to a navGraph destination like this.
view.findNavController().navigate(R.id.your_action_to_nav_graph_id,myBundle)
the implementation of the Navigator will take care of passing the arguments to the start fragment on the destination.
Hope it helps to some one.

Using DialogFragment as one of the Fragment in Android NavController

I have a graph defined using XML and I added a DialogFragment as one of the Fragment in my NavGraph. when I call NavController.navigate with resId and bundle on that nav controller I don't see any Dialog being displayed. Is there any way I can use DialogFragment instead of standard Fragment?
<fragment
android:id="#+id/noLoginDialog"
android:name="com.ram.view.NoLoginDialog"
android:label="NoLoginDialog">
<argument
android:name="argTitle"
android:defaultValue="null"
app:type="string"/>
<argument
android:name="argBody"
android:defaultValue="null"
app:type="string"/>
<argument
android:name="argButton"
android:defaultValue="null"
app:type="string"/>
</fragment>
and my action is defined as below
<container_fragment
android:id="#+id/homeFragment"
android:name="com.ram.home.HomeFragment"
android:label="HomeFragment">
<action
android:id="#+id/action_homeFragment_to_noAuthAlertDialog"
app:destination="#id/noLoginDialog"/>
</container_fragment>
My other actions with activity and fragments works just fine.
hum, Navigation Library will never open an Fragment as Dialog, it just replace the first (home destination) to other destination in your NavHostFragment.
Google said:
A destination is any place you can navigate to in your app. While
destinations are usually Fragments representing specific screens..
Please provide more information, like your navigation code (java/kotlin).
Take a read on this official Google post to understand more about Navigation:
https://developer.android.com/topic/libraries/architecture/navigation/navigation-implementing
Yes you can call up DialogFragment from navigation, you will just have to explicitly tell the navigation that you are going to use dialog
<dialog
android:id="#+id/simCancelFinish"
android:name="mk.telekom.kiosk.ui.dialogs.SimCancelFinish"
android:label="SimCancelFinish" >
<action
android:id="#+id/action_simCancelFinish_to_stornoSimFragment"
app:destination="#id/stornoSimFragment"
app:popUpTo="#id/stornoSimFragment"
app:popUpToInclusive="true" />
</dialog>

Categories

Resources