Execute Fragment Transaction Again on Pop
Multiple dorsum stacks
A deep swoop into what really went into this feature
If a 'back stack' is a set of screens that you tin navigate back through via the organization back push, 'multiple dorsum stacks' is but a bunch of those, right? Well, that's exactly what we've done with the multiple back stack support added in Navigation 2.4.0-alpha01 and Fragment 1.four.0-alpha01!
The joys of the arrangement dorsum button
Whether you're using Android's new gesture navigation system or the traditional navigation bar, the ability for users to become 'dorsum' is a key part to the user experience on Android and doing that correct is an important part to making your app feel similar a natural part of the ecosystem.
In the simplest cases, the organization back button merely finishes your action. While in the past you might have been tempted to override the onBackPressed()
method of your action to customize this beliefs, it is 2021 and that is totally unnecessary. Instead, there are APIs for custom back navigation in the OnBackPressedDispatcher
. This is really the same API that FragmentManager
and NavController
already plug into.
That means when y'all apply either Fragments or Navigation, they utilise the OnBackPressedDispatcher
to ensure that if you're using their back stack APIs, the system back push button works to reverse each of the screens that you've pushed onto the back stack.
Multiple back stacks doesn't change these fundamentals. The organization back button is notwithstanding a one directional command — 'go back'. This has a profound issue on how the multiple back stack APIs piece of work.
Multiple back stacks in Fragments
At the surface level, the back up for multiple dorsum stacks is deceptively straightforward, merely requires a bit of an explanation of what actually is the 'fragment back stack'. The FragmentManager
'south back stack isn't fabricated upwardly of fragments, but instead is made upwards of fragment transactions. Specifically, the ones that have used the addToBackStack(String proper name)
API.
This means when you commit()
a fragment transaction with addToBackStack()
, the FragmentManager
is going to execute the transaction past going through and executing each of the operations (the replace
, etc.) that you specified on the transaction, thus moving each fragment through to its expected state. FragmentManager
and so holds onto that transaction as role of its back stack.
When you call popBackStack()
(either direct or via FragmentManager
'southward integration with the system back button), the topmost transaction on the fragment dorsum stack is reversed — an added fragment is removed, a hidden fragment is shown, etc. This puts the FragmentManager
back into the same state that information technology was earlier the fragment transaction was initially committed.
Annotation: I cannot stress this plenty, only you lot absolutely should never interleave transactions with
addToBackStack()
and transactions without in the sameFragmentManager
: transactions on your back stack are blissfully unaware of non-back stack changing fragment transactions — swapping things out from underneath those transactions makes that reversal when yous pop a much more dicey proffer.
This means that popBackStack()
is a destructive performance: whatever added fragment volition accept its state destroyed when that transaction is popped. This means you lose your view state, any saved instance land, and any ViewModel
instances yous've attached to that fragment are cleared. This is the primary difference between that API and the new saveBackStack()
. saveBackStack()
does the same reversal that popping the transaction does, but it ensures that the view country, saved example state, and ViewModel
instances are all saved from devastation. This is how the restoreBackStack()
API tin can later on recreate those transactions and their fragments from the saved state and finer 'redo' everything that was saved. Magic!
This didn't come without paying downwards a lot of technical debt though.
Paying down our technical debts in Fragments
While fragments have e'er saved the Fragment's view country, the just time that a fragment's onSaveInstanceState()
would exist called would exist when the Activeness's onSaveInstanceState()
was chosen. To ensure that the saved case land is saved when calling saveBackStack()
, we demand to too inject a phone call to onSaveInstanceState()
at the correct point in the fragment lifecycle transitions. We can't call information technology too soon (your fragment should never have its state saved while information technology is still STARTED
), just non too late (you want to save the state before the fragment is destroyed).
This requirement kicked off a process to fix how FragmentManager
moves to country to make certain there'southward one place that manages moving a fragment to its expected country and handles re-entrant behavior and all the state transitions that go into fragments.
35 changes and half-dozen months into that restructuring of fragments, information technology turned out that postponed fragments were seriously cleaved, leading to a world where postponed transactions were left floating in limbo — non actually committed and not actually not committed. Over 65 changes and another 5 months later, and nosotros had completely rewritten near of the internals of how FragmentManager
manages state, postponed transitions, and animations. That endeavor is covered in more particular in my previous weblog post:
What to expect in Fragments
With the technical debt paid downward (and a much more reliable and understandable FragmentManager
), the tip of the iceberg APIs of saveBackStack()
and restoreBackStack()
were added.
If you don't employ these new APIs, nothing changes: the single FragmentManager
back stack works as earlier. The existing addToBackStack()
API remains unchanged — yous can utilise a null proper noun
or any proper noun
you want. Notwithstanding, that proper name
takes on a new importance when you start looking at multiple back stacks: it is that proper name that is the unique key for that fragment transaction that yous'd apply with saveBackStack()
and subsequently with restoreBackStack()
.
This might be easier to see in an example. Let'south say that you have added an initial fragment to your activity, so washed two transactions, each with a single replace
operation:
// This is the initial fragment the user sees
fragmentManager.commit {
setReorderingAllowed(true)
replace<HomeFragment>(R.id.fragment_container)
} // Afterward, in response to user actions, we've added ii more than
// transactions to the back stack
fragmentManager.commit {
setReorderingAllowed(true)
supercede<ProfileFragment>(R.id.fragment_container)
addToBackStack("profile")
} fragmentManager.commit {
setReorderingAllowed(true)
supplant<EditProfileFragment>(R.id.fragment_container)
addToBackStack("edit_profile")
}
This means that our FragmentManager
looks like:
Permit's say that we want to bandy out our profile back stack and swap to the notifications fragment. We'd call saveBackStack()
followed past a new transaction:
fragmentManager.saveBackStack("profile") fragmentManager.commit {
setReorderingAllowed(true)
supplant<NotificationsFragment>(R.id.fragment_container)
addToBackStack("notifications")
}
Now our transaction that added the ProfileFragment
and the transaction that added the EditProfileFragment
has been saved nether the "contour"
key. Those fragments have had their land saved completely and
FragmentManager
is holding onto their state aslope the transaction state. Chiefly: those fragment instances no longer be in memory or in the FragmentManager
— it is just the state (and any not config land in the class of ViewModel
instances):
Swapping back is simple enough: we tin do the same saveBackStack()
operation on our "notifications"
transaction and then restoreBackStack()
:
fragmentManager.saveBackStack("notifications") fragmentManager.restoreBackStack("profile")
The ii stacks accept finer swapped positions:
This style of maintaining a single agile back stack and swapping transactions onto it ensures that the FragmentManager
and the rest of the organization always has a consistent view of what actually is supposed to happen when the system back push is tapped. In fact, that logic remained entirely unchanged: it still just pops the last transaction off of the fragment back stack like before.
These APIs are purposefully minimal, despite their underlying effects. This makes it possible to build your own structure on elevation of these building blocks while fugitive whatsoever hacks to save Fragment view state, saved instance state, and not config land.
Of class, if you don't want to build your own structure on top of these APIs, you tin can also use the i we provide.
Bringing multiple back stacks to any screen type with Navigation
The Navigation Component was built from the beginning as a generic runtime that knows null about Views, Fragments, Composables, or any other type of screen or 'destination' you lot might implement within your action. Instead, it is the responsibleness of an implementation of the NavHost
interface to add one or more Navigator
instances that do know how to interact with a particular type of destination.
This meant that the logic for interacting with fragments was entirely encapsulated in the navigation-fragment
artifact and its FragmentNavigator
and DialogFragmentNavigator
. Similarly the logic for interacting with Composables is in the completely independent navigation-etch
artifact and its ComposeNavigator
. That brainchild means that if you want to build your app solely with Composables, you are not forced to pull in whatever dependency on fragments when y'all use Navigation Compose.
This level of separation means that at that place are really two layers to multiple back stacks in Navigation:
- Saving the state of the individual
NavBackStackEntry
instances that make upwardly theNavController
dorsum stack. This is the responsibility of theNavController
. - Saving any
Navigator
specific state associated with eachNavBackStackEntry
(eastward.grand., the fragment associated with aFragmentNavigator
destination). This is the responsibility of theNavigator
.
Special attention was given to the cases where the Navigator
has not been updated to support saving its state. While the underlying Navigator
API was entirely rewritten to support saving state (with new overloads of its navigate()
and popBackStack()
APIs that y'all should override instead of the previous versions), NavController
will relieve the NavBackStackEntry
country even if the Navigator
has non been updated (backward compatibility is a big deal in the Jetpack world!).
PS: this new
Navigator
API also makes it mode easier to test your own customNavigator
in isolation by attaching aTestNavigatorState
that acts as a mini-NavController
.
If you lot're just using Navigation in your app, the Navigator
level is more of an implementation detail than something yous'll ever demand to collaborate with directly. Suffice it to say, we've already done the work required to get the FragmentNavigator
and the ComposeNavigator
over to the new Navigator
APIs then that they correctly save and restore their state; there'south no work yous demand to exercise at that level.
Enabling multiple dorsum stacks in Navigation
If you're using NavigationUI
, our set up of opinionated helpers for connecting your NavController
to Textile view components, y'all'll find that multiple back stacks is enabled by default for card items, BottomNavigationView
(and now NavigationRailView
!), and NavigationView
. This means that the common combination of using navigation-fragment
and navigation-ui
will just piece of work.
The NavigationUI
APIs are purposefully built on top of the other public APIs available in Navigation, ensuring that you can build your own versions for precisely your set of custom components you want. The APIs to enable saving and restoring a back stack are no exception to this, with new APIs on NavOptions
, the navOptions
Kotlin DSL, in the Navigation XML, and in an overload for popBackStack()
that let you specify that you desire a pop operation to salvage land or you want a navigate operation to restore some previously saved state.
For example, in Compose, whatsoever global navigation design (whether it is a bottom navigation bar, navigation rails, drawer, or anything yous tin dream up) tin can all employ the aforementioned technique as we evidence for integrating with BottomNavigation and call navigate()
with the saveState
and restoreState
attributes:
onClick = {
navController.navigate(screen.route) {
// Pop up to the start destination of the graph to
// avoid building up a large stack of destinations
// on the back stack as users select items
popUpTo(navController.graph.findStartDestination().id) {
saveState = true
}// Avoid multiple copies of the same destination when
// Restore state when reselecting a previously selected item
// reselecting the same item
launchSingleTop = true
restoreState = truthful
}
}
Save your state, salvage your users
One of the most frustrating things for a user is losing their state. That's one of the reasons why fragments have a whole folio on saving state and one of the many reasons why I am so glad to get each layer updated to support multiple back stacks:
- Fragments (i.due east., without using the Navigation Component at all): this is an opt-in change by using the new
FragmentManager
APIs ofsaveBackStack
andrestoreBackStack
. - The core Navigation Runtime: adds opt-in new
NavOptions
methods forrestoreState
andsaveState
and a new overload ofpopBackStack()
that likewise accepts asaveState
boolean (defaults tofalse
). - Navigation with Fragments: the
FragmentNavigator
now utilizes the newNavigator
APIs to properly interpret the Navigation Runtime APIs into the Fragment APIs by using the Navigation Runtime APIs. -
NavigationUI
: TheonNavDestinationSelected()
,NavigationBarView.setupWithNavController()
, andNavigationView.setupWithNavController()
now utilise the newrestoreState
andsaveState
NavOptions
past default whenever they would popular the back stack. This means that every app using thoseNavigationUI
APIs will become multiple back stacks without any lawmaking changes on their role after upgrading the Navigation 2.4.0-alpha01 or higher.
If you'd like to await at some more examples that utilize this API, take a look at the NavigationAdvancedSample
(newly updated without any of the NavigationExtensions
code it used to require to back up multiple dorsum stacks):
And for Navigation Compose, consider looking at Tivi:
If you do meet any issues, please make sure to apply the official issue tracker to file bugs confronting Fragments or Navigation and we'll be certain to have a look at them!
Source: https://medium.com/androiddevelopers/multiple-back-stacks-b714d974f134
0 Response to "Execute Fragment Transaction Again on Pop"
Post a Comment