-
Notifications
You must be signed in to change notification settings - Fork 77
5. Scoping, data or state sharing between screens
If a given key exists within the navigation history, then if it implements ScopeKey
, its associated scope will be automatically created.
To determine the services that are to be bound to this scope, bindServices(ServiceBinder)
is called (in ScopedServices).
A key can also define additional shared scopes using ScopeKey.Child
.
You can set scoped services on either Backstack, or Navigator (which sets it on the Backstack automatically).
Navigator.configure()
.setScopedServices(new DefaultServiceProvider())
.install(...);
or
backstack.setScopedServices(new DefaultServiceProvider());
backstack.setup(...)
When using the DefaultServiceProvider
, then the key should implement DefaultServiceProvider.HasServices
to specify what services it wants to bind to its scope.
The binder is called only if the scope does not exist yet, so services for a given scope tag are only bound once.
The services are destroyed when there are no more keys with the associated scope tag found in the new history.
To be notified of when the service is created (and restored), and destroyed, it is possible to implement ScopedServices.Registered
.
To be notified of when the service's scope becomes the topmost scope (or stops being the topmost scope), it is possible to implement ScopedServices.Activated
.
To automatically persist/restore the state of a service to StateBundle
to survive across process death, the service can implement Bundleable
. (Yes, its state will be persisted/restored automatically.)
To find a service, we must find it by the service tag that it was registered with.
MyService service = Navigator.lookupService(context, serviceTag);
or
MyService service = backstack.lookupService(context, serviceTag);
When using the lookupService
method, Simple-Stack assumes a parent-child hierarchy across all scopes that exist within the navigation history. This means that you can find the service on a given scope tag even if it was bound for a previous scope. This allows sharing data across screens without having to parcel them (however, it is recommended to use an observable holder, like BehaviorRelay or LiveData).
The test in
/*
* PARENT0
* PARENT1
* PARENT2 PARENT3 PARENT4
* CHILD1 CHILD2 CHILD3 CHILD4 CHILD5
*/
backstackManager.setup(
History.of(
new ChildKey("C1", History.of("P0", "P1", "P2")),
new ChildKey("C2", History.of("P0", "P1", "P2")),
new ChildKey("C3", History.of("P0", "P1", "P3")),
new ChildKey("C4", History.of("P0", "P4")),
new ChildKey("C5", History.of("P0", "P4"))
)
);
is testing the following setup:
Note:
-
red
arrows showimplicit scopes
, created if the given key in the history is aScopeKey
-
black
arrows representexplicit scopes
, created if the given key in history is aScopeKey.Child
, and describes its parents with aList<String>
In which a traversal from the active-most scope (CHILD5) is the following order:
CHILD5 -> PARENT4 -> CHILD4 -> CHILD3 -> PARENT3 -> PARENT1 -> PARENT0 -> CHILD2 -> PARENT2 -> CHILD1
Because lookup prefers explicit parents, but it eventually goes to implicit parents, their explicit parents, then next implicit, and so on; but it doesn't check the same scope tag twice.
It is possible to add services to the global scope using one of the following:
Navigator.configure()
.setGlobalServices(GlobalServices.builder()
.add(...)
.build())
.install(...);
or
backstack.setGlobalServices(GlobalServices.builder()
.add(...)
.build())
backstack.setup(...)
It is also possible to pass a GlobalServices.Factory
instead of a GlobalServices
. However, this should not be an anonymous implementation, as that would cause memory leak (it is retained across config changes).
class GlobalsFactory: GlobalServices.Factory {
...
}
Navigator.configure()
.setGlobalServices(GlobalsFactory())
.install(...)
The global scope acts as a "shared parent" that is checked as the last, final scope for any lookup (either via implicit or explicit parent scopes). Therefore, the global scope is a shared parent to all scopes.