This is a simple example of a navigation setup that I can't get to work with the Navigation Component library, after researching for some time.
Let's say I have the following screen:
The sticky fragment at the top and the fragment at the bottom are in their own navigation graphs, here is the main_activity.xml:
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment
android:id="#+id/nav_sticky_top"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="50dp"
app:layout_constraintTop_toTopOf="parent"
app:navGraph="#navigation/navigation_graph_sticky_top" />
<fragment
android:id="#+id/navigation_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="0dp"
app:defaultNavHost="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="#id/nav_sticky_top"
app:navGraph="#navigation/navigation_graph" />
</androidx.constraintlayout.widget.ConstraintLayout>
When I press "navigate to sibling fragment" it navigates to the sibling fragment inside the bottom navigation graph, which is correct, the result:
This is the navigation_graph.xml:
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/navigation_graph.xml"
app:startDestination="#id/blankFragment1">
<fragment
android:id="#+id/blankFragment1"
android:name="com.example.myapplication.BlankFragment1"
android:label="fragment_blank_fragment1"
tools:layout="#layout/fragment_blank_fragment1">
<action
android:id="#+id/action_blankFragment1_to_siblingFragment"
app:destination="#id/siblingFragment" />
</fragment>
<fragment
android:id="#+id/siblingFragment"
android:name="com.example.myapplication.SiblingFragment"
tools:layout="#layout/fragment_sibling_fragment" />
</navigation>
Now I would like to implement the "Navigate to fullscreen fragment" button, which should navigate to a fullscreen fragment which is in a separate, third navigation graph and should be above the sticky fragment navigation graph and the navigation graph below that. How can this be properly achieved? By that I mean without hacks like setting the visibility of the top navigation graph fragment to GONE and navigating in the bottom navigation graph to the fullscreen fragment.
UPDATE: I don't recommend this approach any more. For small apps it may work for you, but I ran into complications when trying to pass data between different navigation graphs hosted inside different NavHostFragments. I think the easiest route is the "hack" of hiding fragments in your top level layout to allow a full screen view. You can add an addOnDestinationChangedListener to listen for destinations that you want full screen, and simply hide the fragment required.
ORIGINAL ANSWER:
I do it like this in my app and it works well. You should wrap all your current fragments in a parent fragment with a main_nav_graph.xml. For example:
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment
android:id="#+id/main_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:navGraph="#navigation/main_nav_graph" />
</androidx.constraintlayout.widget.ConstraintLayout>
Then your navigation for full screen fragments will look like this:
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/main_nav_graph.xml"
app:startDestination="#id/main_fullscreen">
<fragment
android:id="#+id/main_fullscreen"
android:name="com.example.myapplication.MainFullscreen" >
<action
android:id="#+id/action_main_fullscreen_to_fullscreen2"
app:destination="#id/fullscreen2" />
</fragment>
<fragment
android:id="#+id/fullscreen2"
android:name="com.example.myapplication.Fullscreen2" />
</navigation>
Where the MainFullscreen has a layout like your ConstraintLayout you provided. Just make sure you add app:defaultNavHost="true" to all your child NavHostFragments.
Then when I want to navigate from your blankFragment1 to fullscreen2, I call this:
Navigation.findNavController(activity!!, R.id.main_host_fragment).navigate(R.id.action_main_fullscreen_to_fullscreen2)
Granted, there's not much point to having navigation graphs if they are not actually connected except via being in the same activity, but I just like the pretty picture of the app the graphs give you in the navigation editor :)
Related
When implementing a Master-Detail Flow with Navigation Components i don't see any possiblity to hide the detail fragment once a navGraph is set. Let's consinder a item in the Master View is selectable and deselectable. When deselcting an Item i cannot remove the StartDestination from the NavGraph.
Consider a Layout like this as the Master-Detail Flow Layout:
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/itemList"
android:layout_width="400dp"
android:layout_height="match_parent"/>
<fragment
android:id="#+id/detailContainer"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:name="androidx.navigation.fragment.NavHostFragment"
app:defaultNavHost="false"/>
</LinearLayout>
As soon as i set a NavGraph with a startDestination to the detailContainer i cannot remove or hide it when i unselect the current selected item. The only solution i found so far was to to hide the container.
I'm trying to implement support for multiple back stacks with a custom BottomNavigationView and not with menu items as with the BottomNavigationView provided by NavigationUI.
I set my custom BottomNavigationView with a NavController acquired by following code snippet
val navHostFragment =
supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
val navController = navHostFragment.navController
bottomNavigationView.setupWithNavController(navController)
and then internally use the NavController in my implementation to navigate to different Fragments which is supposed to have separated backstacks.
According to the documentation Implement support manually with underlying APIs to support multiple back stacks I needed to use
app:restoreState=”true”
app:popUpToSaveState=”true”
in my <action> elements which I have done.
But what I experienced is
Upon app start the AlphaFragment is shown as expected,
Next I'm navigating to BetaFragment with the action navigate_to_beta_from_alpha which also works as expected.
Tried to navigate back to AlphaFragment with the action navigate_to_alpha_from_beta, now things are not working as expected. Nothing seems to happen and BetaFragment is still visible on screen.
Tried to navigate back to BetaFragment again this time the app is crashing with this error.
java.lang.IllegalArgumentException: Navigation action/destination com.example.app:id/navigate_to_beta_from_alpha cannot be found from the current destination Destination(com.example.app:id/betaFragment) label=Beta class=com.example.app.ui.BetaFragment
From the stacktrace it seems that I still in BetaFragment.
If someone can shed a light of why the navigation back to AlphaFragment didn't work would be very helpful.
nav_graph.xml
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/nav_graph"
app:startDestination="#+id/alpha">
<include app:graph="#navigation/alpha"/>
<include app:graph="#navigation/beta"/>
</navigation>
alpha.xml
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/alpha"
app:startDestination="#+id/alphaFragment">
<fragment
android:id="#+id/alphaFragment"
android:name="com.example.ui.AlphaFragment"
android:label="Alpha"
tools:layout="#layout/fragment_alpha"/>
<action
android:id="#+id/navigate_to_beta_from_alpha"
app:destination="#+id/beta"
app:popUpTo="#id/alphaFragment"
app:popUpToSaveState="true"
app:restoreState="true" />
</navigation>
beta.xml
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/beta"
app:startDestination="#+id/betaFragment"\>
<fragment
android:id="#+id/betaFragment"
android:name="com.example.ui.BetaFragment"
android:label="Beta"
tools:layout="#layout/fragment_beta"/\>
<action
android:id="#+id/navigate_to_alpha_from_beta"
app:destination="#+id/alpha"
app:popUpTo="#id/betaFragment"
app:popUpToSaveState="true"
app:restoreState="true" /\>
</navigation\>
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.fragment.app.FragmentContainerView
android:id="#+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="0dp"
android:layout_height="0dp"
app:defaultNavHost="true"
app:layout_constraintBottom_toTopOf="#+id/bottomNavigationView"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navGraph="#navigation/nav_graph" />
<com.example.app.ui.custom.BottomNavigationView
android:id="#+id/bottomNavigationView"
android:layout_width="0dp"
android:layout_height="88dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
I have an app, that has a few fragments in it. I'm using ViewPager 2 to swipe between them.
Lately I've added Navigation component to make a way to get into one specific fragment that is not avilable for user just by swipe.
I've found out, that whever I place FragmentContainerView over the ViewPager2 in my MainActivity layout, I can go to this unvailable fragment, but I can't swipe. Same thing whener I switch FragmentContainerView with ViewPager2. Is there any way to make them both work at the same time?
Here's my layout
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity"
android:orientation="vertical">
<androidx.fragment.app.FragmentContainerView
android:id="#+id/fragmentContainerView"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:navGraph="#navigation/nav_graph" />
<androidx.viewpager2.widget.ViewPager2
android:id="#+id/viewPager"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
They can work together but not like this, at least not for what you want.
Navigation component is made to handle traveling between destinations. ViewPager2 is a widget to be used in a destination, not for traveling between them.
For what you are describing you will need to have FragmentA that will contain ViewPager2 where all of the swiping will take place and handling of fragments (let's say Fragment1, Fragment2, Fragment3) in the ViewPager2.
Those fragments will have nothing to do with the Navigation component.
Navigation component will come to play when you will want to go from FragmentA to FragmentB.
I have a navigation controller fragment defined inside my activity_main that has a match height and width of parent. So when I navigate between fragments, it replaces the entire screen. Now say I navigate to a fragment that loads up a material card. I want that material card to have its own navigation controller that takes up the height and width of the material card. So when I use its navigation controller, it will only replace fragments within the material card view and not change the parent's view.
Is there a way to have two navigation host fragments? So I can define one inside activity_main and then another one that I can define inside the child fragment.
activity_main:
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment
android:id="#+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="0dp"
app:defaultNavHost="true"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="#id/barrier"
app:navGraph="#navigation/base_nav_graph" />
childfragment:
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/fragment_layout"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="600dp"
android:layout_height="match_parent"
android:layout_gravity="end"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent">
<fragment
android:id="#+id/nav_child_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="0dp"
app:defaultNavHost="true"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:navGraph="#navigation/child_nav_graph" />
See advanced sample
https://github.com/android/architecture-components-samples/tree/master/NavigationAdvancedSample
There used multiple navigation controllers for BottomNavigationView items
A similar question was asked before however this seems to be a different issue.
Using the Navigation component, I define a dialog like so:
<navigation
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/main_graph"
app:startDestination="#id/main_fragment">
<fragment
android:id="#+id/main_fragment"
android:name="com.example.app.MainFragment"
android:label="MainFragment" />
<dialog
android:id="#+id/dialog_fragment"
android:name="com.example.app.DialogFragment"
android:label="AddFeeling" />
</navigation>
In the MainFragment I have a button that runs navController.navigate(R.id.dialog_fragment). This opens the dialog fine. When I swipe the dialog fragment away and click the button again, the app crashes.
The crash is caused by including a fragment inside of the dialog. I technically have a NavHostFragment inside of the dialog fragment, but even putting a standard fragment in the app crashes. If I have no fragments inside the dialog it works fine.
Here is the dialog fragment layout. As you can see the <fragment> attribute is where the duplicate is:
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/bottom_sheet_dialog"
android:layout_width="match_parent"
android:layout_height="match_parent"
<androidx.appcompat.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize
app:layout_constraintTop_toTopOf="parent" />
<fragment
android:id="#+id/dialog_nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="0dp"
app:defaultNavHost="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="#id/toolbar"
app:navGraph="#navigation/dialog_graph" />
</androidx.constraintlayout.widget.ConstraintLayout>
Any ideas to what the issue could be? Thanks
Similar to the question linked in mine, the fragment of the NavHostFragment must not have an ID either, the inflater will notice the fragment has an ID so it will flag it as a duplicate.