Android Navigation Graph, Nested Graph Issue - android

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.

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 fragment navigation: how to navigate to an arbitrary fragment programmatically

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.

Android NavController move to another host destination

I have a question regarding to NavController:
There is the scheme (sorry for my painting, hope it helps :D)
I have MainActivity and BottomNavigationView with 3 tabs: A, B, C.
When I tap to A it opens Fragment A1 and there is next button which opens Fragment A2.
In Fragment A2 there are buttons back, next, no problems with navigation here.
The problem is when I need to navigate from Fragment A2 to section B the same like a click on B in BottomNavigationView.
The problem is that it's different graph, how to switch them?
My ideas:
I found work-around: requireActivity().bottomBar.selectedItemId = R.id.graph_b but it's not good idea.
I would like to achieve it using navigation component. I was trying to do findNavController().navigate(R.id.graph_b), but it leads to crash:
java.lang.IllegalArgumentException: navigation destination
com.my.app.staging:id/graph_b is unknown to this NavController
How to make it using NavController ?
There is Google Example project with all architecture:
https://github.com/googlesamples/android-architecture-components/tree/master/NavigationAdvancedSample
And to simplify my question I've added a button in this project, where on click should opens different screen:
You graphs are defined in your Parent Activity and thats where you will be able to control them.
Your first way is actually the solution. Your graphs are defined within your activity. When you are inside any destinations inside your graph A (say A1, A2 etc.) they have no knowledge of the your other graphs B & C. The only way to get to the graph is through parent activity, and hence
requireActivity().bottomBar.selectedItemId = R.id.graph_b
The second way that you have tried will definitely not work because findNavController().navigate(R.id.graph_b) is used when you have nested navigation. In other words graph_b should be inside graph_a which is not your case.
That being said what you can do is just write
requireActivity().bottomBar.selectedItemId = R.id.graph_b
fancier. Instead of running inside your fragments, its better to run inside your activity.
// In your fragment
requireActivity?.moveToGraphB()
// and in your activity
fun moveToGraphB() {
bottomBar.selectedItemId = R.id.graph_b
}
Or more more fancier would be using SharedViewModel which I don't think is necessary.
In graph B you can add the line
where nav_graphA is the name of the graph you want to navigate to when the button is clicked. Then you can add
` <fragment
android:id="#+id/aboutFragment"
android:name="com.mycompany.aboutFragment"
tools:layout="#layout/fragment_about"
android:label="About" >
<action
android:id="#+id/action_aboutFragment_to_nav_graphA"
app:destination="#id/nav_graphA" />
</fragment>
`
to create the action to navigate when the button is clicked.

Pass data back to previous fragment using Android Navigation

I've started using Android Architecture Components (Navigation and Safe Args, View Models) along with Koin library.
Currently, I've got a problem with passing arguments between two fragments - I need to pass a string value from fragment A to fragment B, modify this value in fragment B and pass it back to fragment A.
I've found one possible solution to my problem - shared view models. Unfortunately, this approach has one problem because I can pass and modify values between screens, but when the fragment A navigate to another destination the value in the shared view model is still stored and not cleared.
Is there any different solution of passing and modifying data between fragments in Android Navigation? I want to avoid clearing this one value by hand (when the fragment A is destroyed).
Android just released a solution for this; Passing data between Destinations (Navigation 2.3.0-alpha02), basically, in fragment A you observe changes in a variable and in fragment B you change that value before executing popBackStack().
Fragment A:
findNavController().currentBackStackEntry?.savedStateHandle?.getLiveData<String>("key")?.observe(viewLifecycleOwner) { result ->
// Do something with the result.
}
Fragment B:
navController.previousBackStackEntry?.savedStateHandle?.set("key", result)
navController.popBackStack()
You can use Fragment Result API.
Fragment A -> Fragment B
In Fragment A :
binding.buttonGo.setOnClickListener {
setFragmentResultListener(ADD_LOCATION) { key, bundle ->
clearFragmentResultListener(requestKey = ADD_LOCATION)
val selectedLocationModel =
bundle.getParcelable<LocationModel>(SELECTED_LOCATION_MODEL)
this.selectedLocationModel = selectedLocationModel
}
navToFragmentB()
}
In Fragment B:
setFragmentResult(
ADD_LOCATION,
bundleOf(SELECTED_LOCATION_MODEL to selectedLocationModel)
)
goBack()
Do not forget to call clearFragmentResultListener() before create new one.
Currently, I've got a problem with passing arguments between two fragments - I need to pass a string value from fragment A to fragment B, modify this value in fragment B and pass it back to fragment A.
The theoretical solution really is to have the two fragments in a shared <navigation tag, then scope the ViewModel to the ID of the navigation tag, this way you now share the ViewModel between the two screens.
To make this reliable, it's best to use the NavBackStackEntry of the Navigation tag as both a ViewModelStoreOwner and SavedStateRegistryOwner, and create an AbstractSavedStateViewModelFactory that will create the ViewModel using the ViewModelProvider, while also giving you a SavedStateHandle.
You can communicate the results from FragmentB to FragmentA using this SavedStateHandle, associated with the shared ViewModel (scoped to the shared NavGraph).
You can try this solution
<fragment
android:id="#+id/a"
android:name="...">
<argument
android:name="text"
app:argType="string" />
<action
android:id="#+id/navigate_to_b"
app:destination="#id/b" />
</fragment>
<fragment
android:id="#+id/b"
android:name="...">
<argument
android:name="text"
app:argType="string" />
<action
android:id="#+id/return_to_a_with_arguments"
app:destination="#id/a"
app:launchSingleTop="true"
app:popUpTo="#id/b"
app:popUpToInclusive="true" />
</fragment>
and navigation fragment
NavHostFragment.findNavController(this).navigate(BFragmentDirections.returnToAWithArguments(text))
ianhanniballake`s comment has helped me solve a similar problem
1) Pass string from Fragment A to Fragment B with action_A_to_B and SafeArgs.
2) popBackStack to remove Fragment B.
navController.popBackStack(R.id.AFragment, false);
or
navController.popBackStack();
3) Then pass modified data from B to A with action_B_to_A.
EDIT.
Here you have some another solution

Categories

Resources