I am using BottomNavigation.
The transition from the screen of menu A to another screen, not the screen transition from menu A to another, is as follows.
menu A(fragment) -> B screen(fragment) -> C screen(fragment) -> B screen(fragment)
I hooked up these screen transitions in nav_graph
I am using BottomNavigation.
The transition from the screen of menu A to another screen, not the screen transition from menu A to another, is as follows.
menu A(fragment) -> B screen(fragment) -> C screen(fragment) -> D screen(fragment)
I hooked up these screen transitions in nav_graph
However, the screen transition from D to C was not connected, but it was possible to switch the screen using view.findNavController.navigate().
I thought transitioning the screen was impossible without connecting to the nav_graph.
How is this possible?
UPDATED
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"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/nav_graph"
app:startDestination="#id/calendar">
<fragment
android:id="#+id/calendar"
android:name="com.example.writeweight.fragment.CalendarFragment"
android:label="fragment_calendar"
tools:layout="#layout/fragment_calendar" >
</fragment>
<fragment
android:id="#+id/list"
android:name="com.example.writeweight.fragment.WorkoutListFragment"
android:label="fragment_workout_list"
tools:layout="#layout/fragment_workout_list" />
<!-- menu A fragment -->
<fragment
android:id="#+id/write_home"
android:name="com.example.writeweight.fragment.WriteRoutineHomeFragment"
android:label="fragment_write_routine_home"
tools:layout="#layout/fragment_write_routine_home" >
<action
android:id="#+id/action_write_home_to_bodyPartDialog"
app:destination="#id/bodyPartDialog" />
</fragment>
<!-- B screen -->
<dialog
android:id="#+id/bodyPartDialog"
android:name="com.example.writeweight.fragment.BodyPartDialogFragment"
android:label="BodyPartDialogFragment"
tools:layout="#layout/fragment_body_part_dialog">
<action
android:id="#+id/action_bodyPartDialog_to_write"
app:destination="#id/write"/>
</dialog>
<!-- C screen -->
<fragment
android:id="#+id/write"
android:name="com.example.writeweight.fragment.WritingRoutineFragment"
android:label="WritingRoutineFragment"
tools:layout="#layout/fragment_writing_routine">
<action
android:id="#+id/action_write_to_bodyPartDialog"
app:destination="#id/bodyPartDialog" />
<argument
android:name="title"
app:argType="string"
android:defaultValue="" />
</fragment>
<!-- D screen -->
<fragment
android:id="#+id/workoutListTabFragment"
android:name="com.example.writeweight.fragment.WorkoutListTabFragment"
android:label="fragment_workout_list_tab"
tools:layout="#layout/fragment_workout_list_tab" />
</navigation>
As per the Navigate using ID documentation:
navigate(int) takes the resource ID of either an action or a destination.
So both directly navigating to any destination is possible (by using the ID of the destination) and navigating via an action is supported.
The documentation goes on to say:
Note: When navigating using IDs, we strongly recommend using actions where possible. Actions provide additional information in your navigation graph, visually showing how your destinations connect to each other. By creating actions, you can replace resource IDs with Safe Args-generated operations, providing additional compile-time safety. By using an action, you can also animate transitions between the destinations. For more information, see Animate transitions between destinations.
Related
I am using the single activity approach with the navigation component. I have the main activity which is a container for the main fragment. The main fragment contains a container for all other fragments in the app and the bottom navigation. I've created several graphs to allow each tab to own its own navigation back stack. The main problem I don't know how to add the ability to open a modal graph by the deep link. By clicking on the deep link, should be opened an info fragment, and clicking the back button should be shown the previous one not depending on the tab. For example, I have 4 tabs: home, search, tools, and account. If the search was opened, and the screen by deep link was shown, by clicking the back button search or any of its nested fragments should be opened. For now, in any case, the account tab will be opened because the deep link flow belongs to its nav graph. Here is the navigation code:
The app navigation for activity:
<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/mainFragment">
<fragment
android:id="#+id/mainFragment"
android:name="com.app.flow.main.MainFragment"
android:label="MainFragment">
<deepLink app:uri="------/password/edit" />
<deepLink app:uri="------/confirmation" />
</fragment>
The main fragment navigation 1 graph per tab:
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/main_nav_graph"
app:startDestination="#id/home_graph">
<include app:graph="#navigation/home_graph" />
<include app:graph="#navigation/search_graph" />
<include app:graph="#navigation/tools_graph" />
<include app:graph="#navigation/account_graph" />
</navigation>
And the part of the account graph which is responsible for deep link flow:
<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/account_graph"
app:startDestination="#id/accountFragment">
***************************
<fragment
android:id="#+id/checkLinkFragment"
android:name="com.app.flow.checklink.CheckLinkFragment"
android:label="CheckLinkFragment">
<deepLink app:uri="-----/confirmation" />
<action
android:id="#+id/action_checkLinkFragment_to_emailConfirmedFragment"
app:destination="#id/emailConfirmedFragment" />
<action
android:id="#+id/action_checkLinkFragment_to_confirmationExpiredFragment"
app:destination="#id/confirmationExpiredFragment" />
</fragment>
***********************
</navigation>
Here's my scenario, boiled down as much as possible:
I have 2 fragments, A and B. There's a circular path between A and B on the graph, so A has a button that navigates to B, and B has a button to navigate to A. I'm having two issues with this scenario.
I'm playing an animation (a simple modal "slide up" where B slides up ontop of A) when launching B. It plays fine forwards (launching B) and back (pressing back). I would like it to play the exit animation when I navigate back to A as well (same animation as if I had pressed the back button, but on the fragment transaction). I can get an enter animation to play on A, but no exit animation will play on B.
The backstack. When navigating back to A, I don't want B or the first instance of A to remain on the backstack.
I've put together a sample project that demonstrates this issue: https://github.com/Morrodin/animtest
I'll include my graph directly in this post as well:
<?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"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/nav_graph"
app:startDestination="#id/FirstFragment">
<fragment
android:id="#+id/FirstFragment"
android:name="com.example.myapplication.FirstFragment"
android:label="#string/first_fragment_label"
tools:layout="#layout/fragment_first">
<action
android:id="#+id/action_FirstFragment_to_SecondFragment"
app:destination="#id/SecondFragment"
app:enterAnim="#anim/slide_in_up"
app:exitAnim="#anim/none"
app:popEnterAnim="#anim/none"
app:popExitAnim="#anim/slide_out_down" />
</fragment>
<fragment
android:id="#+id/SecondFragment"
android:name="com.example.myapplication.SecondFragment"
android:label="#string/second_fragment_label"
tools:layout="#layout/fragment_second">
<action
android:id="#+id/action_SecondFragment_to_FirstFragment"
app:destination="#id/FirstFragment"
app:enterAnim="#anim/none"
app:exitAnim="#anim/slide_out_down"
app:popEnterAnim="#anim/none"
app:popExitAnim="#anim/slide_out_down" />
</fragment>
</navigation>
I'm using BottomNavigationView with Navigation Component. When showing fragment is not root fragment, the tab icon is not updated (selected).
Example:
When I switch between Tab Home with Fragment A (which is root fragment) and Tab Star with Fragment B (which is also root fragment) it is working fine.
But when I navigate from Tab Home to another fragment, like fragment A2, and tap on Tab Star and again return to Tab Home, still Tab Star is selected in BottomNavigationView.
It was working fine with version 2.4.0-alpha05, This is happening when I updated it to 2.5.0-alpha01.
build.gradle (app)
implementation "androidx.navigation:navigation-fragment-ktx:2.5.0-alpha01"
implementation "androidx.navigation:navigation-ui-ktx:2.5.0-alpha01"
implementation "androidx.navigation:navigation-dynamic-features-fragment:2.5.0-alpha01"
build.gradle (root)
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.5.0-alpha01"
Graph:
<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/graph"
app:startDestination="#id/fragmentA">
<fragment
android:id="#+id/fragmentA"
android:name="ui.test.FragmentA"
tools:layout="#layout/fragment_test"
android:label="FragmentA" >
<action
android:id="#+id/action_fragmentA_to_fragmentA2"
app:destination="#id/fragmentA2" />
</fragment>
<fragment
android:id="#+id/fragmentA2"
android:name="ui.test.FragmentA2"
tools:layout="#layout/fragment_test"
android:label="FragmentA2" />
<fragment
android:id="#+id/fragmentB"
android:name="ui.test.FragmentB"
tools:layout="#layout/fragment_test"
android:label="FragmentB" />
</navigation>
Menu:
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="#+id/fragmentA"
android:icon="#drawable/ic_home"
android:title="" />
<item
android:id="#+id/fragmentB"
android:icon="#drawable/ic_star"
android:title="" />
</menu>
Am I doing something wrong? or this is bug?
How can I resolve this problem?
So what worked for me was the solution that ianhanniballake hinted at in his answer: using setOnItemSelectedListener.
// always show selected Bottom Navigation item as selected (return true)
bottomNavigationView.setOnItemSelectedListener { item ->
// In order to get the expected behavior, you have to call default Navigation method manually
NavigationUI.onNavDestinationSelected(item, navController)
return#setOnItemSelectedListener true
}
This will always select the item and navigate to the associated destination while maintaining multiple back stacks.
Given your navigation graph, there is no way to associate fragmentA2 with your menu item fragmentA, so fragmentA is not selected when you return to fragmentA2. As per this issue:
NavigationUI has always used the current destination and what graph it is part of as the source of truth for what tab should be selected.
This can be seen by calling navigate() to go to your SecondFragment - even though you haven't used the bottom nav button, the selected tab was changed because the current destination has changed to R.id.frag_second.
So when you navigate() to R.id.frag_hint via your button in HomeFragment, NavigationUI receives a callback that the current destination has changed to R.id.frag_hint. It looks at that NavDestination and notes that there's no menu item that matches R.id.frag_hint. It then looks at the destination's parent graph - your R.id.sample <navigation> element. There's no menu item that matches that ID either, so NavigationUI can't associated that destination with any menu item and therefore simply does nothing. That is true on all versions of Navigation.
So what is different when you tap on a bottom navigation item? Well, nothing different from a NavigationUI perspective in fact: the exact same code runs and the current destination and what graph it is part of is the source of truth for what tab should be selected. In the Navigation 2.3.5, there was no state saved for each tab, so it only 'worked' because selecting a tab forced the ID of the current destination to match the destination of the menu item you just tapped.
So what you're seeing in your sample app is that there's no link between R.id.frag_hint and any menu item, which means NavigationUI does nothing. If you want to link R.id.frag_hint to your Home tab, then that's exactly what a nested navigation graph can be used for.
I.e., your navigation graph should instead look like:
<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/sample"
app:startDestination="#id/home">
<navigation
android:id="#+id/home"
app:startDestination="#id/frag_home">
<fragment
android:id="#+id/frag_home"
android:name="eu.rekisoft.android.navbug.HomeFragment"
tools:layout="#layout/fragment_home">
<action
android:id="#+id/cause_bug"
app:destination="#id/frag_hint"/>
</fragment>
<fragment
android:id="#+id/frag_hint"
android:name="eu.rekisoft.android.navbug.HintFragment"
android:label="Hint"
tools:layout="#layout/fragment_hint"/>
</navigation>
<fragment
android:id="#+id/frag_second"
android:name="eu.rekisoft.android.navbug.SecondFragment"
android:label="Second Fragment"
tools:layout="#layout/fragment_second"/>
</navigation>
And your menu XML should be updated to use android:id="#id/home" to match your navigation graph.
Now, when you select the Home bottom nav item, the current destination changes to R.id.frag_hint (as your state was restored due to Navigation 2.4's support for multiple back stacks) and NavigationUI looks at the ID - R.id.frag_hint still doesn't match any menu item, but now the parent graph's ID, R.id.home does match a menu item - your Home menu item, hence, it becomes selected.
The intention that your navigation graph and its structure drives the UI is a key part of how NavigationUI works and is working as intended (there was a bug on earlier versions of Navigation 2.4 that broke this driving principle, but that has since been fixed in beta02). All of NavigationUI is built on public APIs specifically so that if you want to use some different logic for which bottom nav item is selected that is independent from your navigation graph structure, you can absolutely do that.
You'll note from the source code that you can call the public onNavDestinationSelected with your MenuItem to get the exact same navigate() logic which retaining your own ability to return any value from the setOnItemSelectedListener (which is what controls whether the tab becomes selected). Similarly, your own OnDestinationChangedListener can choose to look at the hierarchy of a destination to choose whether to change the selected bottom nav item or not.
So your graphs should also be using nested graphs for each tab:
<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/graph"
app:startDestination="#id/graphA">
<navigation
android:id="#+id/graphA"
app:startDestination="#id/fragmentA">
<fragment
android:id="#+id/fragmentA"
android:name="ui.test.FragmentA"
tools:layout="#layout/fragment_test"
android:label="FragmentA" >
<action
android:id="#+id/action_fragmentA_to_fragmentA2"
app:destination="#id/fragmentA2" />
</fragment>
<fragment
android:id="#+id/fragmentA2"
android:name="ui.test.FragmentA2"
tools:layout="#layout/fragment_test"
android:label="FragmentA2" />
</navigation>
<navigation
android:id="#+id/graphB"
app:startDestination="#id/fragmentB">
<fragment
android:id="#+id/fragmentB"
android:name="ui.test.FragmentB"
tools:layout="#layout/fragment_test"
android:label="FragmentB" />
</navigation>
</navigation>
Menu:
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="#+id/graphA"
android:icon="#drawable/ic_home"
android:title="" />
<item
android:id="#+id/graphB"
android:icon="#drawable/ic_star"
android:title="" />
</menu>
I have a project that's using multiple back stacks via Jetpack Navigation androidx.navigation:navigation-fragment-ktx:2.4.0-beta02. I'm investigating a way to navigate back home from another navigation graph. For simplicity, let's assume my main navigation graph contains two root destinations that get configured as bottom navigation destinations.
graph_main:
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/graph_main"
app:startDestination="#+id/graph_home">
<include app:graph="#navigation/graph_home"/>
<include app:graph="#navigation/graph_settings"/>
</navigation>
graph_home contains a start destination and a details screen:
<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/graph_home"
app:startDestination="#+id/home">
<fragment
android:id="#+id/home"
android:name="com.example.playground.ui.home.HomeFragment"
android:label="#string/title_home"
tools:layout="#layout/fragment_home">
<action
android:id="#+id/action_home_to_details"
app:destination="#id/details"/>
</fragment>
<fragment
android:id="#+id/details"
android:name="com.example.playground.ui.home.DetailsFragment"
android:label="#string/title_details"
tools:layout="#layout/fragment_details"/>
</navigation>
graph_settings just contains a start destination with some action(?) to navigate back to the start destination of graph_home:
<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/graph_settings"
app:startDestination="#+id/settings">
<fragment
android:id="#+id/settings"
android:name="com.example.playground.ui.settings.SettingsFragment"
android:label="#string/title_settings"
tools:layout="#layout/fragment_settings">
<!-- maybe? -->
<action
android:id="#+id/action_settings_to_home"
app:destination="#id/graph_home"/>
</fragment>
</navigation>
User launches the app to the home destination in graph_home.
User taps a list item, and system navigates to the details destination in graph_home.
User taps the "Settings" bottom navigation tab, and system navigates to the settings destination in graph_settings.
User taps a button on the settings screen that causes the system to pop all back stacks back to the home screen.
How can I set this up so that in step #4, the original home screen Fragment is shown (not a copy of it) and the back stacks are empty after navigating back home (tapping the system back button closes the app)?
I've a multi-graph navigation app and I'd like to switch between graphs by using a global action as defined in my root main_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"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/main_graph"
app:startDestination="#id/loadingFragment">
<include app:graph="#navigation/learn_graph" />
<action
android:id="#+id/action_global_learn_graph"
app:destination="#id/learn_graph"
app:launchSingleTop="true"
/>
</navigation>
Since I'm trying to switch between graphs, I'd like to clear the back stack from the fragments loaded by the source graph (main_graph) when navigating the global action to the destination graph (explore_graph). The expected behavior would be to navigate to the startDestination fragment of the destination graph keeping only that fragment in the backstack.
For normal actions (actions in the same graph) I'm able to use popUpTo flag, how it's possible to get the same behavior for a global action?
After a lot of attemps, I found out a solution. The base idea is to pop up the backstack to the graph that "owns" the global action. In my case main_graph is the owner, so I did:
<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_graph"
app:startDestination="#id/loadingFragment">
<include app:graph="#navigation/learn_graph" />
<action
android:id="#+id/action_global_learn_graph"
app:destination="#id/learn_graph"
app:popUpTo="#+id/main_graph"
app:launchSingleTop="true" />
</navigation>
In addition, you have to set the app:launchSingleTop flag to true in order to make the instance of destination graph unique in your backstack
You can also include app:popUpToInclusive="true" to indicate that the destination specified in app:popUpTo should also be removed from the back stack.