Android Jet Pack Navigation, setupWithNavController() recreating fragment - android

I have a problem with my navigation view using jetpack's BottomNavBar
so here's how my flow works.
I have 4 views and every one of them have redirections like when I'm in last selection of the navbar I have a fragment A -> fragment B and when I go back to the first selection of the navbar and when I go back to the 4th one its one the fragment A again. I believe it is because fragments are recreating using the setupWithNavController() if it so does jetpack have a workaround for that?
here is my code for some reference.
<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/splashFragment">
<fragment
android:id="#+id/selectionFragment"
android:name="whitecloak.com.allibuy.app.selection.SelectionFragment"
android:label="fragment_selection"
tools:layout="#layout/fragment_selection" >
<action
android:id="#+id/toLogin"
app:destination="#id/loginFragment"
app:launchSingleTop="true"
app:popUpTo="#+id/nav_graph"
app:popUpToInclusive="true/>
</fragment>
<fragment
android:id="#+id/splashFragment"
android:name="whitecloak.com.allibuy.app.splash.SplashFragment"
android:label="fragment_splash"
tools:layout="#layout/splash_fragment"
>
<action
android:id="#+id/toMain"
app:destination="#id/mainFragment"
app:launchSingleTop="true"
app:popUpTo="#+id/nav_graph"
app:popUpToInclusive="true"/>
</fragment>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="#+id/splashFragment"
android:icon="#drawable/home"
android:title="#string/home"
app:popUpTo="#id/nav_graph" />
<item
android:id="#+id/tabCart"
android:icon="#drawable/cart"
android:title="#string/cart"
app:popUpTo="#id/nav_graph" />
<item
android:id="#+id/tabNotif"
android:icon="#drawable/notification"
android:title="#string/notification"
app:popUpTo="#id/nav_graph"/>
<item
android:id="#+id/selectionFragment"
android:icon="#drawable/user"
android:title="#string/account"
app:popUpTo="#id/nav_graph" />
bottomNav.setupWithNavController(findNavController(R.id.nav_main))
I just included the XML for the 1st and last tab. Thank you so much.
EDIT
class MainNavigation : DaggerAppCompatActivity() {
#Inject
lateinit var viewModelFactory: ViewModelProvider.Factory
private lateinit var viewModel: MainNavigationViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
viewModel = ViewModelProviders.of(this, viewModelFactory)[MainNavigationViewModel::class.java]
bottomNav.setupWithNavController(findNavController(R.id.nav_main))
}
}

It's not related to the setup of your BottomNav. It's rather the explicit behavior in the implementation made for Android. I'll quote and explain:
Behavior
On Android: the app navigates to a destination’s top-level screen. Any prior user interactions and temporary screen states are reset, such as scroll position, tab selection, and in-line search.
From https://material.io/design/components/bottom-navigation.html#behavior
This means that when you click an item on the BottomNav, it should always go back to the first fragment on this flow's stack.
If I'm not being clear, here is a pseudo-representation:
BottomNavItem#1 > Fragment1A > Fragment1B
BottomNavItem#2 > Fragment2A > Fragment2B
If you tap on BottomNavItem#1, it loads Fragment1A. Then imagine using a button it shows Fragment1B. If you now click on BottomNavItem#2, you'll see Fragment2A. Now, if you click back on BottomNavItem#1, it will show Fragment1A (not the Fragment1B that you saw last), because it's the root of that stack/flow.

Related

Android Navigation Component - How to have bottom navigation apply to only some fragments

I'm working on an Android app (java) which currently only has a single activity (MainActivity) which loads four fragments via Bottom navigation. I'm using the Navigation component, this is the nav_graph:
<?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/homeFragment">
<fragment
android:id="#+id/homeFragment"
android:name="com.myapp.app.HomeFragment"
android:label="Home"
tools:layout="#layout/fragment_home">
// .. actions to sub fragments ..
</fragment>
<fragment
android:id="#+id/secondFragment"
android:name="com.myapp.app.SecondFragment"
android:label="Second"
tools:layout="#layout/fragment_second" />
// .. actions to sub fragments ..
<fragment
android:id="#+id/thirdFragment"
android:name="com.myapp.app.ThirdFragment"
android:label="Third"
tools:layout="#layout/fragment_third">
// .. actions to sub fragments ..
</fragment>
<fragment
android:id="#+id/fourthFragment"
android:name="com.myapp.app.FourthFragment"
android:label="Fourth"
tools:layout="#layout/fragment_fourth" >
// .. actions to sub fragments ..
</fragment>
// .. More subfragments linked from the first four ..
</navigation>
What I'd like is to have a separate flow where I have an Onboarding Screen, which can link to Sign In or Sign Up. After Signing In or going through the process of Signing Up the user should return to the Home fragment with bottom navigation. The problem is, this whole second flow should not show either toolbar or bottom navigation, they should be full screen and shouldn't allow access to the bottom navigation layout.
How can I get the bottom navigation to just affect those fragments in the main app but not the screens in the sign in/sign up process?
You can use "addOnDestinationChangedListener" method of NavController in oncreate() of your MainActivity. Here is the sample code is written in kotlin.
navController.addOnDestinationChangedListener { controller, destination, arguments ->
when (destination.id) {
R.id.splashFragment, R.id.welcomeFragment, R.id.signInFragment, R.id.signUpFragment, R.id.forgotPasswordFragment,
R.id.forgotPasswordStepTwoFragment -> {
mToolbar.visibility = View.GONE
navViewBottom.visibility = View.GONE
}
R.id.homeFragment -> {
mToolbar.visibility = View.VISIBLE
navViewBottom.visibility = View.VISIBLE
}
else -> {
mToolbar.visibility = View.VISIBLE
navViewBottom.visibility = View.VISIBLE
}
}
You have to declare NavController in your MainActivity class for that.
private lateinit var navController: NavController
navController = findNavController(R.id.nav_host_fragment)
Here "nav_host_fragment" is fragment container in your xml file.
Hope it works...
<fragment
android:id="#+id/logInFragment"
android:name="com.android.bottomnavmultipletablayout.login.LogInFragment"
android:label="fragment_log_in"
tools:layout="#layout/fragment_log_in" >
<action
android:id="#+id/action_logInFragment_to_instagramFragment"
app:destination="#id/instagramFragment" />
</fragment>
<fragment
android:id="#+id/instagramFragment"
android:name="com.android.bottomnavmultipletablayout.main.InstagramFragment"
android:label="fragment_instagram"
tools:layout="#layout/fragment_instagram" />
<fragment
android:id="#+id/twitterFragment"
android:name="com.android.bottomnavmultipletablayout.main.TwitterFragment"
android:label="fragment_twitter"
tools:layout="#layout/fragment_twitter" />
<fragment
android:id="#+id/facebookFragment2"
android:name="com.android.bottomnavmultipletablayout.main.FacebookFragment"
android:label="fragment_facebook"
tools:layout="#layout/fragment_facebook" />
======================================================================
<item
android:id="#+id/instagramFragment"
android:title="instagram" />
<item
android:id="#+id/facebookFragment2"
android:title="facebook" />
<item
android:id="#+id/twitterFragment"
android:title="twitter" />
put your id fragment you want in navigation component in menu.

Android Custom Navigation Drawer has weird backstack

I have an app similar to Android Studios Navigation Drawer Activity:
My activity uses Android Architecture Navigation Components & a navigation drawer to navigate between different fragments.
As the navigation drawer is pretty custom i can't use the usual navigation-view, but use a custom fragment hosting a LinearLayout.
Each item in that LinearLayout has an onClickListener which boils down to
navController.navigate(R.id.myCorrespondingFragment)
So far, so everything works fine.
The problem begins when navigating back:
Let's imagine i navigate from "Home"-Fragment A -> B -> C and then go back.
Android Studios example behaves correctly: C -> A -> close
My implementation doesn't: it just pops the backstack C -> B -> A -> close
How do i fix that?
Minified Main-Layout:
<?xml version="1.0" encoding="utf-8"?>
<androidx.drawerlayout.widget.DrawerLayout android:id="#+id/drawerLayout" >
<LinearLayout android:orientation="vertical" >
<androidx.appcompat.widget.Toolbar android:id="#+id/toolbar" />
<fragment
android:id="#+id/container"
android:name="androidx.navigation.fragment.NavHostFragment"
app:defaultNavHost="true"
app:navGraph="#navigation/nav_graph"
/>
</LinearLayout>
<fragment
android:name="com.company.drawer.DrawerFragment"
android:layout_gravity="start"
/>
</androidx.drawerlayout.widget.DrawerLayout>
Navigation graph:
<?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/navigation_graph"
app:startDestination="#id/doorFragment"
>
<fragment
android:id="#+id/doorFragment"
android:name="com.company.door.DoorFragment"
android:label="#string/shared_empty"
/>
<fragment
android:id="#+id/historyFragment"
android:name="com.company.history.HistoryFragment"
android:label="#string/history"
/>
<fragment
android:id="#+id/settingsFragment"
android:name="com.company.settings.SettingsFragment"
android:label="#string/settings"
/>
</navigation>
Everything navigation related from my Main Activity's onCreate:
setSupportActionBar(toolbar)
val navController = (supportFragmentManager.findFragmentById(R.id.container) as NavHostFragment).navController
toolbar.setupWithNavController(navController, AppBarConfiguration(
listOf(R.id.doorFragment, R.id.historyFragment, R.id.settingsFragment)
, drawerLayout
))
You can try like this
<fragment
android:id="#+id/fragmentA"
android:name="com.package.FragmentA"
android:label="Fragment A">
<action
android:id="#+id/action_fragmentA_to_fragmentB"
app:destination="#id/fragmentB"
app:popUpTo="#id/fragmentB"
app:popUpToInclusive="true / false" />
</fragment>
You can use global actions, so that you don't have to add an action for every fragment to fragment navigation but only for every fragment destination. This would look like this in XML:
<action android:id="#+id/action_global_doorFragment"
app:destination="#id/doorFragment"
app:popUpTo="#id/#id/doorFragment"
app:restoreState="true"
app:popUpToSaveState="true" />
<action android:id="#+id/action_global_historyFragment"
app:destination="#id/historyFragment"
app:popUpTo="#id/#id/doorFragment"
app:restoreState="true"
app:popUpToSaveState="true" />
<action android:id="#+id/action_global_settingsFragment"
app:destination="#id/historyFragment"
app:popUpTo="#id/#id/doorFragment"
app:restoreState="true"
app:popUpToSaveState="true" />
Note that I always used your start destination for popUpTo and also save the backstack state. You don't necessarily need the latter (restoreState and popUpToSaveState) for your use case but maybe want it later down the road when you have multiple fragments for a single drawer entry. This will get you the same behaviour as the current standard drawer integration.
Here's an easy example on how you can use the global action to go to your settings fragment. (The docs also explain how you can use global actions with the SafeArgs Gradle Plugin which is probably the better method.)
navController.navigate(R.id.action_global_settingsFragment)
If you don't want an extra action for every destination and your drawer menu items have the same id as its destination, you can also do it programmatically like this:
override fun onNavigationItemSelected(item: MenuItem): Boolean {
navController.navigate(item.itemId, null, navOptions {
restoreState = true
popUpTo(navController.graph.findStartDestination().id) {
saveState = true
}
})
return true
}

Android navigation component back Button doesn´t work [duplicate]

I use navigation components to navigate from one fragment to another. However, when the user press the back button, I want to navigate back to first fragment. But it keep showing the second fragment. This is my nav_graph:
<?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/fragment1">
<fragment
android:id="#+id/fragment2"
android:name="com.myapp.ui.fragments.Fragment2"
android:label="fragment_2" />
<fragment
android:id="#+id/fragment1"
android:name="com.myapp.ui.fragments.Fragment1"
android:label="fragment_1">
<action
android:id="#+id/action_fragment1_to_fragment2"
app:destination="#id/fragment2"
app:enterAnim="#anim/fragment_fade_enter"
app:exitAnim="#anim/fragment_fade_exit"
app:popUpTo="#id/fragment1" />
</fragment>
</navigation>
And this is how I trigger the navigation in the code of my Fragment1-Class:
viewModel.itemSelected.observe(viewLifecycleOwner) {
navigate(it)
}
....
fun navigate(id: Long){
val bundle = Bundle()
bundle.putLong("itemid", id)
getNavController().navigate(R.id.action_fragment1_to_fragment2, bundle)
}
Edit:
Corrected startDestination in XML.
Edit2:
Added more code.
You're using a LiveData for an event. LiveData always caches the set value, so when you return to your Fragment1, you observe the LiveData again and get the same value a second time, causing you to navigate() yet again.
See this blog post for more information and alternatives.

Navigate up by back button with Navigation Component

I use navigation components to navigate from one fragment to another. However, when the user press the back button, I want to navigate back to first fragment. But it keep showing the second fragment. This is my nav_graph:
<?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/fragment1">
<fragment
android:id="#+id/fragment2"
android:name="com.myapp.ui.fragments.Fragment2"
android:label="fragment_2" />
<fragment
android:id="#+id/fragment1"
android:name="com.myapp.ui.fragments.Fragment1"
android:label="fragment_1">
<action
android:id="#+id/action_fragment1_to_fragment2"
app:destination="#id/fragment2"
app:enterAnim="#anim/fragment_fade_enter"
app:exitAnim="#anim/fragment_fade_exit"
app:popUpTo="#id/fragment1" />
</fragment>
</navigation>
And this is how I trigger the navigation in the code of my Fragment1-Class:
viewModel.itemSelected.observe(viewLifecycleOwner) {
navigate(it)
}
....
fun navigate(id: Long){
val bundle = Bundle()
bundle.putLong("itemid", id)
getNavController().navigate(R.id.action_fragment1_to_fragment2, bundle)
}
Edit:
Corrected startDestination in XML.
Edit2:
Added more code.
You're using a LiveData for an event. LiveData always caches the set value, so when you return to your Fragment1, you observe the LiveData again and get the same value a second time, causing you to navigate() yet again.
See this blog post for more information and alternatives.

How to disable UP in Navigation for some fragment with the new Navigation Architecture Component?

I am trying out the new Navigation Architecture Component, and I can't figure out how to do this:
I have 1 Activity (MainActivity) + 3 Fragments:
SplashFragment (Home)
MainFragment
SignUpFragment
I would like to use SplashFragment to determine if I should navigate to MainFragment or SignUpFragment, but once it reaches either of those 2, you should not be able to pop back to SplashFragment. How can I do that with the new navigation component?
I tried popBackStack before and after calling navigate(R.id.action_xxx), but neither of them work (which make sense: before it has nothing to pop; after it just closes the fragment that just got added). Does that mean the only way to do that is to override onBackPress to intercept it and make sure navigateUp does not get call in those cases?
Thanks!
First, add attributes app:popUpTo='your_nav_graph_id' and app:popUpToInclusive="true" to the action tag.
<fragment
android:id="#+id/signInFragment"
android:name="com.glee.incog2.android.fragment.SignInFragment"
android:label="fragment_sign_in"
tools:layout="#layout/fragment_sign_in" >
<action
android:id="#+id/action_signInFragment_to_usersFragment"
app:destination="#id/usersFragment"
app:launchSingleTop="true"
app:popUpTo="#+id/main_nav_graph"
app:popUpToInclusive="true" />
</fragment>
Second, navigate to the destination, using the above action as parameter.
findNavController(fragment).navigate(SignInFragmentDirections.actionSignInFragmentToUserNameFragment())
NOTE: If you navigate using method navigate(#IdRes int resId), you won't get the desired result. Hence, I used method navigate(#NonNull NavDirections directions).
This worked for me in alpha05 release. Add app:popUpTo="#id/nav_graph" in the action tag(inside your nav_graph.xml file).
Here "#id/nav_graph is the id of my graph or also called as the Root.
<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/startFragment">
.......
<action
android:id="#+id/action_startFragment_to_homeFragment"
app:destination="#id/homeFragment"
app:popUpTo="#id/nav_graph"/>
.......
You can also do this in design tab:- select "SplashFragment" and select the action you want to change and then pick "root" for "Pop To"
WARNING: clearTask has been deprecated and will be remove in future release, not sure what the solution is. Please follow this issue for now to keep up to date
Oh after 10 minutes finally found the key: use clearTask.
All I have to do is add app:clearTask="true" to that specific action, or use .navigate(R.id.actionXXXX, null, NavOptions.Builder().setClearTask(true).build()), and it's done. Just make sure you add it to all the children of SplashFragment (in this case, both MainFragment and SignUpFragment).
So if you have splash fragment and main fragment and you don't want to go back to splash fragment after the main fragment below method you can achieve this
<fragment
android:id="#+id/splashFragment"
android:name="com.example.youappname.views.SplashFragment"
android:label="fragment_splash"
tools:layout="#layout/fragment_splash">
<action
android:id="#+id/action_splashFragment_to_mainFragment"
app:destination="#id/mainFragment"
app:popUpTo="#id/splashFragment"
app:popUpToInclusive="true"/>
</fragment>
In you Kotlin Splash Fragment:
private lateinit var navController: NavController
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
navController = Navigation.findNavController(view)
}
private fun navigateToMainFrag() {
navController.navigate(R.id.action_splashFragment_to_mainFragment)
}
Now when you press back button it will close the app instead of showing the splash screen
For anyone wanted to do this purely in code:
Navigation.findNavController(v)
.navigate(R.id.action_splashFragment_to_userProfileFragment2, null,
new NavOptions.Builder().setPopUpTo(R.id.splashFragment, true).build())
The sample solution is add a onBackPressedDispatcher on Owner Activity of fragment/navigation:
https://developer.android.com/guide/navigation/navigation-custom-back#implement_custom_back_navigation

Categories

Resources