Skip to content

Commit

Permalink
1.12.1: ScopedServices.Activated (#110)
Browse files Browse the repository at this point in the history
  • Loading branch information
Zhuinden authored Aug 27, 2018
1 parent c4de94a commit 00842d2
Show file tree
Hide file tree
Showing 6 changed files with 805 additions and 4 deletions.
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
# Change log

-Simple Stack 1.12.1 (2018-08-28)
--------------------------------
- ADDED: `ScopedServices.Activated`. Implementing `Activated` for a scoped service makes the service receive a callback when the scope it is bound to becomes the top-most scope, and when it stops being the top-most scope.

There are strict ordering guarantees that `onEnterScope`, `onScopeActive`, `onScopeInactive`, `onExitScope` are called in *this* order.

`onScopeInactive` is called in reverse order (just like `onExitScope`).

When navigating from one scope to another scope, the new scope becomes active before the previous scope becomes inactive.

-Simple Stack 1.12.0 (2018-08-23)
--------------------------------
- CHANGE: `backstack.top()` and `backstack.root()` now throw exception (just like `fromTop()`) if the method is called before a StateChanger is set (and the backstack becomes initialized). This makes using `root()`/`top()` nicer in Kotlin.
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ In order to use Simple Stack, you need to add jitpack to your project root gradl

and add the compile dependency to your module level gradle.

compile 'com.github.Zhuinden:simple-stack:1.12.0'
compile 'com.github.Zhuinden:simple-stack:1.12.1'

## How does it work?

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ static String getScopesTag() {
return SCOPES_TAG;
}

private String activeScope = null;

private final StateChanger managedStateChanger = new StateChanger() {
@Override
public void handleStateChange(@NonNull final StateChange stateChange, @NonNull final Callback completionCallback) {
Expand All @@ -77,7 +79,31 @@ public void stateChangeComplete() {
completionCallback.stateChangeComplete();
if(!backstack.isStateChangePending()) {
stateClearStrategy.clearStatesNotIn(keyStateMap, stateChange);
scopeManager.clearScopesNotIn(stateChange.getNewState());

History<Object> newState = stateChange.getNewState();

// activation/deactivation
ScopeKey topScopeKey = null;
for(int i = 0, size = newState.size(); i < size; i++) {
Object key = newState.fromTop(i);
if(key instanceof ScopeKey) {
topScopeKey = (ScopeKey) key;
break;
}
}
String newScopeTag = null;
if(topScopeKey != null) {
newScopeTag = topScopeKey.getScopeTag();
}
if(activeScope == null /* no active scope */
|| (activeScope != null && !activeScope.equals(newScopeTag)) /* new active scope */) {
String previousScopeTag = activeScope;
activeScope = newScopeTag;
scopeManager.dispatchActivation(previousScopeTag, newScopeTag);
}

// scope eviction + scoped
scopeManager.clearScopesNotIn(newState);
}
}
});
Expand Down Expand Up @@ -231,6 +257,13 @@ public void reattachStateChanger() {
* Note that if you use {@link BackstackDelegate} or {@link com.zhuinden.simplestack.navigator.Navigator}, then there is no need to call this method manually.
*/
public void finalizeScopes() {
if(activeScope != null) {
String previousScopeTag = activeScope;
activeScope = null;
//noinspection ConstantConditions
scopeManager.dispatchActivation(previousScopeTag, activeScope);
}

List<Object> history = backstack.getHistory();
Set<String> scopeSet = new LinkedHashSet<>();
for(Object key : history) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.zhuinden.simplestack;

import android.support.annotation.NonNull;
import android.support.annotation.Nullable;

import com.zhuinden.statebundle.StateBundle;

Expand Down Expand Up @@ -33,7 +34,7 @@ void setBackstackManager(BackstackManager backstackManager) {
this.backstackManager = backstackManager;
}

public Backstack getBackstack() {
Backstack getBackstack() {
return backstackManager.getBackstack();
}

Expand Down Expand Up @@ -126,6 +127,36 @@ void destroyScope(String scopeTag) {
}
}

void dispatchActivation(@Nullable String previousScopeTag, @Nullable String newScopeTag) {
if(newScopeTag != null) {
if(!scopes.containsKey(newScopeTag)) {
throw new AssertionError(
"The new scope should exist, but it doesn't! This shouldn't happen. If you see this error, this functionality is broken.");
}
Map<String, Object> newServiceMap = scopes.get(newScopeTag);
for(Object service : newServiceMap.values()) {
if(service instanceof ScopedServices.Activated) {
((ScopedServices.Activated) service).onScopeActive(newScopeTag);
}
}
}

if(previousScopeTag != null) {
if(!scopes.containsKey(previousScopeTag)) {
throw new AssertionError(
"The previous scope should exist, but it doesn't! This shouldn't happen. If you see this error, this functionality is broken.");
}
Map<String, Object> previousServiceMap = scopes.get(previousScopeTag);
List<Object> previousServices = new ArrayList<>(previousServiceMap.values());
Collections.reverse(previousServices);
for(Object service : previousServices) {
if(service instanceof ScopedServices.Activated) {
((ScopedServices.Activated) service).onScopeInactive(previousScopeTag);
}
}
}
}

StateBundle saveStates() {
StateBundle rootBundle = new StateBundle();
for(Map.Entry<String, Map<String, Object>> scopeSet : scopes.entrySet()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,13 @@
*
* Services that implement {@link Scoped} will receive callbacks for when their scope is created, and their scope is destroyed.
*
* Services that implements {@link Activated} will receive callbacks for when their scope becomes the active (top-most) scope, or become inactive.
*
* NOTE: Think of it as configuration: it is kept across configuration change, so it should not reference the Activity directly.
*/
public interface ScopedServices {
/**
* When a service implements Scoped, then it will receive a callback when the service is registered/unregistered from the scope.
* When a service implements {@link Scoped}, then it will receive a callback when the service is registered/unregistered from the scope.
*/
public static interface Scoped {
/**
Expand All @@ -37,6 +39,25 @@ public static interface Scoped {
void onExitScope(@NonNull String scope);
}

/**
* When a service implements {@link Activated}, then it will receive a callback when the scope the service belongs to becomes the active (top-most) scope.
*/
public static interface Activated {
/**
* Called when the scope the service is bound to becomes the top-most scope.
*
* @param scope the tag of the scope
*/
void onScopeActive(@NonNull String scope);

/**
* Called when the scope is no longer the top-most scope.
*
* @param scope the tag of the scope
*/
void onScopeInactive(@NonNull String scope);
}

/**
* The {@link ServiceBinder} allows binding services to a given scope, when that scope is created for the first time.
*
Expand Down
Loading

0 comments on commit 00842d2

Please sign in to comment.