diff --git a/conductor/src/main/java/com/bluelinelabs/conductor/ControllerChangeHandler.java b/conductor/src/main/java/com/bluelinelabs/conductor/ControllerChangeHandler.java index 29a6801..1e137e5 100644 --- a/conductor/src/main/java/com/bluelinelabs/conductor/ControllerChangeHandler.java +++ b/conductor/src/main/java/com/bluelinelabs/conductor/ControllerChangeHandler.java @@ -4,6 +4,7 @@ import android.os.Bundle; import android.support.annotation.NonNull; import android.view.View; import android.view.ViewGroup; +import android.view.ViewParent; import com.bluelinelabs.conductor.changehandler.SimpleSwapChangeHandler; import com.bluelinelabs.conductor.internal.ClassUtils; @@ -25,6 +26,8 @@ public abstract class ControllerChangeHandler { private static final Map inProgressPushHandlers = new HashMap<>(); + private boolean forceRemoveViewOnPush; + /** * Responsible for swapping Views from one Controller to another. * @@ -174,6 +177,13 @@ public abstract class ControllerChangeHandler { for (ControllerChangeListener listener : listeners) { listener.onChangeCompleted(to, from, isPush, container, inHandler); } + + if (handler.forceRemoveViewOnPush && fromView != null) { + ViewParent fromParent = fromView.getParent(); + if (fromParent != null && fromParent instanceof ViewGroup) { + ((ViewGroup)fromParent).removeView(fromView); + } + } } }); } @@ -183,6 +193,10 @@ public abstract class ControllerChangeHandler { return true; } + public void setForceRemoveViewOnPush(boolean force) { + forceRemoveViewOnPush = force; + } + /** * A listener interface useful for allowing external classes to be notified of change events. */ diff --git a/conductor/src/main/java/com/bluelinelabs/conductor/Router.java b/conductor/src/main/java/com/bluelinelabs/conductor/Router.java index e72b442..77aa274 100755 --- a/conductor/src/main/java/com/bluelinelabs/conductor/Router.java +++ b/conductor/src/main/java/com/bluelinelabs/conductor/Router.java @@ -152,8 +152,21 @@ public abstract class Router { trackDestroyingController(backstack.pop()); } + final ControllerChangeHandler handler = transaction.pushChangeHandler(); + final boolean oldHandlerRemovedViews = topTransaction.pushChangeHandler() == null || topTransaction.pushChangeHandler().removesFromViewOnPush(); + final boolean newHandlerRemovesViews = handler == null || handler.removesFromViewOnPush(); + if (!oldHandlerRemovedViews && newHandlerRemovesViews) { + for (RouterTransaction visibleTransaction : getVisibleTransactions(backstack.iterator())) { + performControllerChange(null, visibleTransaction.controller, true, handler != null ? handler.copy() : new SimpleSwapChangeHandler()); + } + } + pushToBackstack(transaction); - performControllerChange(transaction, topTransaction, true); + + if (handler != null) { + handler.setForceRemoveViewOnPush(true); + } + performControllerChange(transaction.pushChangeHandler(handler), topTransaction, true); } void destroy() { @@ -319,7 +332,7 @@ public abstract class Router { * using the passed {@link ControllerChangeHandler} * * @param newBackstack The new backstack - * @param changeHandler An optional change handler to be used to handle the transition + * @param changeHandler An optional change handler to be used to handle the root view of transition */ public void setBackstack(@NonNull List newBackstack, ControllerChangeHandler changeHandler) { List oldVisibleTransactions = getVisibleTransactions(backstack.iterator()); @@ -349,13 +362,15 @@ public abstract class Router { for (int i = oldVisibleTransactions.size() - 1; i > 0; i--) { RouterTransaction transaction = oldVisibleTransactions.get(i); - performControllerChange(null, transaction.controller, true, handler); + ControllerChangeHandler localHandler = handler.copy(); + localHandler.setForceRemoveViewOnPush(true); + performControllerChange(null, transaction.controller, true, localHandler); } for (int i = 1; i < newVisibleTransactions.size(); i++) { RouterTransaction transaction = newVisibleTransactions.get(i); handler = transaction.pushChangeHandler() != null ? transaction.pushChangeHandler().copy() : new SimpleSwapChangeHandler(); - performControllerChange(transaction.controller, newVisibleTransactions.get(i - 1).controller, true, handler); + performControllerChange(transaction.controller, newVisibleTransactions.get(i - 1).controller, true, handler.copy()); } } } diff --git a/conductor/src/test/java/com/bluelinelabs/conductor/ActivityProxy.java b/conductor/src/test/java/com/bluelinelabs/conductor/ActivityProxy.java new file mode 100644 index 0000000..81c4865 --- /dev/null +++ b/conductor/src/test/java/com/bluelinelabs/conductor/ActivityProxy.java @@ -0,0 +1,72 @@ +package com.bluelinelabs.conductor; + +import android.os.Bundle; +import android.support.annotation.IdRes; + +import org.robolectric.Robolectric; +import org.robolectric.util.ActivityController; + +public class ActivityProxy { + + private ActivityController activityController; + private AttachFakingFrameLayout view; + + public ActivityProxy() { + activityController = Robolectric.buildActivity(TestActivity.class); + + @IdRes int containerId = 4; + view = new AttachFakingFrameLayout(activityController.get()); + view.setId(containerId); + } + + public ActivityProxy create(Bundle savedInstanceState) { + activityController.create(savedInstanceState); + return this; + } + + public ActivityProxy start() { + activityController.start(); + view.setAttached(true); + return this; + } + + public ActivityProxy resume() { + activityController.resume(); + return this; + } + + public ActivityProxy pause() { + activityController.pause(); + return this; + } + + public ActivityProxy saveInstanceState(Bundle outState) { + activityController.saveInstanceState(outState); + return this; + } + + public ActivityProxy stop() { + activityController.stop(); + view.setAttached(false); + return this; + } + + public ActivityProxy destroy() { + activityController.destroy(); + return this; + } + + public ActivityProxy rotate() { + getActivity().isChangingConfigurations = true; + getActivity().recreate(); + return this; + } + + public TestActivity getActivity() { + return activityController.get(); + } + + public AttachFakingFrameLayout getView() { + return view; + } +} diff --git a/conductor/src/test/java/com/bluelinelabs/conductor/AttachFakingFrameLayout.java b/conductor/src/test/java/com/bluelinelabs/conductor/AttachFakingFrameLayout.java index adab101..1d1afbc 100644 --- a/conductor/src/test/java/com/bluelinelabs/conductor/AttachFakingFrameLayout.java +++ b/conductor/src/test/java/com/bluelinelabs/conductor/AttachFakingFrameLayout.java @@ -6,6 +6,7 @@ import android.os.IInterface; import android.os.Parcel; import android.os.RemoteException; import android.util.AttributeSet; +import android.view.View; import android.widget.FrameLayout; import java.io.FileDescriptor; @@ -79,7 +80,34 @@ public class AttachFakingFrameLayout extends FrameLayout { } public void setAttached(boolean attached) { - reportAttached = attached; + setAttached(attached, true); + } + + public void setAttached(boolean attached, boolean reportToViewUtils) { + if (reportAttached != attached) { + reportAttached = attached; + if (reportToViewUtils) { + ViewUtils.reportAttached(this, attached); + } + + for (int i = 0; i < getChildCount(); i++) { + ViewUtils.reportAttached(getChildAt(i), attached); + } + } + } + + @Override + public void onViewAdded(View child) { + if (reportAttached) { + ViewUtils.reportAttached(child, true); + } + super.onViewAdded(child); + } + + @Override + public void onViewRemoved(View child) { + ViewUtils.reportAttached(child, false); + super.onViewRemoved(child); } } diff --git a/conductor/src/test/java/com/bluelinelabs/conductor/CallState.java b/conductor/src/test/java/com/bluelinelabs/conductor/CallState.java index 71f999e..72549d7 100644 --- a/conductor/src/test/java/com/bluelinelabs/conductor/CallState.java +++ b/conductor/src/test/java/com/bluelinelabs/conductor/CallState.java @@ -119,9 +119,9 @@ public class CallState implements Parcelable { "\n restoreInstanceStateCalls=" + restoreInstanceStateCalls + "\n saveViewStateCalls=" + saveViewStateCalls + "\n restoreViewStateCalls=" + restoreViewStateCalls + - "\n onActivityResultCalls= " + onActivityResultCalls + - "\n onRequestPermissionsResultCalls= " + onRequestPermissionsResultCalls + - "\n createOptionsMenuCalls= " + createOptionsMenuCalls + + "\n onActivityResultCalls=" + onActivityResultCalls + + "\n onRequestPermissionsResultCalls=" + onRequestPermissionsResultCalls + + "\n createOptionsMenuCalls=" + createOptionsMenuCalls + "}\n"; } diff --git a/conductor/src/test/java/com/bluelinelabs/conductor/ControllerLifecycleTests.java b/conductor/src/test/java/com/bluelinelabs/conductor/ControllerLifecycleTests.java index a78d2fb..8dbe594 100644 --- a/conductor/src/test/java/com/bluelinelabs/conductor/ControllerLifecycleTests.java +++ b/conductor/src/test/java/com/bluelinelabs/conductor/ControllerLifecycleTests.java @@ -1,7 +1,6 @@ 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; @@ -13,29 +12,26 @@ 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 ControllerLifecycleTests { - private ActivityController activityController; private Router router; + private ActivityProxy activityProxy; private CallState currentCallState; - public void createActivityController(Bundle savedInstanceState) { - activityController = Robolectric.buildActivity(TestActivity.class).create(savedInstanceState).start(); + public void createActivityController(Bundle savedInstanceState, boolean includeStartAndResume) { + activityProxy = new ActivityProxy().create(savedInstanceState); - @IdRes int containerId = 4; - AttachFakingFrameLayout routerContainer = new AttachFakingFrameLayout(activityController.get()); - routerContainer.setId(containerId); - routerContainer.setAttached(true); + if (includeStartAndResume) { + activityProxy.start().resume(); + } - router = Conductor.attachRouter(activityController.get(), routerContainer, savedInstanceState); + router = Conductor.attachRouter(activityProxy.getActivity(), activityProxy.getView(), savedInstanceState); if (!router.hasRootController()) { router.setRoot(RouterTransaction.with(new TestController())); } @@ -43,7 +39,7 @@ public class ControllerLifecycleTests { @Before public void setup() { - createActivityController(null); + createActivityController(null, true); currentCallState = new CallState(); } @@ -82,18 +78,20 @@ public class ControllerLifecycleTests { assertCalls(expectedCallState, controller); - activityController.pause(); + activityProxy.getActivity().isDestroying = true; + activityProxy.pause(); assertCalls(expectedCallState, controller); - activityController.stop(); - - assertCalls(expectedCallState, controller); - - activityController.destroy(); + activityProxy.stop(); + expectedCallState.saveViewStateCalls++; expectedCallState.detachCalls++; expectedCallState.destroyViewCalls++; + assertCalls(expectedCallState, controller); + + activityProxy.destroy(); + expectedCallState.destroyCalls++; assertCalls(expectedCallState, controller); } @@ -112,33 +110,32 @@ public class ControllerLifecycleTests { assertCalls(expectedCallState, controller); - activityController.get().isChangingConfigurations = true; + activityProxy.getActivity().isChangingConfigurations = true; Bundle bundle = new Bundle(); - activityController.saveInstanceState(bundle); + activityProxy.saveInstanceState(bundle); expectedCallState.saveViewStateCalls++; expectedCallState.saveInstanceStateCalls++; assertCalls(expectedCallState, controller); - activityController.pause(); + activityProxy.pause(); assertCalls(expectedCallState, controller); - activityController.stop(); - assertCalls(expectedCallState, controller); - - activityController.destroy(); + activityProxy.stop(); expectedCallState.detachCalls++; expectedCallState.destroyViewCalls++; assertCalls(expectedCallState, controller); - createActivityController(bundle); + activityProxy.destroy(); + assertCalls(expectedCallState, controller); + + createActivityController(bundle, false); controller = (TestController)router.getControllerWithTag("root"); expectedCallState.restoreInstanceStateCalls++; expectedCallState.restoreViewStateCalls++; expectedCallState.changeStartCalls++; - expectedCallState.changeEndCalls++; expectedCallState.createViewCalls++; // Lifecycle listener isn't attached during restore, grab the current views from the controller for this stuff... @@ -147,10 +144,19 @@ public class ControllerLifecycleTests { currentCallState.changeStartCalls = controller.currentCallState.changeStartCalls; currentCallState.changeEndCalls = controller.currentCallState.changeEndCalls; currentCallState.createViewCalls = controller.currentCallState.createViewCalls; + currentCallState.attachCalls = controller.currentCallState.attachCalls; assertCalls(expectedCallState, controller); - activityController.resume(); + activityProxy.start().resume(); + currentCallState.changeEndCalls = controller.currentCallState.changeEndCalls; + currentCallState.attachCalls = controller.currentCallState.attachCalls; + expectedCallState.changeEndCalls++; + expectedCallState.attachCalls++; + + assertCalls(expectedCallState, controller); + + activityProxy.resume(); assertCalls(expectedCallState, controller); } @@ -167,16 +173,16 @@ public class ControllerLifecycleTests { assertCalls(expectedCallState, controller); - activityController.pause(); + activityProxy.pause(); Bundle bundle = new Bundle(); - activityController.saveInstanceState(bundle); + activityProxy.saveInstanceState(bundle); expectedCallState.saveInstanceStateCalls++; expectedCallState.saveViewStateCalls++; assertCalls(expectedCallState, controller); - activityController.resume(); + activityProxy.resume(); } @Test diff --git a/conductor/src/test/java/com/bluelinelabs/conductor/ControllerTests.java b/conductor/src/test/java/com/bluelinelabs/conductor/ControllerTests.java index 4495f70..87c9dbb 100644 --- a/conductor/src/test/java/com/bluelinelabs/conductor/ControllerTests.java +++ b/conductor/src/test/java/com/bluelinelabs/conductor/ControllerTests.java @@ -3,7 +3,6 @@ package com.bluelinelabs.conductor; import android.app.Activity; import android.content.Intent; import android.os.Bundle; -import android.support.annotation.IdRes; import android.view.View; import android.view.ViewGroup; @@ -13,27 +12,19 @@ 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 ControllerTests { - private ActivityController activityController; + private ActivityProxy activityProxy; private Router router; public void createActivityController(Bundle savedInstanceState) { - activityController = Robolectric.buildActivity(TestActivity.class).create(savedInstanceState).start(); - - @IdRes int containerId = 4; - AttachFakingFrameLayout routerContainer = new AttachFakingFrameLayout(activityController.get()); - routerContainer.setId(containerId); - routerContainer.setAttached(true); - - router = Conductor.attachRouter(activityController.get(), routerContainer, savedInstanceState); + activityProxy = new ActivityProxy().create(savedInstanceState).start().resume(); + router = Conductor.attachRouter(activityProxy.getActivity(), activityProxy.getView(), savedInstanceState); if (!router.hasRootController()) { router.setRoot(RouterTransaction.with(new TestController())); } @@ -53,18 +44,18 @@ public class ControllerTests { Assert.assertNull(controller.getView()); View view = controller.inflate(new AttachFakingFrameLayout(router.getActivity())); Assert.assertNotNull(controller.getView()); - ViewUtils.setAttached(view, true); + ViewUtils.reportAttached(view, true); Assert.assertNotNull(controller.getView()); - ViewUtils.setAttached(view, false); + ViewUtils.reportAttached(view, false); Assert.assertNull(controller.getView()); // Test View getting retained w/ RETAIN_DETACH controller.setRetainViewMode(RetainViewMode.RETAIN_DETACH); view = controller.inflate(new AttachFakingFrameLayout(router.getActivity())); Assert.assertNotNull(controller.getView()); - ViewUtils.setAttached(view, true); + ViewUtils.reportAttached(view, true); Assert.assertNotNull(controller.getView()); - ViewUtils.setAttached(view, false); + ViewUtils.reportAttached(view, false); Assert.assertNotNull(controller.getView()); // Ensure re-setting RELEASE_DETACH releases @@ -78,7 +69,6 @@ public class ControllerTests { CallState expectedCallState = new CallState(true); router.pushController(RouterTransaction.with(controller)); - ViewUtils.setAttached(controller.getView(), true); // Ensure that calling onActivityResult w/o requesting a result doesn't do anything router.onActivityResult(1, Activity.RESULT_OK, null); @@ -103,10 +93,8 @@ public class ControllerTests { TestController child = new TestController(); router.pushController(RouterTransaction.with(parent)); - ViewUtils.setAttached(parent.getView(), true); parent.getChildRouter((ViewGroup)parent.getView().findViewById(TestController.VIEW_ID), null) .setRoot(RouterTransaction.with(child)); - ViewUtils.setAttached(child.getView(), true); CallState childExpectedCallState = new CallState(true); CallState parentExpectedCallState = new CallState(true); @@ -139,7 +127,6 @@ public class ControllerTests { CallState expectedCallState = new CallState(true); router.pushController(RouterTransaction.with(controller)); - ViewUtils.setAttached(controller.getView(), true); // Ensure that calling handleRequestedPermission w/o requesting a result doesn't do anything router.onRequestPermissionsResult("anotherId", 1, requestedPermissions, new int[] {1}); @@ -163,10 +150,8 @@ public class ControllerTests { TestController child = new TestController(); router.pushController(RouterTransaction.with(parent)); - ViewUtils.setAttached(parent.getView(), true); parent.getChildRouter((ViewGroup)parent.getView().findViewById(TestController.VIEW_ID), null) .setRoot(RouterTransaction.with(child)); - ViewUtils.setAttached(child.getView(), true); CallState childExpectedCallState = new CallState(true); CallState parentExpectedCallState = new CallState(true); @@ -193,7 +178,6 @@ public class ControllerTests { CallState expectedCallState = new CallState(true); router.pushController(RouterTransaction.with(controller)); - ViewUtils.setAttached(controller.getView(), true); // Ensure that calling onCreateOptionsMenu w/o declaring that we have one doesn't do anything router.onCreateOptionsMenu(null, null); @@ -230,10 +214,8 @@ public class ControllerTests { TestController child = new TestController(); router.pushController(RouterTransaction.with(parent)); - ViewUtils.setAttached(parent.getView(), true); parent.getChildRouter((ViewGroup)parent.getView().findViewById(TestController.VIEW_ID), null) .setRoot(RouterTransaction.with(child)); - ViewUtils.setAttached(child.getView(), true); CallState childExpectedCallState = new CallState(true); CallState parentExpectedCallState = new CallState(true); @@ -285,6 +267,7 @@ public class ControllerTests { Assert.assertNull(child2.getParentController()); Router childRouter = parent.getChildRouter((ViewGroup)parent.getView().findViewById(TestController.VIEW_ID), null); + childRouter.setPopsLastView(true); childRouter.setRoot(RouterTransaction.with(child1)); Assert.assertEquals(1, parent.getChildRouters().size()); diff --git a/conductor/src/test/java/com/bluelinelabs/conductor/MockChangeHandler.java b/conductor/src/test/java/com/bluelinelabs/conductor/MockChangeHandler.java index 0982f8d..703bf7e 100644 --- a/conductor/src/test/java/com/bluelinelabs/conductor/MockChangeHandler.java +++ b/conductor/src/test/java/com/bluelinelabs/conductor/MockChangeHandler.java @@ -1,12 +1,15 @@ package com.bluelinelabs.conductor; +import android.os.Bundle; import android.support.annotation.NonNull; import android.view.View; import android.view.ViewGroup; public class MockChangeHandler extends ControllerChangeHandler { + private static final String KEY_REMOVES_FROM_VIEW_ON_PUSH = "MockChangeHandler.removesFromViewOnPush"; + static class ChangeHandlerListener { void willStartChange() { } void didAttachOrDetach() { } @@ -14,13 +17,28 @@ public class MockChangeHandler extends ControllerChangeHandler { } final ChangeHandlerListener listener; + boolean removesFromViewOnPush; public MockChangeHandler() { - this(new ChangeHandlerListener() { }); + this(true, null); + } + + public MockChangeHandler(boolean removesViewOnPush) { + this(removesViewOnPush, null); } public MockChangeHandler(@NonNull ChangeHandlerListener listener) { - this.listener = listener; + this(true, listener); + } + + public MockChangeHandler(boolean removesFromViewOnPush, ChangeHandlerListener listener) { + this.removesFromViewOnPush = removesFromViewOnPush; + + if (listener == null) { + this.listener = new ChangeHandlerListener() { }; + } else { + this.listener = listener; + } } @Override @@ -29,23 +47,17 @@ public class MockChangeHandler extends ControllerChangeHandler { if (isPush) { container.addView(to); - ViewUtils.setAttached(to, true); - listener.didAttachOrDetach(); - if (from != null) { + if (removesFromViewOnPush && 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); } } @@ -54,4 +66,20 @@ public class MockChangeHandler extends ControllerChangeHandler { listener.didEndChange(); } + @Override + public boolean removesFromViewOnPush() { + return removesFromViewOnPush; + } + + @Override + public void saveToBundle(@NonNull Bundle bundle) { + super.saveToBundle(bundle); + bundle.putBoolean(KEY_REMOVES_FROM_VIEW_ON_PUSH, removesFromViewOnPush); + } + + @Override + public void restoreFromBundle(@NonNull Bundle bundle) { + super.restoreFromBundle(bundle); + removesFromViewOnPush = bundle.getBoolean(KEY_REMOVES_FROM_VIEW_ON_PUSH); + } } diff --git a/conductor/src/test/java/com/bluelinelabs/conductor/ReattachCaseTests.java b/conductor/src/test/java/com/bluelinelabs/conductor/ReattachCaseTests.java index f550e5f..2c4439d 100644 --- a/conductor/src/test/java/com/bluelinelabs/conductor/ReattachCaseTests.java +++ b/conductor/src/test/java/com/bluelinelabs/conductor/ReattachCaseTests.java @@ -1,34 +1,25 @@ 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 ReattachCaseTests { - private ActivityController activityController; + private ActivityProxy activityProxy; 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); + activityProxy = new ActivityProxy().create(savedInstanceState).start().resume(); + router = Conductor.attachRouter(activityProxy.getActivity(), activityProxy.getView(), savedInstanceState); if (!router.hasRootController()) { router.setRoot(RouterTransaction.with(new TestController())); } @@ -63,7 +54,8 @@ public class ReattachCaseTests { Assert.assertFalse(controllerA.isAttached()); Assert.assertTrue(controllerB.isAttached()); - rotateDevice(); + activityProxy.rotate(); + router.rebindIfNeeded(); Assert.assertFalse(controllerA.isAttached()); Assert.assertTrue(controllerB.isAttached()); @@ -102,7 +94,8 @@ public class ReattachCaseTests { Assert.assertFalse(childController.isAttached()); Assert.assertTrue(controllerB.isAttached()); - rotateDevice(); + activityProxy.rotate(); + router.rebindIfNeeded(); Assert.assertFalse(controllerA.isAttached()); Assert.assertFalse(childController.isAttached()); @@ -138,7 +131,8 @@ public class ReattachCaseTests { Assert.assertTrue(controllerB.isAttached()); Assert.assertTrue(childController.isAttached()); - rotateDevice(); + activityProxy.rotate(); + router.rebindIfNeeded(); Assert.assertFalse(controllerA.isAttached()); Assert.assertTrue(controllerB.isAttached()); @@ -201,7 +195,8 @@ public class ReattachCaseTests { Assert.assertTrue(controllerB.isAttached()); Assert.assertTrue(childController.isAttached()); - rotateDevice(); + activityProxy.rotate(); + router.rebindIfNeeded(); Assert.assertFalse(controllerA.isAttached()); Assert.assertTrue(controllerB.isAttached()); @@ -232,18 +227,8 @@ public class ReattachCaseTests { } private void sleepWakeDevice() { - activityController.saveInstanceState(new Bundle()).pause(); - activityController.resume(); - } - - private void rotateDevice() { - @IdRes int containerId = 4; - FrameLayout routerContainer = new FrameLayout(activityController.get()); - routerContainer.setId(containerId); - - activityController.get().isChangingConfigurations = true; - activityController.get().recreate(); - router.rebindIfNeeded(); + activityProxy.saveInstanceState(new Bundle()).pause(); + activityProxy.resume(); } } diff --git a/conductor/src/test/java/com/bluelinelabs/conductor/RouterTests.java b/conductor/src/test/java/com/bluelinelabs/conductor/RouterTests.java index b4dbd24..4195b34 100644 --- a/conductor/src/test/java/com/bluelinelabs/conductor/RouterTests.java +++ b/conductor/src/test/java/com/bluelinelabs/conductor/RouterTests.java @@ -1,16 +1,15 @@ package com.bluelinelabs.conductor; -import android.app.Activity; -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 java.util.ArrayList; +import java.util.List; + @RunWith(RobolectricTestRunner.class) @Config(manifest = Config.NONE) public class RouterTests { @@ -19,8 +18,8 @@ public class RouterTests { @Before public void setup() { - Activity activity = Robolectric.buildActivity(TestActivity.class).create().get(); - router = Conductor.attachRouter(activity, new FrameLayout(activity), null); + ActivityProxy activityProxy = new ActivityProxy().create(null).start().resume(); + router = Conductor.attachRouter(activityProxy.getActivity(), activityProxy.getView(), null); } @Test @@ -174,4 +173,149 @@ public class RouterTests { Assert.assertEquals(controller3, router.getControllerWithTag(controller3Tag)); } + @Test + public void testSetBackstack() { + RouterTransaction rootTransaction = RouterTransaction.with(new TestController()); + RouterTransaction middleTransaction = RouterTransaction.with(new TestController()); + RouterTransaction topTransaction = RouterTransaction.with(new TestController()); + + List backstack = new ArrayList<>(); + backstack.add(rootTransaction); + backstack.add(middleTransaction); + backstack.add(topTransaction); + + router.setBackstack(backstack, null); + + Assert.assertEquals(3, router.getBackstackSize()); + + List fetchedBackstack = router.getBackstack(); + Assert.assertEquals(rootTransaction, fetchedBackstack.get(0)); + Assert.assertEquals(middleTransaction, fetchedBackstack.get(1)); + Assert.assertEquals(topTransaction, fetchedBackstack.get(2)); + } + + @Test + public void testNewSetBackstack() { + router.setRoot(RouterTransaction.with(new TestController())); + + Assert.assertEquals(1, router.getBackstackSize()); + + RouterTransaction rootTransaction = RouterTransaction.with(new TestController()); + RouterTransaction middleTransaction = RouterTransaction.with(new TestController()); + RouterTransaction topTransaction = RouterTransaction.with(new TestController()); + + List backstack = new ArrayList<>(); + backstack.add(rootTransaction); + backstack.add(middleTransaction); + backstack.add(topTransaction); + + router.setBackstack(backstack, null); + + Assert.assertEquals(3, router.getBackstackSize()); + + List fetchedBackstack = router.getBackstack(); + Assert.assertEquals(rootTransaction, fetchedBackstack.get(0)); + Assert.assertEquals(middleTransaction, fetchedBackstack.get(1)); + Assert.assertEquals(topTransaction, fetchedBackstack.get(2)); + } + + @Test + public void testNewSetBackstackWithNoRemoveViewOnPush() { + RouterTransaction oldRootTransaction = RouterTransaction.with(new TestController()); + RouterTransaction oldTopTransaction = RouterTransaction.with(new TestController()).pushChangeHandler(new MockChangeHandler(false)); + + router.setRoot(oldRootTransaction); + router.pushController(oldTopTransaction); + Assert.assertEquals(2, router.getBackstackSize()); + + Assert.assertTrue(oldRootTransaction.controller.isAttached()); + Assert.assertTrue(oldTopTransaction.controller.isAttached()); + + RouterTransaction rootTransaction = RouterTransaction.with(new TestController()); + RouterTransaction middleTransaction = RouterTransaction.with(new TestController()).pushChangeHandler(new MockChangeHandler(false)); + RouterTransaction topTransaction = RouterTransaction.with(new TestController()).pushChangeHandler(new MockChangeHandler(false)); + + List backstack = new ArrayList<>(); + backstack.add(rootTransaction); + backstack.add(middleTransaction); + backstack.add(topTransaction); + + router.setBackstack(backstack, null); + + Assert.assertEquals(3, router.getBackstackSize()); + + List fetchedBackstack = router.getBackstack(); + Assert.assertEquals(rootTransaction, fetchedBackstack.get(0)); + Assert.assertEquals(middleTransaction, fetchedBackstack.get(1)); + Assert.assertEquals(topTransaction, fetchedBackstack.get(2)); + + Assert.assertFalse(oldRootTransaction.controller.isAttached()); + Assert.assertFalse(oldTopTransaction.controller.isAttached()); + Assert.assertTrue(rootTransaction.controller.isAttached()); + Assert.assertTrue(middleTransaction.controller.isAttached()); + Assert.assertTrue(topTransaction.controller.isAttached()); + } + + @Test + public void testReplaceTopController() { + RouterTransaction rootTransaction = RouterTransaction.with(new TestController()); + RouterTransaction topTransaction = RouterTransaction.with(new TestController()); + + List backstack = new ArrayList<>(); + backstack.add(rootTransaction); + backstack.add(topTransaction); + + router.setBackstack(backstack, null); + + Assert.assertEquals(2, router.getBackstackSize()); + + List fetchedBackstack = router.getBackstack(); + Assert.assertEquals(rootTransaction, fetchedBackstack.get(0)); + Assert.assertEquals(topTransaction, fetchedBackstack.get(1)); + + RouterTransaction newTopTransaction = RouterTransaction.with(new TestController()); + router.replaceTopController(newTopTransaction); + + Assert.assertEquals(2, router.getBackstackSize()); + + fetchedBackstack = router.getBackstack(); + Assert.assertEquals(rootTransaction, fetchedBackstack.get(0)); + Assert.assertEquals(newTopTransaction, fetchedBackstack.get(1)); + } + + @Test + public void testReplaceTopControllerWithNoRemoveViewOnPush() { + RouterTransaction rootTransaction = RouterTransaction.with(new TestController()); + RouterTransaction topTransaction = RouterTransaction.with(new TestController()).pushChangeHandler(new MockChangeHandler(false)); + + List backstack = new ArrayList<>(); + backstack.add(rootTransaction); + backstack.add(topTransaction); + + router.setBackstack(backstack, null); + + Assert.assertEquals(2, router.getBackstackSize()); + + Assert.assertTrue(rootTransaction.controller.isAttached()); + Assert.assertTrue(topTransaction.controller.isAttached()); + + List fetchedBackstack = router.getBackstack(); + Assert.assertEquals(rootTransaction, fetchedBackstack.get(0)); + Assert.assertEquals(topTransaction, fetchedBackstack.get(1)); + + RouterTransaction newTopTransaction = RouterTransaction.with(new TestController()).pushChangeHandler(new MockChangeHandler(false)); + router.replaceTopController(newTopTransaction); + newTopTransaction.pushChangeHandler().completeImmediately(); + + Assert.assertEquals(2, router.getBackstackSize()); + + fetchedBackstack = router.getBackstack(); + Assert.assertEquals(rootTransaction, fetchedBackstack.get(0)); + Assert.assertEquals(newTopTransaction, fetchedBackstack.get(1)); + + Assert.assertTrue(rootTransaction.controller.isAttached()); + Assert.assertFalse(topTransaction.controller.isAttached()); + Assert.assertTrue(newTopTransaction.controller.isAttached()); + } + } diff --git a/conductor/src/test/java/com/bluelinelabs/conductor/TargetControllerTests.java b/conductor/src/test/java/com/bluelinelabs/conductor/TargetControllerTests.java index 287f1cb..182c788 100644 --- a/conductor/src/test/java/com/bluelinelabs/conductor/TargetControllerTests.java +++ b/conductor/src/test/java/com/bluelinelabs/conductor/TargetControllerTests.java @@ -1,34 +1,24 @@ 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 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); + ActivityProxy activityProxy = new ActivityProxy().create(savedInstanceState).start().resume(); + router = Conductor.attachRouter(activityProxy.getActivity(), activityProxy.getView(), savedInstanceState); if (!router.hasRootController()) { router.setRoot(RouterTransaction.with(new TestController())); } diff --git a/conductor/src/test/java/com/bluelinelabs/conductor/TestActivity.java b/conductor/src/test/java/com/bluelinelabs/conductor/TestActivity.java index f19e14b..1a05326 100644 --- a/conductor/src/test/java/com/bluelinelabs/conductor/TestActivity.java +++ b/conductor/src/test/java/com/bluelinelabs/conductor/TestActivity.java @@ -5,10 +5,15 @@ import android.app.Activity; public class TestActivity extends Activity { public boolean isChangingConfigurations = false; + public boolean isDestroying = false; @Override public boolean isChangingConfigurations() { return isChangingConfigurations; } + @Override + public boolean isDestroyed() { + return isDestroying || super.isDestroyed(); + } } diff --git a/conductor/src/test/java/com/bluelinelabs/conductor/ViewUtils.java b/conductor/src/test/java/com/bluelinelabs/conductor/ViewUtils.java index 3eaf309..dc0a3c4 100644 --- a/conductor/src/test/java/com/bluelinelabs/conductor/ViewUtils.java +++ b/conductor/src/test/java/com/bluelinelabs/conductor/ViewUtils.java @@ -9,13 +9,26 @@ import java.util.List; public class ViewUtils { - public static void setAttached(View view, boolean attached) { + public static void reportAttached(View view, boolean attached) { if (view instanceof AttachFakingFrameLayout) { - ((AttachFakingFrameLayout)view).setAttached(attached); + ((AttachFakingFrameLayout)view).setAttached(attached, false); } - Object listenerInfo = ReflectionHelpers.callInstanceMethod(view, "getListenerInfo"); - List listeners = ReflectionHelpers.getField(listenerInfo, "mOnAttachStateChangeListeners"); + List listeners = getAttachStateListeners(view); + + // Add, then remove an OnAttachStateChangeListener to initialize the attachStateListeners variable inside a view + if (listeners == null) { + OnAttachStateChangeListener tmpListener = new OnAttachStateChangeListener() { + @Override + public void onViewAttachedToWindow(View v) { } + + @Override + public void onViewDetachedFromWindow(View v) { } + }; + view.addOnAttachStateChangeListener(tmpListener); + view.removeOnAttachStateChangeListener(tmpListener); + listeners = getAttachStateListeners(view); + } for (OnAttachStateChangeListener listener : listeners) { if (attached) { @@ -24,6 +37,12 @@ public class ViewUtils { listener.onViewDetachedFromWindow(view); } } + + } + + private static List getAttachStateListeners(View view) { + Object listenerInfo = ReflectionHelpers.callInstanceMethod(view, "getListenerInfo"); + return ReflectionHelpers.getField(listenerInfo, "mOnAttachStateChangeListeners"); } }