Compare commits

...

18 Commits

Author SHA1 Message Date
Eric Kuck dc68990bff Version bump 2016-09-26 10:32:23 -05:00
Eric Kuck e4f7e9e175 Fixes memory leak in CircularRevealAnimatorChangeHandler (and any other AnimatorChangeHandler that kept a reference to its view) 2016-09-26 09:50:29 -05:00
Eric Kuck 4ab99b68da Fixes #123 2016-09-26 09:34:41 -05:00
Eric Kuck b8bd64e078 Fixed leak in demo app 2016-09-26 09:24:32 -05:00
Eric Kuck 812d1f8911 Fixes issue where controllers that are being animated out, but haven't yet been detached, at the time of an orientation change could be incorrectly attached to the new Activity. 2016-09-24 11:24:13 -05:00
Eric Kuck ac8288fece Fixed issue where views would not detach when rapidly setting the root controller 2016-09-24 00:29:36 -05:00
Eric Kuck d2dd786b72 Fixed issue where setting the root controller twice in a row will cause the first controller's view to remain attached 2016-09-23 17:42:57 -05:00
Eric Kuck 7cf30b820c Travis yml fix 2016-09-06 16:44:45 -05:00
Eric Kuck 1120896438 Fixed some Leak Canary false-positives 2016-09-06 16:12:56 -05:00
Eric Kuck 093238cc52 Should fix #106 (more testing needed) 2016-09-06 16:10:47 -05:00
Eric Kuck c153e29273 Fixes #107 2016-09-06 15:49:30 -05:00
Eric Kuck 2757c7a4b6 Fixes #108 2016-09-06 15:46:25 -05:00
Eric Kuck 46091a7c99 Build tools update 2016-09-06 15:30:02 -05:00
Hannes Struß b0a5da2b82 Remove listeners after animator ends/is canceled (#104) 2016-09-05 10:43:22 -05:00
Adrian Pascu 0026566ba0 Nitpicks (#96)
* [Demo] Remove Butterknife unbinder from MainActivity

* Make deploy_snapshot.sh executable
2016-07-29 15:11:04 -05:00
dimsuz 9f640380bf Document a return value of Router.handleBack() (#93) 2016-07-20 17:49:59 -05:00
Eric Kuck a3faaede61 Fixes #97 and cleans up some tests 2016-07-20 15:41:19 -05:00
Eric Kuck 94d8add220 Fixes #98 2016-07-20 14:56:12 -05:00
24 changed files with 542 additions and 296 deletions
Regular → Executable
View File
+1 -4
View File
@@ -14,13 +14,10 @@ jdk:
- oraclejdk8
branches:
only
only:
- develop
- master
before_install:
- chmod +x .buildscript/deploy_snapshot.sh
after_success:
- .buildscript/deploy_snapshot.sh
+6 -6
View File
@@ -20,22 +20,22 @@ Conductor is architecture-agnostic and does not try to force any design decision
## Installation
```gradle
compile 'com.bluelinelabs:conductor:2.0.1'
compile 'com.bluelinelabs:conductor:2.0.2'
// If you want the components that go along with
// Android's support libraries (currently just a PagerAdapter):
compile 'com.bluelinelabs:conductor-support:2.0.1'
compile 'com.bluelinelabs:conductor-support:2.0.2'
// If you want RxJava/RxAndroid lifecycle support:
compile 'com.bluelinelabs:conductor-rxlifecycle:2.0.1'
compile 'com.bluelinelabs:conductor-rxlifecycle:2.0.2'
```
SNAPSHOT:
```gradle
compile 'com.bluelinelabs:conductor:2.0.2-SNAPSHOT'
compile 'com.bluelinelabs:conductor-support:2.0.2-SNAPSHOT'
compile 'com.bluelinelabs:conductor-rxlifecycle:2.0.2-SNAPSHOT'
compile 'com.bluelinelabs:conductor:2.0.3-SNAPSHOT'
compile 'com.bluelinelabs:conductor-support:2.0.3-SNAPSHOT'
compile 'com.bluelinelabs:conductor-rxlifecycle:2.0.3-SNAPSHOT'
```
You also have to add the url to the snapshot repository:
+1 -1
View File
@@ -3,7 +3,7 @@ buildscript {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.1.2'
classpath 'com.android.tools.build:gradle:2.2.0'
}
}
@@ -92,4 +92,9 @@ public class ActivityHostedRouter extends Router {
List<Router> getSiblingRouters() {
return lifecycleHandler.getRouters();
}
@Override
Router getRootRouter() {
return this;
}
}
@@ -24,6 +24,7 @@ import com.bluelinelabs.conductor.ControllerChangeHandler.ControllerChangeListen
import com.bluelinelabs.conductor.internal.ClassUtils;
import com.bluelinelabs.conductor.internal.RouterRequiringFunc;
import java.lang.ref.WeakReference;
import java.lang.reflect.Constructor;
import java.util.ArrayDeque;
import java.util.ArrayList;
@@ -84,6 +85,7 @@ public abstract class Controller {
private final ArrayList<String> requestedPermissions = new ArrayList<>();
private final ArrayList<RouterRequiringFunc> onRouterSetListeners = new ArrayList<>();
private final Deque<Controller> childBackstack = new ArrayDeque<>();
private WeakReference<View> destroyedView;
private final ControllerChangeListener childRouterChangeListener = new ControllerChangeListener() {
@Override
@@ -313,7 +315,10 @@ public abstract class Controller {
* @return This Controller's target
*/
public final Controller getTargetController() {
return targetInstanceId != null ? router.getControllerWithInstanceId(targetInstanceId) : null;
if (targetInstanceId != null) {
return router.getRootRouter().getControllerWithInstanceId(targetInstanceId);
}
return null;
}
/**
@@ -780,6 +785,10 @@ public abstract class Controller {
view.removeOnAttachStateChangeListener(onAttachStateChangeListener);
viewIsAttached = false;
if (isBeingDestroyed) {
destroyedView = new WeakReference<>(view);
}
view = null;
for (LifecycleListener lifecycleListener : lifecycleListeners) {
@@ -842,7 +851,7 @@ public abstract class Controller {
return view;
}
final void performDestroy() {
private void performDestroy() {
if (!destroyed) {
for (LifecycleListener lifecycleListener : lifecycleListeners) {
lifecycleListener.preDestroy(this);
@@ -850,10 +859,6 @@ public abstract class Controller {
destroyed = true;
if (router != null) {
router.unregisterForActivityResults(instanceId);
}
onDestroy();
parentController = null;
@@ -868,9 +873,13 @@ public abstract class Controller {
destroy(false);
}
final void destroy(boolean removeViews) {
private void destroy(boolean removeViews) {
isBeingDestroyed = true;
if (router != null) {
router.unregisterForActivityResults(instanceId);
}
for (ControllerHostedRouter childRouter : childRouters) {
childRouter.destroy();
}
@@ -882,7 +891,7 @@ public abstract class Controller {
}
}
final void saveViewState(@NonNull View view) {
private void saveViewState(@NonNull View view) {
hasSavedViewState = true;
viewState = new Bundle();
@@ -900,7 +909,7 @@ public abstract class Controller {
}
}
final void restoreViewState(@NonNull View view) {
private void restoreViewState(@NonNull View view) {
if (viewState != null) {
view.restoreHierarchyState(viewState.getSparseParcelableArray(KEY_VIEW_STATE_HIERARCHY));
onRestoreViewState(view, viewState.getBundle(KEY_VIEW_STATE_BUNDLE));
@@ -1025,6 +1034,13 @@ public abstract class Controller {
for (LifecycleListener lifecycleListener : lifecycleListeners) {
lifecycleListener.onChangeEnd(this, changeHandler, changeType);
}
if (isBeingDestroyed && !viewIsAttached && !attached && destroyedView != null) {
View view = destroyedView.get();
if (router.container != null && view.getParent() == router.container) {
router.container.removeView(view);
}
}
}
final void setDetachFrozen(boolean frozen) {
@@ -1062,13 +1078,15 @@ public abstract class Controller {
}
private void onChildControllerPushed(Controller controller) {
childBackstack.add(controller);
controller.addLifecycleListener(new LifecycleListener() {
@Override
public void postDestroy(@NonNull Controller controller) {
childBackstack.remove(controller);
}
});
if (!childBackstack.contains(controller)) {
childBackstack.add(controller);
controller.addLifecycleListener(new LifecycleListener() {
@Override
public void postDestroy(@NonNull Controller controller) {
childBackstack.remove(controller);
}
});
}
}
final void setParentController(Controller controller) {
@@ -28,10 +28,10 @@ public abstract class ControllerChangeHandler {
/**
* Responsible for swapping Views from one Controller to another.
*
* @param container The container these Views are hosted in.
* @param from The previous View in the container, if any.
* @param to The next View that should be put in the container, if any.
* @param isPush True if this is a push transaction, false if it's a pop.
* @param container The container these Views are hosted in.
* @param from The previous View in the container, if any.
* @param to The next View that should be put in the container, if any.
* @param isPush True if this is a push transaction, false if it's a pop.
* @param changeListener This listener must be called when any transitions or animations are completed.
*/
public abstract void performChange(@NonNull ViewGroup container, View from, View to, boolean isPush, @NonNull ControllerChangeCompletedListener changeListener);
@@ -59,10 +59,16 @@ public abstract class ControllerChangeHandler {
* popped before it has completed.
*
* @param newHandler the change handler that has caused this push to be aborted
* @param newTop the controller that will now be at the top of the backstack
* @param newTop the controller that will now be at the top of the backstack
*/
public void onAbortPush(@NonNull ControllerChangeHandler newHandler, Controller newTop) { }
/**
* Will be called on change handlers that push a controller if the controller being pushed is
* needs to be attached immediately, without any animations or transitions.
*/
public void completeImmediately() { }
final Bundle toBundle() {
Bundle bundle = new Bundle();
bundle.putString(KEY_CLASS_NAME, getClass().getName());
@@ -94,6 +100,21 @@ public abstract class ControllerChangeHandler {
}
}
public static void completePushImmediately(String controllerInstanceId) {
ControllerChangeHandler changeHandler = inProgressPushHandlers.get(controllerInstanceId);
if (changeHandler != null) {
changeHandler.completeImmediately();
}
}
public static void abortPush(Controller toAbort, Controller newController, ControllerChangeHandler newChangeHandler) {
ControllerChangeHandler handlerForPush = inProgressPushHandlers.get(toAbort.getInstanceId());
if (handlerForPush != null) {
handlerForPush.onAbortPush(newChangeHandler, newController);
inProgressPushHandlers.remove(toAbort.getInstanceId());
}
}
public static void executeChange(final Controller to, final Controller from, boolean isPush, ViewGroup container, ControllerChangeHandler inHandler) {
executeChange(to, from, isPush, container, inHandler, new ArrayList<ControllerChangeListener>());
}
@@ -102,14 +123,10 @@ public abstract class ControllerChangeHandler {
if (container != null) {
final ControllerChangeHandler handler = inHandler != null ? inHandler : new SimpleSwapChangeHandler();
if (isPush) {
if (isPush && to != null) {
inProgressPushHandlers.put(to.getInstanceId(), handler);
} else if (from != null) {
ControllerChangeHandler handlerForPush = inProgressPushHandlers.get(from.getInstanceId());
if (handlerForPush != null) {
handlerForPush.onAbortPush(handler, to);
inProgressPushHandlers.remove(from.getInstanceId());
}
} else if (!isPush && from != null) {
abortPush(from, to, handler);
}
for (ControllerChangeListener listener : listeners) {
@@ -155,6 +172,10 @@ public abstract class ControllerChangeHandler {
}
}
public boolean removesFromViewOnPush() {
return true;
}
/**
* A listener interface useful for allowing external classes to be notified of change events.
*/
@@ -162,21 +183,22 @@ public abstract class ControllerChangeHandler {
/**
* Called when a {@link ControllerChangeHandler} has started changing {@link Controller}s
*
* @param to The new Controller
* @param from The old Controller
* @param isPush True if this is a push operation, or false if it's a pop.
* @param to The new Controller
* @param from The old Controller
* @param isPush True if this is a push operation, or false if it's a pop.
* @param container The containing ViewGroup
* @param handler The change handler being used.
* @param handler The change handler being used.
*/
void onChangeStarted(Controller to, Controller from, boolean isPush, ViewGroup container, ControllerChangeHandler handler);
/**
* Called when a {@link ControllerChangeHandler} has completed changing {@link Controller}s
* @param to The new Controller
* @param from The old Controller
* @param isPush True if this was a push operation, or false if it's a pop.
*
* @param to The new Controller
* @param from The old Controller
* @param isPush True if this was a push operation, or false if it's a pop.
* @param container The containing ViewGroup
* @param handler The change handler that was used.
* @param handler The change handler that was used.
*/
void onChangeCompleted(Controller to, Controller from, boolean isPush, ViewGroup container, ControllerChangeHandler handler);
}
@@ -52,7 +52,7 @@ class ControllerHostedRouter extends Router {
controller.detach(controller.getView(), true);
}
}
for (RouterTransaction transaction : backStack) {
for (RouterTransaction transaction : backstack) {
if (transaction.controller.getView() != null) {
transaction.controller.detach(transaction.controller.getView(), true);
}
@@ -64,11 +64,17 @@ class ControllerHostedRouter extends Router {
}
final void setDetachFrozen(boolean frozen) {
for (RouterTransaction transaction : backStack) {
for (RouterTransaction transaction : backstack) {
transaction.controller.setDetachFrozen(frozen);
}
}
@Override
void destroy() {
setDetachFrozen(false);
super.destroy();
}
@Override
public Activity getActivity() {
return hostController != null ? hostController.getActivity() : null;
@@ -179,4 +185,13 @@ class ControllerHostedRouter extends Router {
list.addAll(hostController.getRouter().getSiblingRouters());
return list;
}
@Override
Router getRootRouter() {
if (hostController != null && hostController.getRouter() != null) {
return hostController.getRouter().getRootRouter();
} else {
return this;
}
}
}
@@ -16,6 +16,7 @@ import com.bluelinelabs.conductor.changehandler.SimpleSwapChangeHandler;
import com.bluelinelabs.conductor.internal.NoOpControllerChangeHandler;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
@@ -29,7 +30,7 @@ public abstract class Router {
private static final String KEY_BACKSTACK = "Router.backstack";
private static final String KEY_POPS_LAST_VIEW = "Router.popsLastView";
protected final Backstack backStack = new Backstack();
protected final Backstack backstack = new Backstack();
private final List<ControllerChangeListener> changeListeners = new ArrayList<>();
final List<Controller> destroyingControllers = new ArrayList<>();
@@ -71,10 +72,12 @@ public abstract class Router {
/**
* This should be called by the host Activity when its onBackPressed method is called. The call will be forwarded
* to its top {@link Controller}. If that controller doesn't handle it, then it will be popped.
*
* @return Whether or not a back action was handled by the Router
*/
public boolean handleBack() {
if (!backStack.isEmpty()) {
if (backStack.peek().controller.handleBack()) {
if (!backstack.isEmpty()) {
if (backstack.peek().controller.handleBack()) {
return true;
} else if (popCurrentController()) {
return true;
@@ -90,7 +93,7 @@ public abstract class Router {
* @return Whether or not this Router still has controllers remaining on it after popping.
*/
public boolean popCurrentController() {
return popController(backStack.peek().controller);
return popController(backstack.peek().controller);
}
/**
@@ -100,28 +103,28 @@ public abstract class Router {
* @return Whether or not this Router still has controllers remaining on it after popping.
*/
public boolean popController(Controller controller) {
RouterTransaction topController = backStack.peek();
RouterTransaction topController = backstack.peek();
boolean poppingTopController = topController != null && topController.controller == controller;
if (poppingTopController) {
trackDestroyingController(backStack.pop());
trackDestroyingController(backstack.pop());
} else {
for (RouterTransaction transaction : backStack) {
for (RouterTransaction transaction : backstack) {
if (transaction.controller == controller) {
backStack.remove(transaction);
backstack.remove(transaction);
break;
}
}
}
if (poppingTopController) {
performControllerChange(backStack.peek(), topController, false);
performControllerChange(backstack.peek(), topController, false);
}
if (popsLastView) {
return topController != null;
} else {
return !backStack.isEmpty();
return !backstack.isEmpty();
}
}
@@ -132,7 +135,7 @@ public abstract class Router {
* and its push and pop {@link ControllerChangeHandler}, and its tag.
*/
public void pushController(@NonNull RouterTransaction transaction) {
RouterTransaction from = backStack.peek();
RouterTransaction from = backstack.peek();
pushToBackstack(transaction);
performControllerChange(transaction, from, true);
}
@@ -144,9 +147,9 @@ public abstract class Router {
* and its push and pop {@link ControllerChangeHandler}, and its tag.
*/
public void replaceTopController(@NonNull RouterTransaction transaction) {
RouterTransaction topTransaction = backStack.peek();
if (!backStack.isEmpty()) {
trackDestroyingController(backStack.pop());
RouterTransaction topTransaction = backstack.peek();
if (!backstack.isEmpty()) {
trackDestroyingController(backstack.pop());
}
pushToBackstack(transaction);
@@ -155,7 +158,7 @@ public abstract class Router {
void destroy() {
popsLastView = true;
List<RouterTransaction> poppedControllers = backStack.popAll();
List<RouterTransaction> poppedControllers = backstack.popAll();
if (poppedControllers.size() > 0) {
trackDestroyingControllers(poppedControllers);
@@ -164,9 +167,13 @@ public abstract class Router {
}
}
public int getContainerId() {
return container != null ? container.getId() : 0;
}
/**
* If set to true, this router will handle back presses by performing a change handler on the last controller and view
* in the stack. This defaults to false so that the developer can either finish its containing Activity or otherwise
* in the stack. This defaults to false so that the developer can either finish its containing Activity or otherwise
* hide its parent view without any strange artifacting.
*/
public Router setPopsLastView(boolean popsLastView) {
@@ -190,8 +197,8 @@ public abstract class Router {
* @return Whether or not any {@link Controller}s were popped in order to get to the root transaction
*/
public boolean popToRoot(ControllerChangeHandler changeHandler) {
if (backStack.size() > 1) {
popToTransaction(backStack.root(), changeHandler);
if (backstack.size() > 1) {
popToTransaction(backstack.root(), changeHandler);
return true;
} else {
return false;
@@ -216,7 +223,7 @@ public abstract class Router {
* @return Whether or not the {@link Controller} with the passed tag is now at the top
*/
public boolean popToTag(@NonNull String tag, ControllerChangeHandler changeHandler) {
for (RouterTransaction transaction : backStack) {
for (RouterTransaction transaction : backstack) {
if (tag.equals(transaction.tag())) {
popToTransaction(transaction, changeHandler);
return true;
@@ -232,13 +239,29 @@ public abstract class Router {
* and its push and pop {@link ControllerChangeHandler}, and its tag.
*/
public void setRoot(@NonNull RouterTransaction transaction) {
RouterTransaction currentTop = backStack.peek();
removeAllExceptTopAndUnowned();
ControllerChangeHandler newHandler = transaction.pushChangeHandler() != null ? transaction.pushChangeHandler() : new SimpleSwapChangeHandler();
trackDestroyingControllers(backStack.popAll());
List<RouterTransaction> visibleTransactions = getVisibleTransactions(backstack.iterator());
RouterTransaction rootTransaction = visibleTransactions.size() > 0 ? visibleTransactions.get(0) : null;
removeAllExceptVisibleAndUnowned();
trackDestroyingControllers(backstack.popAll());
pushToBackstack(transaction);
performControllerChange(transaction, currentTop, true);
for (int i = visibleTransactions.size() - 1; i > 0; i--) {
if (visibleTransactions.get(i).controller.getView() == null) {
ControllerChangeHandler.abortPush(visibleTransactions.get(i).controller, transaction.controller, newHandler);
} else {
performControllerChange(null, visibleTransactions.get(i).controller, true, newHandler);
}
}
if (rootTransaction != null && rootTransaction.controller.getView() == null) {
ControllerChangeHandler.abortPush(rootTransaction.controller, transaction.controller, newHandler);
}
performControllerChange(transaction, rootTransaction, true);
}
/**
@@ -248,7 +271,7 @@ public abstract class Router {
* @return The matching Controller, if one exists
*/
public Controller getControllerWithInstanceId(String instanceId) {
for (RouterTransaction transaction : backStack) {
for (RouterTransaction transaction : backstack) {
Controller controllerWithId = transaction.controller.findController(instanceId);
if (controllerWithId != null) {
return controllerWithId;
@@ -264,7 +287,7 @@ public abstract class Router {
* @return The matching Controller, if one exists
*/
public Controller getControllerWithTag(String tag) {
for (RouterTransaction transaction : backStack) {
for (RouterTransaction transaction : backstack) {
if (tag.equals(transaction.tag())) {
return transaction.controller;
}
@@ -276,7 +299,7 @@ public abstract class Router {
* Returns the number of {@link Controller}s currently in the backstack
*/
public int getBackstackSize() {
return backStack.size();
return backstack.size();
}
/**
@@ -284,7 +307,7 @@ public abstract class Router {
*/
public List<RouterTransaction> getBackstack() {
List<RouterTransaction> list = new ArrayList<>();
Iterator<RouterTransaction> backstackIterator = backStack.reverseIterator();
Iterator<RouterTransaction> backstackIterator = backstack.reverseIterator();
while (backstackIterator.hasNext()) {
list.add(backstackIterator.next());
}
@@ -295,20 +318,45 @@ public abstract class Router {
* Sets the backstack, transitioning from the current top controller to the top of the new stack (if different)
* using the passed {@link ControllerChangeHandler}
*
* @param backstack The new backstack
* @param newBackstack The new backstack
* @param changeHandler An optional change handler to be used to handle the transition
*/
public void setBackstack(@NonNull List<RouterTransaction> backstack, ControllerChangeHandler changeHandler) {
RouterTransaction oldTopTransaction = backStack.peek();
Controller oldTop = oldTopTransaction != null ? oldTopTransaction.controller : null;
removeAllExceptTopAndUnowned();
public void setBackstack(@NonNull List<RouterTransaction> newBackstack, ControllerChangeHandler changeHandler) {
List<RouterTransaction> oldVisibleTransactions = getVisibleTransactions(backstack.iterator());
if (backstack.size() > 0) {
Controller newTop = backstack.get(backstack.size() - 1).controller;
removeAllExceptVisibleAndUnowned();
if (newTop != oldTop) {
if (newBackstack.size() > 0) {
List<RouterTransaction> reverseNewBackstack = new ArrayList<>(newBackstack);
Collections.reverse(reverseNewBackstack);
List<RouterTransaction> newVisibleTransactions = getVisibleTransactions(reverseNewBackstack.iterator());
boolean visibleTransactionsChanged = newVisibleTransactions.size() != oldVisibleTransactions.size();
if (!visibleTransactionsChanged) {
for (int i = 0; i < oldVisibleTransactions.size(); i++) {
if (oldVisibleTransactions.get(i).controller != newVisibleTransactions.get(i).controller) {
visibleTransactionsChanged = true;
break;
}
}
}
if (visibleTransactionsChanged) {
ControllerChangeHandler handler = changeHandler != null ? changeHandler : new SimpleSwapChangeHandler();
performControllerChange(newTop, oldTop, true, handler);
Controller rootController = oldVisibleTransactions.size() > 0 ? oldVisibleTransactions.get(0).controller : null;
performControllerChange(newVisibleTransactions.get(0).controller, rootController, true, handler);
for (int i = oldVisibleTransactions.size() - 1; i > 0; i--) {
RouterTransaction transaction = oldVisibleTransactions.get(i);
performControllerChange(null, transaction.controller, true, handler);
}
for (int i = 1; i < newVisibleTransactions.size(); i++) {
RouterTransaction transaction = newVisibleTransactions.get(i);
handler = transaction.pushChangeHandler() != null ? transaction.pushChangeHandler(): new SimpleSwapChangeHandler();
performControllerChange(transaction.controller, newVisibleTransactions.get(i - 1).controller, true, handler);
}
}
}
@@ -316,7 +364,7 @@ public abstract class Router {
transaction.onAttachedToRouter();
}
backStack.setBackstack(backstack);
backstack.setBackstack(newBackstack);
}
/**
@@ -350,7 +398,7 @@ public abstract class Router {
* Attaches this Router's existing backstack to its container if one exists.
*/
public void rebindIfNeeded() {
Iterator<RouterTransaction> backstackIterator = backStack.reverseIterator();
Iterator<RouterTransaction> backstackIterator = backstack.reverseIterator();
while (backstackIterator.hasNext()) {
RouterTransaction transaction = backstackIterator.next();
@@ -368,7 +416,7 @@ public abstract class Router {
}
public final void onActivityStarted(Activity activity) {
for (RouterTransaction transaction : backStack) {
for (RouterTransaction transaction : backstack) {
transaction.controller.activityStarted(activity);
for (Router childRouter : transaction.controller.getChildRouters()) {
@@ -378,7 +426,7 @@ public abstract class Router {
}
public final void onActivityResumed(Activity activity) {
for (RouterTransaction transaction : backStack) {
for (RouterTransaction transaction : backstack) {
transaction.controller.activityResumed(activity);
for (Router childRouter : transaction.controller.getChildRouters()) {
@@ -388,7 +436,7 @@ public abstract class Router {
}
public final void onActivityPaused(Activity activity) {
for (RouterTransaction transaction : backStack) {
for (RouterTransaction transaction : backstack) {
transaction.controller.activityPaused(activity);
for (Router childRouter : transaction.controller.getChildRouters()) {
@@ -398,7 +446,7 @@ public abstract class Router {
}
public final void onActivityStopped(Activity activity) {
for (RouterTransaction transaction : backStack) {
for (RouterTransaction transaction : backstack) {
transaction.controller.activityStopped(activity);
for (Router childRouter : transaction.controller.getChildRouters()) {
@@ -411,7 +459,7 @@ public abstract class Router {
prepareForContainerRemoval();
changeListeners.clear();
for (RouterTransaction transaction : backStack) {
for (RouterTransaction transaction : backstack) {
transaction.controller.activityDestroyed(activity.isChangingConfigurations());
for (Router childRouter : transaction.controller.getChildRouters()) {
@@ -432,7 +480,8 @@ public abstract class Router {
}
public void prepareForHostDetach() {
for (RouterTransaction transaction : backStack) {
for (RouterTransaction transaction : backstack) {
ControllerChangeHandler.completePushImmediately(transaction.controller.getInstanceId());
transaction.controller.prepareForHostDetach();
}
}
@@ -441,7 +490,7 @@ public abstract class Router {
prepareForHostDetach();
Bundle backstackState = new Bundle();
backStack.saveInstanceState(backstackState);
backstack.saveInstanceState(backstackState);
outState.putParcelable(KEY_BACKSTACK, backstackState);
outState.putBoolean(KEY_POPS_LAST_VIEW, popsLastView);
@@ -449,17 +498,17 @@ public abstract class Router {
public void restoreInstanceState(Bundle savedInstanceState) {
Bundle backstackBundle = savedInstanceState.getParcelable(KEY_BACKSTACK);
backStack.restoreInstanceState(backstackBundle);
backstack.restoreInstanceState(backstackBundle);
popsLastView = savedInstanceState.getBoolean(KEY_POPS_LAST_VIEW);
Iterator<RouterTransaction> backstackIterator = backStack.reverseIterator();
Iterator<RouterTransaction> backstackIterator = backstack.reverseIterator();
while (backstackIterator.hasNext()) {
setControllerRouter(backstackIterator.next().controller);
}
}
public final void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
for (RouterTransaction transaction : backStack) {
for (RouterTransaction transaction : backstack) {
transaction.controller.createOptionsMenu(menu, inflater);
for (Router childRouter : transaction.controller.getChildRouters()) {
@@ -469,7 +518,7 @@ public abstract class Router {
}
public final void onPrepareOptionsMenu(Menu menu) {
for (RouterTransaction transaction : backStack) {
for (RouterTransaction transaction : backstack) {
transaction.controller.prepareOptionsMenu(menu);
for (Router childRouter : transaction.controller.getChildRouters()) {
@@ -479,7 +528,7 @@ public abstract class Router {
}
public final boolean onOptionsItemSelected(MenuItem item) {
for (RouterTransaction transaction : backStack) {
for (RouterTransaction transaction : backstack) {
if (transaction.controller.optionsItemSelected(item)) {
return true;
}
@@ -494,8 +543,8 @@ public abstract class Router {
}
private void popToTransaction(@NonNull RouterTransaction transaction, ControllerChangeHandler changeHandler) {
RouterTransaction topTransaction = backStack.peek();
List<RouterTransaction> poppedTransactions = backStack.popTo(transaction);
RouterTransaction topTransaction = backstack.peek();
List<RouterTransaction> poppedTransactions = backstack.popTo(transaction);
trackDestroyingControllers(poppedTransactions);
if (poppedTransactions.size() > 0) {
@@ -503,7 +552,7 @@ public abstract class Router {
changeHandler = topTransaction.popChangeHandler();
}
performControllerChange(backStack.peek().controller, topTransaction.controller, false, changeHandler);
performControllerChange(backstack.peek().controller, topTransaction.controller, false, changeHandler);
}
}
@@ -516,7 +565,7 @@ public abstract class Router {
final List<Controller> getControllers() {
List<Controller> controllers = new ArrayList<>();
Iterator<RouterTransaction> backstackIterator = backStack.reverseIterator();
Iterator<RouterTransaction> backstackIterator = backstack.reverseIterator();
while (backstackIterator.hasNext()) {
controllers.add(backstackIterator.next().controller);
}
@@ -525,7 +574,7 @@ public abstract class Router {
}
public final Boolean handleRequestedPermission(@NonNull String permission) {
for (RouterTransaction transaction : backStack) {
for (RouterTransaction transaction : backstack) {
if (transaction.controller.didRequestPermission(permission)) {
return transaction.controller.shouldShowRequestPermissionRationale(permission);
}
@@ -557,7 +606,7 @@ public abstract class Router {
private void performControllerChange(final Controller to, final Controller from, boolean isPush, @NonNull ControllerChangeHandler changeHandler) {
if (to != null) {
setControllerRouter(to);
} else if (backStack.size() == 0 && !popsLastView) {
} else if (backstack.size() == 0 && !popsLastView) {
// We're emptying out the backstack. Views get weird if you transition them out, so just no-op it. The hosting
// Activity should be handling this by finishing or at least hiding this view.
changeHandler = new NoOpControllerChangeHandler();
@@ -567,7 +616,7 @@ public abstract class Router {
}
void pushToBackstack(@NonNull RouterTransaction entry) {
backStack.push(entry);
backstack.push(entry);
}
private void trackDestroyingController(RouterTransaction transaction) {
@@ -589,13 +638,13 @@ public abstract class Router {
}
}
private void removeAllExceptTopAndUnowned() {
private void removeAllExceptVisibleAndUnowned() {
List<View> views = new ArrayList<>();
RouterTransaction topTransaction = backStack.peek();
final View topView = topTransaction != null ? topTransaction.controller.getView() : null;
if (topView != null) {
views.add(topView);
for (RouterTransaction transaction : getVisibleTransactions(backstack.iterator())) {
if (transaction.controller.getView() != null) {
views.add(transaction.controller.getView());
}
}
for (Router router : getSiblingRouters()) {
@@ -625,6 +674,21 @@ public abstract class Router {
}
}
private List<RouterTransaction> getVisibleTransactions(Iterator<RouterTransaction> backstackIterator) {
List<RouterTransaction> transactions = new ArrayList<>();
while (backstackIterator.hasNext()) {
RouterTransaction transaction = backstackIterator.next();
transactions.add(transaction);
if (transaction.pushChangeHandler() == null || transaction.pushChangeHandler().removesFromViewOnPush()) {
break;
}
}
Collections.reverse(transactions);
return transactions;
}
void setControllerRouter(Controller controller) {
controller.setRouter(this);
}
@@ -638,4 +702,5 @@ public abstract class Router {
abstract void requestPermissions(String instanceId, String[] permissions, int requestCode);
abstract boolean hasHost();
abstract List<Router> getSiblingRouters();
abstract Router getRootRouter();
}
@@ -24,6 +24,7 @@ public abstract class AnimatorChangeHandler extends ControllerChangeHandler {
private long animationDuration;
private boolean removesFromViewOnPush;
private boolean canceled;
private boolean needsImmediateCompletion;
private Animator animator;
public AnimatorChangeHandler() {
@@ -67,10 +68,21 @@ public abstract class AnimatorChangeHandler extends ControllerChangeHandler {
}
}
@Override
public void completeImmediately() {
super.completeImmediately();
needsImmediateCompletion = true;
if (animator != null) {
animator.cancel();
}
}
public long getAnimationDuration() {
return animationDuration;
}
@Override
public boolean removesFromViewOnPush() {
return removesFromViewOnPush;
}
@@ -99,7 +111,7 @@ public abstract class AnimatorChangeHandler extends ControllerChangeHandler {
if (addingToView) {
if (isPush || from == null) {
container.addView(to);
} else {
} else if (to.getParent() == null) {
container.addView(to, container.indexOfChild(from));
}
@@ -139,7 +151,13 @@ public abstract class AnimatorChangeHandler extends ControllerChangeHandler {
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationCancel(Animator animation) {
if (from != null && (!isPush || removesFromViewOnPush) && needsImmediateCompletion) {
container.removeView(from);
}
changeListener.onChangeCompleted();
animator.removeListener(this);
animator = null;
}
@Override
@@ -149,12 +167,16 @@ public abstract class AnimatorChangeHandler extends ControllerChangeHandler {
}
changeListener.onChangeCompleted();
animator.removeListener(this);
if (isPush && from != null) {
resetFromView(from);
}
animator = null;
}
});
animator.start();
}
@@ -5,6 +5,7 @@ import android.support.annotation.NonNull;
import android.view.View;
import android.view.ViewGroup;
import com.bluelinelabs.conductor.Controller;
import com.bluelinelabs.conductor.ControllerChangeHandler;
/**
@@ -16,6 +17,8 @@ public class SimpleSwapChangeHandler extends ControllerChangeHandler {
private boolean removesFromViewOnPush;
private boolean canceled;
public SimpleSwapChangeHandler() {
this(true);
}
@@ -37,16 +40,29 @@ public class SimpleSwapChangeHandler extends ControllerChangeHandler {
}
@Override
public void performChange(@NonNull ViewGroup container, View from, View to, boolean isPush, @NonNull final ControllerChangeCompletedListener changeListener) {
if (from != null && (!isPush || removesFromViewOnPush)) {
container.removeView(from);
}
public void onAbortPush(@NonNull ControllerChangeHandler newHandler, Controller newTop) {
super.onAbortPush(newHandler, newTop);
if (to != null && to.getParent() == null) {
container.addView(to);
canceled = true;
}
@Override
public void performChange(@NonNull ViewGroup container, View from, View to, boolean isPush, @NonNull final ControllerChangeCompletedListener changeListener) {
if (!canceled) {
if (from != null && (!isPush || removesFromViewOnPush)) {
container.removeView(from);
}
if (to != null && to.getParent() == null) {
container.addView(to);
}
}
changeListener.onChangeCompleted();
}
}
@Override
public boolean removesFromViewOnPush() {
return removesFromViewOnPush;
}
}
@@ -76,4 +76,8 @@ public abstract class TransitionChangeHandler extends ControllerChangeHandler {
}
}
@Override
public final boolean removesFromViewOnPush() {
return true;
}
}
@@ -77,4 +77,12 @@ public class TransitionChangeHandlerCompat extends ControllerChangeHandler {
fallbackChangeHandler.restoreFromBundle(bundle.getBundle(KEY_FALLBACK_HANDLER_STATE));
}
@Override
public boolean removesFromViewOnPush() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
return transitionChangeHandler.removesFromViewOnPush();
} else {
return fallbackChangeHandler.removesFromViewOnPush();
}
}
}
@@ -29,6 +29,7 @@ public class LifecycleHandler extends Fragment implements ActivityLifecycleCallb
private static final String KEY_PERMISSION_REQUEST_CODES = "LifecycleHandler.permissionRequests";
private static final String KEY_ACTIVITY_REQUEST_CODES = "LifecycleHandler.activityRequests";
private static final String KEY_ROUTER_STATE_PREFIX = "LifecycleHandler.routerState";
private Activity activity;
private boolean hasRegisteredCallbacks;
@@ -69,7 +70,10 @@ public class LifecycleHandler extends Fragment implements ActivityLifecycleCallb
router.setHost(this, container);
if (savedInstanceState != null) {
router.restoreInstanceState(savedInstanceState);
Bundle routerSavedState = savedInstanceState.getBundle(KEY_ROUTER_STATE_PREFIX + router.getContainerId());
if (routerSavedState != null) {
router.restoreInstanceState(routerSavedState);
}
}
routerMap.put(getRouterHashKey(container), router);
} else {
@@ -303,7 +307,9 @@ public class LifecycleHandler extends Fragment implements ActivityLifecycleCallb
public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
if (this.activity == activity) {
for (Router router : routerMap.values()) {
router.saveInstanceState(outState);
Bundle bundle = new Bundle();
router.saveInstanceState(bundle);
outState.putBundle(KEY_ROUTER_STATE_PREFIX + router.getContainerId(), bundle);
}
}
}
@@ -8,7 +8,7 @@ import android.view.ViewGroup;
import android.widget.FrameLayout;
import com.bluelinelabs.conductor.Controller.LifecycleListener;
import com.bluelinelabs.conductor.ControllerChangeHandler.ControllerChangeCompletedListener;
import com.bluelinelabs.conductor.MockChangeHandler.ChangeHandlerListener;
import org.junit.Assert;
import org.junit.Before;
@@ -377,22 +377,8 @@ public class ControllerLifecycleTests {
});
router.pushController(RouterTransaction.with(testController)
.pushChangeHandler(new ChangeHandler(new ChangeHandlerListener() {
@Override
public void performChange(@NonNull ViewGroup container, View from, View to, boolean isPush, @NonNull ControllerChangeCompletedListener changeListener) {
container.addView(to);
ViewUtils.setAttached(to, true);
changeListener.onChangeCompleted();
}
}))
.popChangeHandler(new ChangeHandler(new ChangeHandlerListener() {
@Override
public void performChange(@NonNull ViewGroup container, View from, View to, boolean isPush, @NonNull ControllerChangeCompletedListener changeListener) {
container.removeView(from);
ViewUtils.setAttached(from, false);
changeListener.onChangeCompleted();
}
})));
.pushChangeHandler(new MockChangeHandler())
.popChangeHandler(new MockChangeHandler()));
router.popController(testController);
@@ -407,14 +393,7 @@ public class ControllerLifecycleTests {
public void testChildLifecycle() {
Controller parent = new TestController();
router.pushController(RouterTransaction.with(parent)
.pushChangeHandler(new ChangeHandler(new ChangeHandlerListener() {
@Override
public void performChange(@NonNull ViewGroup container, View from, View to, boolean isPush, @NonNull ControllerChangeCompletedListener changeListener) {
container.addView(to);
ViewUtils.setAttached(to, true);
changeListener.onChangeCompleted();
}
})));
.pushChangeHandler(new MockChangeHandler()));
TestController child = new TestController();
attachLifecycleListener(child);
@@ -440,22 +419,8 @@ public class ControllerLifecycleTests {
public void testChildLifecycle2() {
Controller parent = new TestController();
router.pushController(RouterTransaction.with(parent)
.pushChangeHandler(new ChangeHandler(new ChangeHandlerListener() {
@Override
public void performChange(@NonNull ViewGroup container, View from, View to, boolean isPush, @NonNull ControllerChangeCompletedListener changeListener) {
container.addView(to);
ViewUtils.setAttached(to, true);
changeListener.onChangeCompleted();
}
}))
.popChangeHandler(new ChangeHandler(new ChangeHandlerListener() {
@Override
public void performChange(@NonNull ViewGroup container, View from, View to, boolean isPush, @NonNull ControllerChangeCompletedListener changeListener) {
container.removeView(from);
ViewUtils.setAttached(from, false);
changeListener.onChangeCompleted();
}
})));
.pushChangeHandler(new MockChangeHandler())
.popChangeHandler(new MockChangeHandler()));
TestController child = new TestController();
attachLifecycleListener(child);
@@ -477,44 +442,47 @@ public class ControllerLifecycleTests {
assertCalls(expectedCallState, child);
}
private ChangeHandler getPushHandler(final CallState expectedCallState, final TestController controller) {
return new ChangeHandler(new ChangeHandlerListener() {
private MockChangeHandler getPushHandler(final CallState expectedCallState, final TestController controller) {
return new MockChangeHandler(new ChangeHandlerListener() {
@Override
public void performChange(@NonNull ViewGroup container, View from, View to, boolean isPush, @NonNull ControllerChangeCompletedListener changeListener) {
void willStartChange() {
expectedCallState.changeStartCalls++;
expectedCallState.createViewCalls++;
assertCalls(expectedCallState, controller);
}
container.addView(to);
ViewUtils.setAttached(to, true);
@Override
void didAttachOrDetach() {
expectedCallState.attachCalls++;
assertCalls(expectedCallState, controller);
}
changeListener.onChangeCompleted();
@Override
void didEndChange() {
expectedCallState.changeEndCalls++;
assertCalls(expectedCallState, controller);
}
});
}
private ChangeHandler getPopHandler(final CallState expectedCallState, final TestController controller) {
return new ChangeHandler(new ChangeHandlerListener() {
private MockChangeHandler getPopHandler(final CallState expectedCallState, final TestController controller) {
return new MockChangeHandler(new ChangeHandlerListener() {
@Override
public void performChange(@NonNull ViewGroup container, View from, View to, boolean isPush, @NonNull ControllerChangeCompletedListener changeListener) {
void willStartChange() {
expectedCallState.changeStartCalls++;
assertCalls(expectedCallState, controller);
container.removeView(from);
ViewUtils.setAttached(from, false);
}
@Override
void didAttachOrDetach() {
expectedCallState.destroyViewCalls++;
expectedCallState.detachCalls++;
expectedCallState.destroyCalls++;
assertCalls(expectedCallState, controller);
}
changeListener.onChangeCompleted();
@Override
void didEndChange() {
expectedCallState.changeEndCalls++;
assertCalls(expectedCallState, controller);
}
@@ -585,24 +553,4 @@ public class ControllerLifecycleTests {
});
}
interface ChangeHandlerListener {
void performChange(@NonNull ViewGroup container, View from, View to, boolean isPush, @NonNull ControllerChangeCompletedListener changeListener);
}
public static class ChangeHandler extends ControllerChangeHandler {
private ChangeHandlerListener listener;
public ChangeHandler() { }
public ChangeHandler(ChangeHandlerListener listener) {
this.listener = listener;
}
@Override
public void performChange(@NonNull ViewGroup container, View from, View to, boolean isPush, @NonNull ControllerChangeCompletedListener changeListener) {
listener.performChange(container, from, to, isPush, changeListener);
}
}
}
@@ -0,0 +1,57 @@
package com.bluelinelabs.conductor;
import android.support.annotation.NonNull;
import android.view.View;
import android.view.ViewGroup;
public class MockChangeHandler extends ControllerChangeHandler {
static class ChangeHandlerListener {
void willStartChange() { }
void didAttachOrDetach() { }
void didEndChange() { }
}
final ChangeHandlerListener listener;
public MockChangeHandler() {
this(new ChangeHandlerListener() { });
}
public MockChangeHandler(@NonNull ChangeHandlerListener listener) {
this.listener = listener;
}
@Override
public void performChange(@NonNull ViewGroup container, View from, View to, boolean isPush, @NonNull ControllerChangeCompletedListener changeListener) {
listener.willStartChange();
if (isPush) {
container.addView(to);
ViewUtils.setAttached(to, true);
listener.didAttachOrDetach();
if (from != null) {
container.removeView(from);
ViewUtils.setAttached(from, false);
}
} else {
container.removeView(from);
ViewUtils.setAttached(from, false);
listener.didAttachOrDetach();
if (to != null) {
container.addView(to);
ViewUtils.setAttached(to, true);
}
}
changeListener.onChangeCompleted();
listener.didEndChange();
}
}
@@ -2,13 +2,9 @@ package com.bluelinelabs.conductor;
import android.os.Bundle;
import android.support.annotation.IdRes;
import android.support.annotation.NonNull;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import com.bluelinelabs.conductor.ControllerChangeHandler.ControllerChangeCompletedListener;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
@@ -49,8 +45,8 @@ public class ReattachCaseTests {
final TestController controllerB = new TestController();
router.pushController(RouterTransaction.with(controllerA)
.pushChangeHandler(getPushHandler())
.popChangeHandler(getPopHandler()));
.pushChangeHandler(new MockChangeHandler())
.popChangeHandler(new MockChangeHandler()));
Assert.assertTrue(controllerA.isAttached());
Assert.assertFalse(controllerB.isAttached());
@@ -61,8 +57,8 @@ public class ReattachCaseTests {
Assert.assertFalse(controllerB.isAttached());
router.pushController(RouterTransaction.with(controllerB)
.pushChangeHandler(getPushHandler())
.popChangeHandler(getPopHandler()));
.pushChangeHandler(new MockChangeHandler())
.popChangeHandler(new MockChangeHandler()));
Assert.assertFalse(controllerA.isAttached());
Assert.assertTrue(controllerB.isAttached());
@@ -80,13 +76,13 @@ public class ReattachCaseTests {
final TestController controllerB = new TestController();
router.pushController(RouterTransaction.with(controllerA)
.pushChangeHandler(getPushHandler())
.popChangeHandler(getPopHandler()));
.pushChangeHandler(new MockChangeHandler())
.popChangeHandler(new MockChangeHandler()));
Router childRouter = controllerA.getChildRouter((ViewGroup)controllerA.getView().findViewById(TestController.VIEW_ID), null);
childRouter.pushController(RouterTransaction.with(childController)
.pushChangeHandler(getPushHandler())
.popChangeHandler(getPopHandler()));
.pushChangeHandler(new MockChangeHandler())
.popChangeHandler(new MockChangeHandler()));
Assert.assertTrue(controllerA.isAttached());
Assert.assertTrue(childController.isAttached());
@@ -99,8 +95,8 @@ public class ReattachCaseTests {
Assert.assertFalse(controllerB.isAttached());
router.pushController(RouterTransaction.with(controllerB)
.pushChangeHandler(getPushHandler())
.popChangeHandler(getPopHandler()));
.pushChangeHandler(new MockChangeHandler())
.popChangeHandler(new MockChangeHandler()));
Assert.assertFalse(controllerA.isAttached());
Assert.assertFalse(childController.isAttached());
@@ -121,22 +117,22 @@ public class ReattachCaseTests {
final TestController childController = new TestController();
router.pushController(RouterTransaction.with(controllerA)
.pushChangeHandler(getPushHandler())
.popChangeHandler(getPopHandler()));
.pushChangeHandler(new MockChangeHandler())
.popChangeHandler(new MockChangeHandler()));
Assert.assertTrue(controllerA.isAttached());
Assert.assertFalse(controllerB.isAttached());
Assert.assertFalse(childController.isAttached());
router.pushController(RouterTransaction.with(controllerB)
.pushChangeHandler(getPushHandler())
.popChangeHandler(getPopHandler()));
.pushChangeHandler(new MockChangeHandler())
.popChangeHandler(new MockChangeHandler()));
Router childRouter = controllerB.getChildRouter((ViewGroup)controllerB.getView().findViewById(TestController.VIEW_ID), null);
childRouter.setPopsLastView(true);
childRouter.pushController(RouterTransaction.with(childController)
.pushChangeHandler(getPushHandler())
.popChangeHandler(getPopHandler()));
.pushChangeHandler(new MockChangeHandler())
.popChangeHandler(new MockChangeHandler()));
Assert.assertFalse(controllerA.isAttached());
Assert.assertTrue(controllerB.isAttached());
@@ -169,22 +165,22 @@ public class ReattachCaseTests {
TestController childController = new TestController();
router.pushController(RouterTransaction.with(controllerA)
.pushChangeHandler(getPushHandler())
.popChangeHandler(getPopHandler()));
.pushChangeHandler(new MockChangeHandler())
.popChangeHandler(new MockChangeHandler()));
Assert.assertTrue(controllerA.isAttached());
Assert.assertFalse(controllerB.isAttached());
Assert.assertFalse(childController.isAttached());
router.pushController(RouterTransaction.with(controllerB)
.pushChangeHandler(getPushHandler())
.popChangeHandler(getPopHandler()));
.pushChangeHandler(new MockChangeHandler())
.popChangeHandler(new MockChangeHandler()));
Router childRouter = controllerB.getChildRouter((ViewGroup)controllerB.getView().findViewById(TestController.VIEW_ID), null);
childRouter.setPopsLastView(true);
childRouter.pushController(RouterTransaction.with(childController)
.pushChangeHandler(getPushHandler())
.popChangeHandler(getPopHandler()));
.pushChangeHandler(new MockChangeHandler())
.popChangeHandler(new MockChangeHandler()));
Assert.assertFalse(controllerA.isAttached());
Assert.assertTrue(controllerB.isAttached());
@@ -198,8 +194,8 @@ public class ReattachCaseTests {
childController = new TestController();
childRouter.pushController(RouterTransaction.with(childController)
.pushChangeHandler(getPushHandler())
.popChangeHandler(getPopHandler()));
.pushChangeHandler(new MockChangeHandler())
.popChangeHandler(new MockChangeHandler()));
Assert.assertFalse(controllerA.isAttached());
Assert.assertTrue(controllerB.isAttached());
@@ -215,8 +211,8 @@ public class ReattachCaseTests {
childController = new TestController();
childRouter.pushController(RouterTransaction.with(childController)
.pushChangeHandler(getPushHandler())
.popChangeHandler(getPopHandler()));
.pushChangeHandler(new MockChangeHandler())
.popChangeHandler(new MockChangeHandler()));
Assert.assertFalse(controllerA.isAttached());
Assert.assertTrue(controllerB.isAttached());
@@ -250,57 +246,4 @@ public class ReattachCaseTests {
router.rebindIfNeeded();
}
private ChangeHandler getPushHandler() {
return new ChangeHandler(new ChangeHandlerListener() {
@Override
public void performChange(@NonNull ViewGroup container, View from, View to, @NonNull ControllerChangeCompletedListener changeListener) {
container.addView(to);
ViewUtils.setAttached(to, true);
if (from != null) {
container.removeView(from);
ViewUtils.setAttached(from, false);
}
changeListener.onChangeCompleted();
}
});
}
private ChangeHandler getPopHandler() {
return new ChangeHandler(new ChangeHandlerListener() {
@Override
public void performChange(@NonNull ViewGroup container, View from, View to, @NonNull ControllerChangeCompletedListener changeListener) {
container.removeView(from);
ViewUtils.setAttached(from, false);
changeListener.onChangeCompleted();
if (to != null) {
container.addView(to);
ViewUtils.setAttached(to, true);
}
}
});
}
interface ChangeHandlerListener {
void performChange(@NonNull ViewGroup container, View from, View to, @NonNull ControllerChangeCompletedListener changeListener);
}
public static class ChangeHandler extends ControllerChangeHandler {
private ChangeHandlerListener listener;
public ChangeHandler() { }
public ChangeHandler(ChangeHandlerListener listener) {
this.listener = listener;
}
@Override
public void performChange(@NonNull ViewGroup container, View from, View to, boolean isPush, @NonNull ControllerChangeCompletedListener changeListener) {
listener.performChange(container, from, to, changeListener);
}
}
}
@@ -0,0 +1,110 @@
package com.bluelinelabs.conductor;
import android.os.Bundle;
import android.support.annotation.IdRes;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.Robolectric;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import org.robolectric.util.ActivityController;
@RunWith(RobolectricTestRunner.class)
@Config(manifest = Config.NONE)
public class TargetControllerTests {
private ActivityController<TestActivity> activityController;
private Router router;
public void createActivityController(Bundle savedInstanceState) {
activityController = Robolectric.buildActivity(TestActivity.class).create(savedInstanceState).start();
@IdRes int containerId = 4;
FrameLayout routerContainer = new FrameLayout(activityController.get());
routerContainer.setId(containerId);
router = Conductor.attachRouter(activityController.get(), routerContainer, savedInstanceState);
if (!router.hasRootController()) {
router.setRoot(RouterTransaction.with(new TestController()));
}
}
@Before
public void setup() {
createActivityController(null);
}
@Test
public void testSiblingTarget() {
final TestController controllerA = new TestController();
final TestController controllerB = new TestController();
Assert.assertNull(controllerA.getTargetController());
Assert.assertNull(controllerB.getTargetController());
router.pushController(RouterTransaction.with(controllerA)
.pushChangeHandler(new MockChangeHandler())
.popChangeHandler(new MockChangeHandler()));
controllerB.setTargetController(controllerA);
router.pushController(RouterTransaction.with(controllerB)
.pushChangeHandler(new MockChangeHandler())
.popChangeHandler(new MockChangeHandler()));
Assert.assertNull(controllerA.getTargetController());
Assert.assertEquals(controllerA, controllerB.getTargetController());
}
@Test
public void testParentChildTarget() {
final TestController controllerA = new TestController();
final TestController controllerB = new TestController();
Assert.assertNull(controllerA.getTargetController());
Assert.assertNull(controllerB.getTargetController());
router.pushController(RouterTransaction.with(controllerA)
.pushChangeHandler(new MockChangeHandler())
.popChangeHandler(new MockChangeHandler()));
controllerB.setTargetController(controllerA);
Router childRouter = controllerA.getChildRouter((ViewGroup)controllerA.getView().findViewById(TestController.VIEW_ID), null);
childRouter.pushController(RouterTransaction.with(controllerB)
.pushChangeHandler(new MockChangeHandler())
.popChangeHandler(new MockChangeHandler()));
Assert.assertNull(controllerA.getTargetController());
Assert.assertEquals(controllerA, controllerB.getTargetController());
}
@Test
public void testChildParentTarget() {
final TestController controllerA = new TestController();
final TestController controllerB = new TestController();
Assert.assertNull(controllerA.getTargetController());
Assert.assertNull(controllerB.getTargetController());
router.pushController(RouterTransaction.with(controllerA)
.pushChangeHandler(new MockChangeHandler())
.popChangeHandler(new MockChangeHandler()));
controllerA.setTargetController(controllerB);
Router childRouter = controllerA.getChildRouter((ViewGroup)controllerA.getView().findViewById(TestController.VIEW_ID), null);
childRouter.pushController(RouterTransaction.with(controllerB)
.pushChangeHandler(new MockChangeHandler())
.popChangeHandler(new MockChangeHandler()));
Assert.assertNull(controllerB.getTargetController());
Assert.assertEquals(controllerB, controllerA.getTargetController());
}
}
@@ -12,22 +12,20 @@ import com.bluelinelabs.conductor.demo.controllers.HomeController;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.Unbinder;
public class MainActivity extends AppCompatActivity implements ActionBarProvider {
public final class MainActivity extends AppCompatActivity implements ActionBarProvider {
@BindView(R.id.toolbar) Toolbar toolbar;
@BindView(R.id.controller_container) ViewGroup container;
private Router router;
private Unbinder unbinder;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
unbinder = ButterKnife.bind(this);
ButterKnife.bind(this);
setSupportActionBar(toolbar);
@@ -44,10 +42,4 @@ public class MainActivity extends AppCompatActivity implements ActionBarProvider
}
}
@Override
protected void onDestroy() {
super.onDestroy();
unbinder.unbind();
}
}
@@ -37,6 +37,7 @@ public abstract class ButterKnifeController extends RxController {
protected void onDestroyView(View view) {
super.onDestroyView(view);
unbinder.unbind();
unbinder = null;
}
}
@@ -1,7 +1,10 @@
package com.bluelinelabs.conductor.demo.controllers.base;
import android.os.Bundle;
import android.support.annotation.NonNull;
import com.bluelinelabs.conductor.ControllerChangeHandler;
import com.bluelinelabs.conductor.ControllerChangeType;
import com.bluelinelabs.conductor.demo.DemoApplication;
public abstract class RefWatchingController extends ButterKnifeController {
@@ -11,10 +14,24 @@ public abstract class RefWatchingController extends ButterKnifeController {
super(args);
}
private boolean hasExited;
@Override
public void onDestroy() {
super.onDestroy();
DemoApplication.refWatcher.watch(this);
if (hasExited) {
DemoApplication.refWatcher.watch(this);
}
}
@Override
protected void onChangeEnded(@NonNull ControllerChangeHandler changeHandler, @NonNull ControllerChangeType changeType) {
super.onChangeEnded(changeHandler, changeType);
hasExited = !changeType.isEnter;
if (isDestroyed()) {
DemoApplication.refWatcher.watch(this);
}
}
}
+2 -2
View File
@@ -14,8 +14,8 @@ ext {
picasso = 'com.squareup.picasso:picasso:2.5.2'
leakCanary = 'com.squareup.leakcanary:leakcanary-android:1.4-beta2'
leakCanaryNoOp = 'com.squareup.leakcanary:leakcanary-android-no-op:1.4-beta2'
leakCanary = 'com.squareup.leakcanary:leakcanary-android:1.4'
leakCanaryNoOp = 'com.squareup.leakcanary:leakcanary-android-no-op:1.4'
rxJava = 'io.reactivex:rxjava:1.1.0'
rxAndroid = 'io.reactivex:rxandroid:1.1.0'
+1 -1
View File
@@ -1,4 +1,4 @@
VERSION_NAME=2.0.2-SNAPSHOT
VERSION_NAME=2.0.3-SNAPSHOT
VERSION_CODE=2
GROUP=com.bluelinelabs
+2 -2
View File
@@ -1,6 +1,6 @@
#Tue Jan 19 22:43:12 CST 2016
#Mon Sep 05 10:45:48 CDT 2016
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip