Android deeplink navigation replaces current fragment in view and cannot navigate back - android

I have a NavigationView (Drawer) with 5 menus each pointing to 5 different fragments. Say Fragment A,B,C,D,E.
Now I want to navigate to fragment C on clicking a notification (Pending Intent)
This is my deepLink
return NavDeepLinkBuilder(this)
.setComponentName(MainActivity::class.java)
.setGraph(R.navigation.mobile_navigation)
.setDestination(R.id.nav_fragment_c)
.setArguments(args)
.createPendingIntent()
It navigates to the fragment but suppose the user is currently on fragment A, the deep link adds the fragment on top of fragment A and won't let the user navigate back to fragment A even on clicking the drawer menu A (behaves like it's disabled). However, on clicking the back button it takes the user back to fragment A and everything works fine again (I guess it pops the fragment).
Am I doing something wrong here? How do I tell the navController to navigate to the desired fragment without adding it on top of whichever fragment the user is currently on.
Also noticed the same behavior when I do navController.navigate(destinationId)
Here is my navContoller
val navController = findNavController(R.id.nav_host_fragment_content_main)
binding.navView?.let {
appBarConfiguration = AppBarConfiguration(
setOf(
R.id.nav_fragment_a,
R.id.nav_fragment_b,
R.id.nav_fragment_c,
R.id.nav_fragment_d,
R.id.nav_fragment_e,
),
binding.drawerLayout
)
setupActionBarWithNavController(navController, appBarConfiguration)
it.setupWithNavController(navController)
}

Related

Reselect operation on Bottom Naviation bar android with naviagtion component

I have a bottom navigation bar which is connected with navHost and is configured using the following code:
Val navHostFragment =supportFragmentManager.findFragmentById(R.id.fragment) as NavHostFragment
val navController = navHostFragment.navController
val bottomNavBar = findViewById<BottomNavigationView>(R.id.bottomBar)
setupWithNavController(bottomNavBar, navController)
I have 4 fragments now when I switch to 2nd fragment(by clicking on 2nd icon in the bottom navigation bar) and then I navigate to another fragment which is linked to the 2nd fragment. When I click on the back button I switch to 2nd fragment.
All good till far.
The problem is: I want to go back to 2nd fragment from the opened fragment when I reselect the same icon in the bottom navigation bar
I solved my issue with the help of this thread. If someone is facing the same issue, check this out:
Android clear backstack after reselecting Bottom Navigation tab
I have integrated a better solution which lets you have animations too when switching from one fragment to another.
val id = navController.currentDestination?.id
when (id) {
R.id.detailedTransactionAnalysis -> {
navController.navigate(R.id.action_detailedTransactionAnalysis_to_MainScreen)
}
R.id.detailedCategoryTransactionsFragment -> {
navController.navigate(R.id.action_detailedCategoryTransactionsFragment_to_MainScreen)
}
R.id.addTransaction -> {
navController.navigate(R.id.action_addTransaction_to_Stats)
}
}
navController.popBackStack(reselectedDestinationId, inclusive = false)
It works totally fine.

Navigation Component - Starting destination is incorrect

I started noticing something in my app the other day and its wildly inconsistent. Sometimes it happens and sometimes it doesn't.
I am using the Navigation Component to handle navigation in the app and I started noticing that sometimes, when popping the backstack via the action bar back button or the device back button, it returns to a fragment that is no longer the starting destination (or at least shouldn't be).
In my case the app starts in MainFragment and once authenticated moves to DashboardFragment. This is a pretty common scenario.
Navigation in the app is pretty flat. most of the time its only 1 level deep so nearly all views are accessible from the dashboard.
The app starts at a login view as many do and then to a dashboard where the session will remain as the "start destination". To accomplish this, its done in the nav_graph using popUpTo and popUpToInclusive.
<fragment
android:id="#+id/mainFragment"
android:name="com.example.view.fragments.MainFragment"
android:label="Welcome">
<action
android:id="#+id/action_mainFragment_to_dashboardFragment"
app:destination="#id/dashboardFragment"
app:popUpTo="#id/mainFragment"
app:popUpToInclusive="true"/>
</fragment>
<fragment
android:id="#+id/dashboardFragment"
android:name="com.example.view.fragments.dashboard.DashboardFragment"
android:label="#string/dashboard_header" >
<action
android:id="#+id/action_dashboardFragment_to_notificationsFragment"
app:destination="#id/notificationsFragment" />
</fragment>
When the user successfully authenticates and its time to go to the dashboard, I use NavController.navigate() to send them there.
findNavController().navigate(
MainFragmentDirections.actionMainFragmentToDashboardFragment()
)
// This should have the same result and it does appear to be affected by the same issue
// findNavController().navigate(R.id.action_mainFragment_to_dashboardFragment)
I have an action bar with a back arrow and a navigation drawer. In the main activity I need to define the AppBarConfiguration and override onSupportNavigateUp()
lateinit var appBarConfiguration: AppBarConfiguration
...
override fun onCreate(savedInstanceState: Bundle?) {
Timber.d("onCreate()")
super.onCreate(savedInstanceState)
_binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
// There is 2 different drawer menu's respectfully.
appBarConfiguration = AppBarConfiguration(
setOf(
R.id.mainFragment,
R.id.dashboardFragment
), binding.drawerLayout
)
setSupportActionBar(binding.toolbar)
setupActionBarWithNavController(navController, appBarConfiguration)
}
...
override fun onSupportNavigateUp(): Boolean {
Timber.d("-> onSupportNavigateUp()")
val breadcrumb = navController
.backStack
.map { it.destination }
.filterNot { it is NavGraph }
.joinToString(" > ") { it.displayName.split('/')[1] }
Timber.d("Backstack: $breadcrumb")
Timber.d("Previous backstack entry: ${navController.previousBackStackEntry?.destination?.displayName}")
return navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp()
}
...
The logs look like this when we step back and it is working correctly
D/MainActivity: -> onSupportNavigateUp()
D/MainActivity: Backstack: dashboardFragment > testingFragment
D/MainActivity: Previous backstack entry: com.example:id/dashboardFragment
D/DashboardFragment: -> onCreateView()
D/BaseFragment: -> onCreateView()
D/DashboardFragment: -> onViewCreated()
I also noticed when using the hamburger in the action bar that it also calls onSupportNavigateUp()
D/MainActivity: -> onSupportNavigateUp()
D/MainActivity: pendingAction: false
D/MainActivity: Backstack: dashboardFragment
D/MainActivity: Previous backstack entry: null
When I use the drawer to navigate to a destination I do see this in the logs and im not sure where/why this is returned or if it has any importance
I/NavController: Ignoring popBackStack to destination com.example:id/mainFragment as it was not found on the current back stack
Now, when its NOT working correctly, this is what the logs look like
D/MainActivity: -> onSupportNavigateUp()
D/MainActivity: Backstack: mainFragment > testingFragment
D/MainActivity: Previous backstack entry: com.example:id/mainFragment
D/MainFragment: -> onCreateView()
D/BaseFragment: -> onCreateView()
D/MainFragment: -> onViewCreated()
This really feels like the popUpTo and popUpToInclusive properties are not being applied (sometimes) when performing the navigation from main fragment to dashboard fragment. It's also suspicious that even though the dashboard fragment is not set as the new starting destination but also its missing from the backstack. Assuming the properties were NOT applied I would expected to see the breadcrumb Backstack: mainFragment > dashboardFragment > testingFragment
Any help would be greatly appreciated!
While there may certainly be a better procedure to follow (as outlined in the Principles of Navigation in the previous comments) the overhead of the change introduces a plethora of new errors and the scope is too large at this time.
Its still unknown why popUpTo and popUpToInclusive are not reliable via XML when navigating (even using NavDirections) however, thus far, passing the NavOptions while navigating seems to resolve the issue.
findNavController().navigate(
MainFragmentDirections.actionMainFragmentToDashboardFragment(),
NavOptions.Builder().setPopUpTo(R.id.mainFragment, true).build()
)
Thus far the issue has yet to arise again.
As it says in Ian's first link:
The back stack always has the start destination of the app at the bottom of the stack.
Your start destination is the "home" one in a navigation graph, the one identified with the little house icon. Set with the startDestination attribute in the XML. When the Navigation library creates a backstack, it always has that destination at the bottom. And it will always be there, even if you try to avoid it with the poUpTo attributes.
That's why if there's a fragment you consider your "home" one, like your dashboard, that needs to explicitly be your startDestination. It's the last thing a user will see if they back out of your app. If you have something like a login or welcome screen as the start destination, they'll back out to that.
Which is why you need to set the "home" fragment as the start destination, and then handle any extra navigation to a login or welcome screen from there. It's just how the Navigation stuff is designed to work. If you try and work around it (I did!) you'll run into other problems, and a lot of the nice features like automatic backstack recreation might not work properly

Navigation Controller (Managing Backstack) Jetpack Android

Good day. So I've been working around with NavComponent of Jetpack for Android
I've thought that management of BackStack of fragments had to be implemented there already, well in fact it is there but I have faced an issue.
Here is my structure:
I have and entry Activity
I have a NavHost in the activity
I have Bottom Navigation bar in the Activity
For each Bottom Item I am using separate Fragments to navigate through.
Here is the code for the navigation.
bottomNavigationView.setOnNavigationItemSelectedListener {
when (it.itemId) {
R.id.navigation_home -> {
navController.apply {
navigate(R.id.navigation_home)
}
true
}
R.id.navigation_dashboard -> {
navController.apply {
navigate(R.id.dashboardFragment)
}
true
}
R.id.navigation_notifications -> {
true
}
else -> {
false
}
}
}
Never mind the last item.
So the issue is next.
If I try to switch between home and dashboard multiple times, when I press back then the stack surely will start popping all the items included there. So if I move like 6 times it will take me 12 attempts to actually exit the app.
Currently I couldn't find any source where for example the navigate() method will accept some sort of argument to cash my fragments instead of recreating it each time and adding to the BackStack.
So what kind of approach would you suggest?
If I to manage the BackStack manually on each back button pressed, what's the purpose of NavController at all? Just for creating and FORWARD navigation?
I think I'm missing some source in Android's official docs.
Thank you beforehand.
P.S.
using navController.popBackStack() before calling navigate() surely isn't the correct choice.
According to the documentation here :
NavigationUI can also handle bottom navigation. When a user selects a menu item, the NavController calls onNavDestinationSelected() and automatically updates the selected item in the bottom navigation bar.
to do so you have to give your bottom navigation items an ids as same as the corresponding destination in your navigation graph , and then tie you bottom view to the controller like this :
NavHostFragment navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment);
NavController navController = navHostFragment.getNavController();
BottomNavigationView bottomNav = findViewById(R.id.bottom_nav);
NavigationUI.setupWithNavController(bottomNav, navController);
Note : from my personal experience , when the startDestination in the graph , that start by default is not currently in back stack (In my case it was the landing page which i pop it out when going to home fragment) then the app act with weird behavior like this . so make sure the start destination is existed in your back stack on should work fine .

How to kill all fragments created by a NavHostFragment?

I have 2 navigation files, and in my Activity, 2 fragments. One of the navigations is always shown inside one of the fragments, but I show the other one only when I need it.
The way they're drawn is the always showing fragment is inside a relativeLayout, and the other fragment is inside the same relativeLayout with it's visibility set as gone. When I need the second navigation, I set the visibility to visible and when I don't need it, I set it to gone again.Visually this works well, but what I want to accomplish is that when I don't want the second navigation, I want to completely kill it and redraw it the next time I need it.
What I've done so far was to get a hold of the NavHostFragment used to start the navigation, and when I dont need it anymore, call popBackStack() on it's navController, but it doesn't work:
val navHost: NavHostFragment? = null
fun createSecondNav() {
navHostLogin = NavHostFragment.create(R.navigation.navigation_second)
theFragment.visibility = View.VISIBLE
supportFragmentManager.beginTransaction()
.replace(R.id.theFragment, navHostLogin!!)
.commit()
}
fun killSecondNav() {
theFragment.visibility = View.GONE
navHostLogin?.navController?.popBackStack() // returns false
navHostLogin = null
}
So how can I completely kill the fragments created by the second navHost?
NavController maintains it's own back-stack, independent form the FragmentManager back-stack.
And popBackStack() without arguments only pops that back-stack once:
Attempts to pop the controller's back stack. Analogous to when the user presses the system Back button when the associated navigation host has focus.
While popBackStack(int destinationId, boolean inclusive) reads:
Attempts to pop the controller's back stack back to a specific destination.
destinationId int: The topmost destination to retain
inclusive boolean: Whether the given destination should also be popped.
So this should be:
navController.popBackStack(R.id.startDestination, true)
I'd wonder why even using two NavController, because one can set the graph at run-time with setGraph(NavGraph graph, Bundle startDestinationArgs):
Sets the navigation graph to the specified graph.
Any current navigation graph data (including back stack) will be replaced.

Navigation Architecture Component - Navigation Drawer

I am using Navigation Component with Navigation Drawer.
I have added Fragments for each Navigation item in the menu.
Add NavHostFragment where this Fragment will be swapped
Then added Fragments as Destinations
I want master Detail Navigation i.e.
start destination -> fragment 2
back button -> start destination
start destination -> fragment 2 -> fragment 3 -> fragment 4
back button -> start destination
My question is whether I should add any connections to this graph?
I also need to have one menu item which is just logout function call not fragment swapping, so I could not configure it with default setup
val navController = findNavController(R.id.main_nav_host_fragment)
nav_view.setupWithNavController(navController)
But rather have to use
nav_view.setNavigationItemSelectedListener(this)
override fun onNavigationItemSelected(item: MenuItem): Boolean {
// Handle navigation view item clicks here.
when (item.itemId) {
R.id.navSignOut -> {
loginViewModel.logout()
}
else -> {
val navController = findNavController(R.id.main_nav_host_fragment)
navController.navigate(item.itemId)
}
}
drawer_layout.closeDrawer(GravityCompat.END)
return true
}
I also need to show Login Activity above (modally) the Main App Activity with Navigation Drawer. Can I use Navigation Graph for it and how?
Login Activity should: on back button -> close app, if logged out -> start above main activity, if logged in go to main activity
So I have to questions:
1. Should I use any actions?
2. Should I use custom navigation for drawer or setupWithNavController()?
3. What about modal login activity navigation?
The part I undrestood from your question is how to navigate to logout.
Usually when the user clicks on logout you want to log them out and navigate to a startup screen.
So you need to add your startup activity to nav_graph.
Michael, navigation architecture it is not that easy to understand at the beginning, what you want to is not the exception. But as this navigation is new, I just suggest you to follow the code lab.
https://codelabs.developers.google.com/codelabs/android-navigation/#0
I see for instance your line to close the drawer. The code lab has instructions on how to setup the nav drawer with navigation and the UI
I hope it helps you.

Categories

Resources