Releases: Zhuinden/simple-stack
Simple Stack 2.9.0
Simple-Stack 2.9.0 (2024-05-06)
- ADDED:
Backstack.goAppendChain(newKeys)
andBackstack.goAppendChain(asReplace, newKeys)
which appends the provided keys to the end of
the current history.
If any duplicates are provided, they will also be added to the end. If a key already exists in the history, it gets removed from earlier and
appended to the end.
If there are implicit parents used as the reorder occurs, please make sure that the scope hierarchy can still be rebuilt from left-to-right
order. It might be preferred top use ScopeKey.Child
instead of ScopeKey
in these cases.
- ADDED:
Backstack.findServices(serviceSearchMode)
andBackstack.findServicesFromScope(scopeTag, serviceSearchMode)
. (#284)
This allows for getting the services of a backstack (either only local services, or including parent services) that are accessible within
the backstack.
Simple Stack 2.8.0
Simple-Stack 2.8.0 (2023-07-03)
- ADDED:
Backstack.setParentServices(Backstack parentServices)
,Backstack.setParentServices(Backstack parentServices, String parentScopeTag)
andBackstack.getParentServices()
(as per #239).
When using backstack.lookupService()
, backstack.canFindService()
, backstack.canFindFromScope()
and backstack.lookupFromScope()
, then
if parent services are set, it will attempt to lookup the service with ALL from either the full scope hierarchy, or from the scope provided
as the parentScopeTag
.
Please note that findScopesForKey()
is NOT affected, as it would drastically alter behavior. If you need this, you can collect it from the
parent manually (which is partly why getParentServices()
was added).
Simple Stack 2.7.0
Simple-Stack 2.7.0 (2023-03-31)
- MAJOR FEATURE ADDITION: Added
Backstack.setBackHandlingModel(BackHandlingModel.AHEAD_OF_TIME)
to
supportandroid:enableBackInvokedCallback="true"
on Android 14 for predictive back gesture support.
With this, Navigator.Installer.setBackHandlingModel()
, BackstackDelegate.setBackHandlingModel()
,
and Backstack.setBackHandlingModel()
are added.
Also, ServiceBinder.getAheadOfTimeBackCallbackRegistry()
is added as a replacement for ScopedServices.HandlesBack
.
Please note that using it requires AHEAD_OF_TIME
mode, and without it, trying to
use ServiceBinder.getAheadOfTimeBackCallbackRegistry()
throws an exception.
Also, Backstack.willHandleAheadOfTimeBack()
, Backstack.addAheadOfTimeWillHandleBackChangedListener()
and Backstack.removeAheadOfTimeWillHandleBackChangedListener()
are added.
IMPORTANT:
The AHEAD_OF_TIME
back handling model must be enabled similarly to how setScopedServices()
or other similar configs
must be called before backstack.setup()
, Navigator.install()
, or BackstackDelegate.onCreate()
.
When AHEAD_OF_TIME
is set, the behavior of goBack()
changes. Calling goBack()
when willHandleAheadOfTimeBack()
returns false throws an exception.
When AHEAD_OF_TIME
is set, ScopedServices.HandlesBack
will no longer be called (as it cannot return whether a
service WILL handle back or not), and should be replaced with registrations to the AheadOfTimeBackCallbackRegistry
.
When AHEAD_OF_TIME
is NOT set (and therefore the default, EVENT_BUBBLING
is set),
calling willHandleAheadOfTimeBack
or addAheadOfTimeWillHandleBackChangedListener
or removeAheadOfTimeWillHandleBackChangedListener
throws an exception.
To migrate to use the ahead-of-time back handling model, then you might have the previous
somewhat onBackPressedDispatcher
-compatible (but not predictive-back-gesture compatible) code:
class MainActivity : AppCompatActivity(), SimpleStateChanger.NavigationHandler {
private lateinit var fragmentStateChanger: DefaultFragmentStateChanger
@Suppress("DEPRECATION")
private val backPressedCallback = object: OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
if (!Navigator.onBackPressed(this@MainActivity)) {
this.remove()
onBackPressed() // this is the reliable way to handle back for now
this@MainActivity.onBackPressedDispatcher.addCallback(this)
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
onBackPressedDispatcher.addCallback(backPressedCallback) // this is the reliable way to handle back for now
val binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
fragmentStateChanger = DefaultFragmentStateChanger(supportFragmentManager, R.id.container)
Navigator.configure()
.setStateChanger(SimpleStateChanger(this))
.install(this, binding.container, History.single(HomeKey))
}
override fun onNavigationEvent(stateChange: StateChange) {
fragmentStateChanger.handleStateChange(stateChange)
}
}
This code changes to the following in order to support predictive back gesture using ahead-of-time model:
class MainActivity : AppCompatActivity(), SimpleStateChanger.NavigationHandler {
private lateinit var fragmentStateChanger: FragmentStateChanger
private lateinit var authenticationManager: AuthenticationManager
private lateinit var backstack: Backstack
private val backPressedCallback = object : OnBackPressedCallback(false) { // <-- !
override fun handleOnBackPressed() {
backstack.goBack()
}
}
private val updateBackPressedCallback = AheadOfTimeWillHandleBackChangedListener { // <-- !
backPressedCallback.isEnabled = it // <-- !
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.main_activity)
onBackPressedDispatcher.addCallback(backPressedCallback) // <-- !
fragmentStateChanger = FragmentStateChanger(supportFragmentManager, R.id.container)
backstack = Navigator.configure()
.setBackHandlingModel(BackHandlingModel.AHEAD_OF_TIME) // <-- !
.setStateChanger(SimpleStateChanger(this))
.install(this, binding.container, History.single(HomeKey))
backPressedCallback.isEnabled = backstack.willHandleAheadOfTimeBack() // <-- !
backstack.addAheadOfTimeWillHandleBackChangedListener(updateBackPressedCallback) // <-- !
}
override fun onDestroy() {
backstack.removeAheadOfTimeWillHandleBackChangedListener(updateBackPressedCallback); // <-- !
super.onDestroy()
}
override fun onNavigationEvent(stateChange: StateChange) {
fragmentStateChanger.handleStateChange(stateChange)
}
}
Please make sure to remove the AheadOfTimeWillHandleBackChangedListener
in onDestroy
(Activity) or onDestroyView
(
Fragment), because the listener staying registered would be a memory leak.
A "lifecycle-aware" callback might be added to simple-stack-extensions
later.
If you can't update to the AHEAD_OF_TIME
back handling model, then don't worry, as backwards compatibility has been
preserved with the previous behavior.
When using AHEAD_OF_TIME
back handling model, ScopedServices.HandlesBack
is no longer called. To replace this, you
might have had something like this:
class FragmentStackHost(
initialKey: Any
) : Bundleable, ScopedServices.HandlesBack {
var isActiveForBack: Boolean = false
// ...
override fun onBackEvent(): Boolean {
if (isActiveForBack) {
return backstack.goBack()
} else {
return false
}
}
}
This is replaced like so:
class FragmentStackHost(
initialKey: Any,
private val aheadOfTimeBackCallbackRegistry: AheadOfTimeBackCallbackRegistry,
) : Bundleable, ScopedServices.Registered {
var isActiveForBack: Boolean = false
set(value) {
field = value
backCallback.isEnabled = value && backstackWillHandleBack
}
private var backstackWillHandleBack = false
set(value) {
field = value
backCallback.isEnabled = isActiveForBack && value
}
private val backCallback = object : AheadOfTimeBackCallback(false) {
override fun onBackReceived() {
backstack.goBack()
}
}
private val willHandleBackChangedListener = AheadOfTimeWillHandleBackChangedListener {
backstackWillHandleBack = it
}
init {
// ...
backstackWillHandleBack = backstack.willHandleAheadOfTimeBack()
backstack.addAheadOfTimeWillHandleBackChangedListener(willHandleBackChangedListener)
}
override fun onServiceRegistered() {
aheadOfTimeBackCallbackRegistry.registerAheadOfTimeBackCallback(backCallback)
}
override fun onServiceUnregistered() {
aheadOfTimeBackCallbackRegistry.unregisterAheadOfTimeCallback(backCallback)
}
}
Where FragmentStackHost
gets the AheadOfTimeBackCallbackRegistry
from serviceBinder.getAheadOfTimeBackCallbackRegistry()
.
So in this snippet, whether back will be handled needs to be propagated up, and manage the enabled state of
the AheadOfTimeBackCallback
to intercept back if needed.
While this might seem a bit tricky, this is how Google does it in their own micromanagement of communicating with
the onBackPressedDispatcher
as well, so evaluating ahead of time who will want to handle back later is unavoidable.
- DEPRECATED:
BackstackDelegate.onBackPressed()
andNavigator.onBackPressed()
. Not only are they the same
asbackstack.goBack()
and merely managed to confuse people historically, but this deprecation mirros the deprecation
ofonBackPressed
in compileSdk 33, to push towards using predictive back.
Simple Stack 2.6.5
Simple-Stack 2.6.5 (2022-11-11)
-
FIX:
Backstack.CompletionListener
added toBackstack
that unregistered themselves during dispatching notifications
would cause either a ConcurrentModificationException or invalid results, this is now fixed and no longer the case (
#263, thanks @angusholder) -
MINOR CHANGE: When
Backstack.CompletionListener
's are being notified, the state changer is temporarily removed (
similarly to dispatchingScopedServices.Activated
events), so that navigation actions invoked onBackstack
are
deferred until allBackstack.CompletionListener
s are notified.
Simple Stack 2.6.4
Simple-Stack 2.6.4 (2022-04-21)
-
FIX: Attempt at fixing a crash related to
LinkedHashMap.retainAll()
specifically on Android 6 and Android 6.1 devices (#256). -
2.6.3 had an issue with
maven-publish
and transitive dependencies not getting resolved for consumers, therefore it is skipped.
Simple Stack 2.6.2
Simple Stack 2.6.2 (2021-06-07)
- ADDED:
Backstack.canSetScopeProviders()
.
This is in conjunction with the 2.6.1 change, while making it safe to use them without extra checks such as if(lastNonConfigurationInstance == null) {
. (see #243)
Simple Stack 2.6.1
Simple-Stack 2.6.1 (2021-05-03)
- CHANGE:
Backstack.setScopedServices(ScopedServices)
,Backstack.setGlobalServices(GlobalServices)
, andBackstack.setGlobalServices(GlobalServices.Factory)
can now be called aftersetup()
, but beforesetStateChanger()
.
This allows setting the scoped services on the backstack instance, when using deferred initialization, before the initial state change is run.
Simple Stack 2.6.0
Simple-Stack 2.6.0 (2021-03-08)
- ADD:
Backstack.addRetainedObject(objectTag, retainedObject)
,Backstack.hasRetainedObject(objectTag)
,Backstack.removeRetainedObject(objectTag)
,Backstack.getRetainedObject(objectTag)
.
This allows simpler way of persisting an object instance across configuration changes.
Also, retained objects that implement Bundleable
are given state restoration callbacks.
-
UPDATE: Add
simple-stack-example-multistack-nested-fragment
that shows how to create a fragment that hasBackstack
s for its child fragments, thus creating true multi-stack apps using nested backstacks. -
DEPRECATED:
Backstack.addCompletionListener
,Backstack.removeCompletionListener
,Backstack.removeCompletionListeners
.
These were the same as addStateChangeCompletionListener
and removeStateChangeCompletionListener
, and should not have been duplicate APIs.
Simple Stack 2.5.0
Simple Stack 2.5.0 (2020-12-16)
- ADD:
Backstack.exitScope(scopeTag)
,Backstack.exitScope(scopeTag, direction)
andBackstack.exitScopeTo(scopeTag, targetKey, direction)
.
If a scope is found, the backstack now allows exiting from it. Providing a target allows exiting into a new target key.
- ADD:
AsyncStateChanger
for convenience.
Mirroring the addition of SimpleStateChanger
for synchronous state changes, AsyncStateChanger
is for async state changes (while still no longer having to remember checking for the same key being provided using isTopNewKeyEqualToPrevious
).
- UPDATE:
state-bundle
is updated to1.4.0
(add a few missing@Nullable
s that became platform types instead of nullables).
Simple Stack 2.4.0
Simple-Stack 2.4.0 (2020-07-08)
- SIGNATURE CHANGE:
GlobalServices.Factory
now receivesBackstack
parameter increate()
. (#231)
I'm aware this is technically "breaking", but the effect should be minor, and hopefully shouldn't cause problems.`
The Backstack
cannot be added as a service
directly, but it can be added as an alias
.
-
FIX:
GlobalServices.Factory
'screate()
method was non-null, but@Nonnull
was missing. -
MINOR FIX: Adding the
Backstack
fromserviceBinder.getBackstack()
withaddService()
would cause a loop intoBundle()
. Now it explicitly throwsIllegalArgumentException
instead sooner (not reported before). -
DEPRECATED:
backstack.removeAllStateChangeCompletionListeners()
. This was added "for convenience", but in reality it is not a good/safe API, and it should not exist. -
UPDATE: Create and release
simple-stack-extensions:2.0.0
for default scoping and default fragment behaviors. -
ADD:
GlobalServices.SCOPE_TAG
to make it possible to see the scope tag of global services without relying on internals.