问题
I'm having a problem when dealing with multiple NavHosts. This issue is very similar to the one asked here. I think the solution for this question would help me as well, but it's a post from 2017 and it still has no answer. Android Developers Documentation doesn't help and searching through the web shows absolutely nothing that could possibly help.
So basically I have one Activity and two Fragments. Let's call them
FruitsActivity, FruitListFragment, FruitDetailFragment, where FruitsActivity has no relevant code and its xml layout is composed by a <fragment>
tag, serving as NavHost
, like that:
<fragment
android:id="@+id/fragmentContainer"
android:name="androidx.navigation.fragment.NavHostFragment"
app:navGraph="@navigation/fruits_nav_graph"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
The FruitListFragment is the startDestination of my NavGraph, it handles a list of fruits that will come from the server. The FruitDetailFragment shows details about the Fruit selected in the list displayed by FruitListFragment.
So far we have Activity -> ListFragment -> DetailFragment.
Now I need to add one more Fragment, called GalleryFragment. It's a simple fragment that displays many pictures of the Fruit selected and it's called by FruitDetailFragment when clicking a button.
The problem here is: In Portrait mode I simply use findNavController().navigate(...)
and I navigate through the Fragments like I want. but when I'm using a Tablet in Landscape mode, I'm using that Master Detail Flow to display List and Details on the same screen. There is an example of how it works here, and I want the GalleryFragment to replace the FruitDetailFragment, sharing the screen with the list of fruits, but so far I could only manage to make it replace the "main navigation" flow, occupying the entire screen and sending the FruitListFragment to the Back Stack.
I already tried to play around with findNavController()
method, but no matter from where I call it I can only get the same NavController
all the time, and it always navigates in the same linear way.
I tried to implement my own NavHost, but I get and error "class file for androidx.navigation.NavHost not found".
This is the xml of my FruitsActivity:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment
android:id="@+id/fragmentContainer"
android:name="androidx.navigation.fragment.NavHostFragment"
app:navGraph="@navigation/listing_nav_graph"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</FrameLayout>
</layout>
This is the xml of the FruitListActivity in Landscape mode (in portrait mode there is just the RecyclerView):
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvFruits"
android:layout_weight="3"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="8dp"/>
<FrameLayout
android:layout_weight="1"
android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment
android:id="@+id/sideFragmentContainer"
android:name="fruits.example.FruitDetailFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</FrameLayout>
</LinearLayout>
</RelativeLayout>
</layout>
And now I want to call the GalleryFragment and make it replace just the <fragment>
of id 'sideFragmentContainer' instead of the whole screen (instead of replacing the <fragment>
of id fragmentContainer
in the Activity's xml).
I didn't find any explanations of how to handle multiple NavHosts or <fragment>
inside <fragment>
.
So based on that, is it possible to use Navigation Architecture and display a Fragment inside another Fragment? Do I need multiple NavHosts for that, or is there another way?
回答1:
As suggested by jsmyth886, this blog post pointed me to the right direction.
The trick was to findFragmentById()
to get the NavHost
directly from the fragment container (in this case, the one sharing the screen with the rest of the Master Detail screen). This allowed me to access the correct NavController
and navigate as expected.
It's important to create a second NavGraph
too.
So a quick step-by-step:
- Create the main
NavGraph
to make all the usual navigation (how it would work without the Master Detail Flow); - Create a secondary
NavGraph
containing only the possibleDestinations
that the Master Detail fragment will access. NoActions
connecting then, just theDestinations
. - In the main
<fragment>
container, set the attributes like that:
<fragment
android:id="@+id/fragmentContainer"
android:name="androidx.navigation.fragment.NavHostFragment"
app:navGraph="@navigation/main_nav_graph"
app:defaultNavHost="true"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
- The
app:defaultNavHost="true"
is important. Then the Master Detail layout will look like that:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvFruits"
android:layout_weight="3"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="8dp"/>
<FrameLayout
android:layout_weight="1"
android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment
android:id="@+id/sideFragmentContainer"
android:name="androidx.navigation.fragment.NavHostFragment"
app:navGraph="@navigation/secondary_nav_graph"
app:defaultNavHost="false"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</FrameLayout>
</LinearLayout>
</RelativeLayout>
</layout>
- Again, the attribute
app:defaultNavGraph
is important, set it to false here. - In the code part, you should have a boolean flag to verify if your app is running on a Tablet or not (the link provided in the beginning of the answer explains how to do it). In my case, I have it as a
MutableLiveData
inside myViewModel
, like that I can observe it and change layouts accordingly. - If is not tablet (i.e. follows the normal navigation flow), simply call
findNavController().navigate(R.id.your_action_id_from_detail_to_some_other_fragment)
. The main navigation will happen using the mainNavController
; - If is tablet using the Master Detail Flow, you must find the correct
NavHost
andNavController
by finding the<fragment>
that contains it, like that:
val navHostFragment = childFragmentManager.findFragmentById(R.id. sideFragmentContainer) as NavHostFragment
- And finally you can navigate to the Fragment that you want to appear dividing the screen with the rest of the Master Detail screen by calling
navHostFragment.navController.navigate(R.id.id_of_the_destination)
. Notice that here we don't callActions
, we call theDestination
directly.
That's it, simpler than what I thought. Thank you Lara Martín for the blog post and jsmyth886 for pointing me to the right direction!
来源:https://stackoverflow.com/questions/56804449/how-to-handle-multiple-navhosts-navcontrollers