From 25cb51ce8b5b5acfb1ddc54c6f3ed187bcadd270 Mon Sep 17 00:00:00 2001 From: TMTron Date: Mon, 7 Nov 2016 10:53:10 +0100 Subject: [PATCH 01/75] updated android build tools to 2.2.2 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 87b40877..82d92403 100755 --- a/build.gradle +++ b/build.gradle @@ -3,7 +3,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:2.2.0' + classpath 'com.android.tools.build:gradle:2.2.2' } } From 81db35d0c412c47b65735485782f7b06b474f106 Mon Sep 17 00:00:00 2001 From: TMTron Date: Mon, 7 Nov 2016 16:57:32 +0100 Subject: [PATCH 02/75] implemented first level of deep linking --- demo/src/main/AndroidManifest.xml | 12 +++++ .../conductor/demo/MainActivity.java | 49 ++++++++++++++++++- .../demo/controllers/HomeController.java | 17 +++++++ 3 files changed, 77 insertions(+), 1 deletion(-) diff --git a/demo/src/main/AndroidManifest.xml b/demo/src/main/AndroidManifest.xml index b9c7f043..e2426dfd 100755 --- a/demo/src/main/AndroidManifest.xml +++ b/demo/src/main/AndroidManifest.xml @@ -25,6 +25,18 @@ + + + + + + + + + diff --git a/demo/src/main/java/com/bluelinelabs/conductor/demo/MainActivity.java b/demo/src/main/java/com/bluelinelabs/conductor/demo/MainActivity.java index 88b4d6c1..7febe218 100755 --- a/demo/src/main/java/com/bluelinelabs/conductor/demo/MainActivity.java +++ b/demo/src/main/java/com/bluelinelabs/conductor/demo/MainActivity.java @@ -1,8 +1,11 @@ package com.bluelinelabs.conductor.demo; +import android.content.Intent; +import android.net.Uri; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.Toolbar; +import android.util.Log; import android.view.ViewGroup; import com.bluelinelabs.conductor.Conductor; @@ -19,6 +22,8 @@ public final class MainActivity extends AppCompatActivity implements ActionBarPr @BindView(R.id.controller_container) ViewGroup container; private Router router; + private static final String TAG = MainActivity.class.getSimpleName(); + private static final String PATH_PREFIX = "/demo/"; @Override protected void onCreate(Bundle savedInstanceState) { @@ -31,7 +36,49 @@ protected void onCreate(Bundle savedInstanceState) { router = Conductor.attachRouter(this, container, savedInstanceState); if (!router.hasRootController()) { - router.setRoot(RouterTransaction.with(new HomeController())); + + HomeController homeController = new HomeController(); + router.setRoot(RouterTransaction.with(homeController)); + handleIntentDataUri(homeController); + } + } + + /** + * will handle the data URI of the intent and when it is valid, navigate to the matching + * controller + * + * EXAMPLE: to test this via command line + * + *
+     * adb shell am start -W -a android.intent.action.VIEW
+     *   -d "http://bluelinelabs.com/demo/NAVIGATION"
+     *   com.bluelinelabs.conductor.demo
+     * 
+ *
+ * + * @param homeController the {@link HomeController} will handle the actual navigation + */ + private void handleIntentDataUri(HomeController homeController) { + final Intent intent = getIntent(); + Uri intentDataUri = intent.getData(); + if (intentDataUri == null || intentDataUri.getPath() == null) return; + + String path = intentDataUri.getPath(); + if (!path.startsWith(PATH_PREFIX)) { + Log.w(TAG, "unexpected path: " + intentDataUri.getPath()); + return; + } + + /* Example: http://bluelinelabs.com/demo/MASTER_DETAIL?item=1 + + intentDataUri.getPath() = /demo/MASTER_DETAIL + intentDataUri.getQuery() = item=1 + */ + + // skip the expected prefix, so that the remaining part is e.g. MASTER_DETAIL + String navigationPath = path.substring(PATH_PREFIX.length()); + if (!homeController.navigateTo(navigationPath)) { + Log.w(TAG, "cannot navigate to: " + intentDataUri.getPath()); } } diff --git a/demo/src/main/java/com/bluelinelabs/conductor/demo/controllers/HomeController.java b/demo/src/main/java/com/bluelinelabs/conductor/demo/controllers/HomeController.java index 5b9f3b1f..f060813c 100644 --- a/demo/src/main/java/com/bluelinelabs/conductor/demo/controllers/HomeController.java +++ b/demo/src/main/java/com/bluelinelabs/conductor/demo/controllers/HomeController.java @@ -130,6 +130,23 @@ protected String getTitle() { return "Conductor Demos"; } + /** + * will navigate to the controller represented by the @{code enumIdParam} parameter. + * + * @param enumIdParam can be any Id of the HomeDemoModel enumerations (case does not matter) + * @return @{code false} when we did not find a matching controller, @{code true} otherwise + */ + public boolean navigateTo(@NonNull String enumIdParam) { + String enumIdentifier = enumIdParam.toUpperCase(); + try { + HomeDemoModel hdm = HomeDemoModel.valueOf(enumIdentifier); + onModelRowClick(hdm); + return true; + } catch (IllegalArgumentException e) { + return false; + } + } + void onModelRowClick(HomeDemoModel model) { switch (model) { case NAVIGATION: From 104d96e6e2c370b1b037d373f9888b777d1dd46d Mon Sep 17 00:00:00 2001 From: Eric Kuck Date: Wed, 9 Nov 2016 10:09:00 -0600 Subject: [PATCH 03/75] Filled out @Nullable and @NonNull annotations throughout the library --- build.gradle | 2 +- .../conductor/ActivityHostedRouter.java | 23 ++-- .../com/bluelinelabs/conductor/Backstack.java | 22 ++-- .../conductor/ChangeHandlerFrameLayout.java | 6 +- .../com/bluelinelabs/conductor/Conductor.java | 4 +- .../bluelinelabs/conductor/Controller.java | 102 ++++++++++------- .../conductor/ControllerChangeHandler.java | 26 +++-- .../conductor/ControllerHostedRouter.java | 32 +++--- .../RestoreViewOnCreateController.java | 5 +- .../com/bluelinelabs/conductor/Router.java | 104 ++++++++++-------- .../conductor/RouterTransaction.java | 16 ++- .../changehandler/AnimatorChangeHandler.java | 12 +- .../AutoTransitionChangeHandler.java | 6 +- .../changehandler/FadeChangeHandler.java | 5 +- .../HorizontalChangeHandler.java | 5 +- .../SimpleSwapChangeHandler.java | 9 +- .../TransitionChangeHandler.java | 7 +- .../TransitionChangeHandlerCompat.java | 58 ++++------ .../changehandler/VerticalChangeHandler.java | 5 +- .../conductor/internal/ClassUtils.java | 17 ++- .../conductor/internal/LifecycleHandler.java | 32 ++++-- .../internal/NoOpControllerChangeHandler.java | 3 +- .../internal/StringSparseArrayParceler.java | 10 +- .../conductor/TestController.java | 2 +- .../CircularRevealChangeHandler.java | 2 +- .../CircularRevealChangeHandlerCompat.java | 2 +- .../demo/changehandler/FlipChangeHandler.java | 2 +- .../changehandler/ScaleFadeChangeHandler.java | 2 +- .../controllers/DragDismissController.java | 2 +- .../demo/controllers/PagerController.java | 6 +- .../controllers/RxLifecycleController.java | 2 +- .../base/ButterKnifeController.java | 2 +- demo/src/main/res/values/material_colors.xml | 36 +++--- 33 files changed, 322 insertions(+), 247 deletions(-) diff --git a/build.gradle b/build.gradle index 87b40877..82d92403 100755 --- a/build.gradle +++ b/build.gradle @@ -3,7 +3,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:2.2.0' + classpath 'com.android.tools.build:gradle:2.2.2' } } diff --git a/conductor/src/main/java/com/bluelinelabs/conductor/ActivityHostedRouter.java b/conductor/src/main/java/com/bluelinelabs/conductor/ActivityHostedRouter.java index 4f9f3cc8..352ba6ad 100644 --- a/conductor/src/main/java/com/bluelinelabs/conductor/ActivityHostedRouter.java +++ b/conductor/src/main/java/com/bluelinelabs/conductor/ActivityHostedRouter.java @@ -4,6 +4,7 @@ import android.content.Intent; import android.os.Bundle; import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.view.ViewGroup; import com.bluelinelabs.conductor.ControllerChangeHandler.ControllerChangeListener; @@ -30,13 +31,13 @@ public final void setHost(@NonNull LifecycleHandler lifecycleHandler, @NonNull V } } - @Override + @Override @Nullable public Activity getActivity() { return lifecycleHandler != null ? lifecycleHandler.getLifecycleActivity() : null; } @Override - public void onActivityDestroyed(Activity activity) { + public void onActivityDestroyed(@NonNull Activity activity) { super.onActivityDestroyed(activity); lifecycleHandler = null; } @@ -49,37 +50,37 @@ public final void invalidateOptionsMenu() { } @Override - public void onActivityResult(int requestCode, int resultCode, Intent data) { + public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { lifecycleHandler.onActivityResult(requestCode, resultCode, data); } @Override - void startActivity(Intent intent) { + void startActivity(@NonNull Intent intent) { lifecycleHandler.startActivity(intent); } @Override - void startActivityForResult(String instanceId, Intent intent, int requestCode) { + void startActivityForResult(@NonNull String instanceId, @NonNull Intent intent, int requestCode) { lifecycleHandler.startActivityForResult(instanceId, intent, requestCode); } @Override - void startActivityForResult(String instanceId, Intent intent, int requestCode, Bundle options) { + void startActivityForResult(@NonNull String instanceId, @NonNull Intent intent, int requestCode, @Nullable Bundle options) { lifecycleHandler.startActivityForResult(instanceId, intent, requestCode, options); } @Override - void registerForActivityResult(String instanceId, int requestCode) { + void registerForActivityResult(@NonNull String instanceId, int requestCode) { lifecycleHandler.registerForActivityResult(instanceId, requestCode); } @Override - void unregisterForActivityResults(String instanceId) { + void unregisterForActivityResults(@NonNull String instanceId) { lifecycleHandler.unregisterForActivityResults(instanceId); } @Override - void requestPermissions(String instanceId, @NonNull String[] permissions, int requestCode) { + void requestPermissions(@NonNull String instanceId, @NonNull String[] permissions, int requestCode) { lifecycleHandler.requestPermissions(instanceId, permissions, requestCode); } @@ -88,12 +89,12 @@ boolean hasHost() { return lifecycleHandler != null; } - @Override + @Override @NonNull List getSiblingRouters() { return lifecycleHandler.getRouters(); } - @Override + @Override @NonNull Router getRootRouter() { return this; } diff --git a/conductor/src/main/java/com/bluelinelabs/conductor/Backstack.java b/conductor/src/main/java/com/bluelinelabs/conductor/Backstack.java index 3ac02106..2563966c 100755 --- a/conductor/src/main/java/com/bluelinelabs/conductor/Backstack.java +++ b/conductor/src/main/java/com/bluelinelabs/conductor/Backstack.java @@ -1,6 +1,8 @@ package com.bluelinelabs.conductor; import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import java.util.ArrayDeque; import java.util.ArrayList; @@ -24,20 +26,23 @@ public int size() { return backStack.size(); } + @Nullable public RouterTransaction root() { return backStack.size() > 0 ? backStack.getLast() : null; } - @Override + @Override @NonNull public Iterator iterator() { return backStack.iterator(); } + @NonNull public Iterator reverseIterator() { return backStack.descendingIterator(); } - public List popTo(RouterTransaction transaction) { + @NonNull + public List popTo(@NonNull RouterTransaction transaction) { List popped = new ArrayList<>(); if (backStack.contains(transaction)) { while (backStack.peek() != transaction) { @@ -50,24 +55,27 @@ public List popTo(RouterTransaction transaction) { return popped; } + @NonNull public RouterTransaction pop() { RouterTransaction popped = backStack.pop(); popped.controller.destroy(); return popped; } + @Nullable public RouterTransaction peek() { return backStack.peek(); } - public void remove(RouterTransaction transaction) { + public void remove(@NonNull RouterTransaction transaction) { backStack.removeFirstOccurrence(transaction); } - public void push(RouterTransaction transaction) { + public void push(@NonNull RouterTransaction transaction) { backStack.push(transaction); } + @NonNull public List popAll() { List list = new ArrayList<>(); while (!isEmpty()) { @@ -76,7 +84,7 @@ public List popAll() { return list; } - public void setBackstack(List backstack) { + public void setBackstack(@NonNull List backstack) { for (RouterTransaction existingTransaction : backStack) { boolean contains = false; for (RouterTransaction newTransaction : backstack) { @@ -97,7 +105,7 @@ public void setBackstack(List backstack) { } } - public void saveInstanceState(Bundle outState) { + public void saveInstanceState(@NonNull Bundle outState) { ArrayList entryBundles = new ArrayList<>(backStack.size()); for (RouterTransaction entry : backStack) { entryBundles.add(entry.saveInstanceState()); @@ -106,7 +114,7 @@ public void saveInstanceState(Bundle outState) { outState.putParcelableArrayList(KEY_ENTRIES, entryBundles); } - public void restoreInstanceState(Bundle savedInstanceState) { + public void restoreInstanceState(@NonNull Bundle savedInstanceState) { ArrayList entryBundles = savedInstanceState.getParcelableArrayList(KEY_ENTRIES); if (entryBundles != null) { Collections.reverse(entryBundles); diff --git a/conductor/src/main/java/com/bluelinelabs/conductor/ChangeHandlerFrameLayout.java b/conductor/src/main/java/com/bluelinelabs/conductor/ChangeHandlerFrameLayout.java index 89eb4be6..50eefd0c 100644 --- a/conductor/src/main/java/com/bluelinelabs/conductor/ChangeHandlerFrameLayout.java +++ b/conductor/src/main/java/com/bluelinelabs/conductor/ChangeHandlerFrameLayout.java @@ -3,6 +3,8 @@ import android.annotation.TargetApi; import android.content.Context; import android.os.Build; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.ViewGroup; @@ -42,12 +44,12 @@ public boolean onInterceptTouchEvent(MotionEvent ev) { } @Override - public void onChangeStarted(Controller to, Controller from, boolean isPush, ViewGroup container, ControllerChangeHandler handler) { + public void onChangeStarted(@Nullable Controller to, @Nullable Controller from, boolean isPush, @NonNull ViewGroup container, @NonNull ControllerChangeHandler handler) { inProgressTransactionCount++; } @Override - public void onChangeCompleted(Controller to, Controller from, boolean isPush, ViewGroup container, ControllerChangeHandler handler) { + public void onChangeCompleted(@Nullable Controller to, @Nullable Controller from, boolean isPush, @NonNull ViewGroup container, @NonNull ControllerChangeHandler handler) { inProgressTransactionCount--; } diff --git a/conductor/src/main/java/com/bluelinelabs/conductor/Conductor.java b/conductor/src/main/java/com/bluelinelabs/conductor/Conductor.java index 3bc84704..63ff6850 100644 --- a/conductor/src/main/java/com/bluelinelabs/conductor/Conductor.java +++ b/conductor/src/main/java/com/bluelinelabs/conductor/Conductor.java @@ -3,6 +3,7 @@ import android.app.Activity; import android.os.Bundle; import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.view.ViewGroup; import com.bluelinelabs.conductor.internal.LifecycleHandler; @@ -26,7 +27,8 @@ private Conductor() {} * for restoring the Router's state if possible. * @return A fully configured {@link Router} instance for use with this Activity/ViewGroup pair. */ - public static Router attachRouter(@NonNull Activity activity, @NonNull ViewGroup container, Bundle savedInstanceState) { + @NonNull + public static Router attachRouter(@NonNull Activity activity, @NonNull ViewGroup container, @Nullable Bundle savedInstanceState) { LifecycleHandler lifecycleHandler = LifecycleHandler.install(activity); Router router = lifecycleHandler.getRouter(container, savedInstanceState); diff --git a/conductor/src/main/java/com/bluelinelabs/conductor/Controller.java b/conductor/src/main/java/com/bluelinelabs/conductor/Controller.java index 08d6c95f..4ca9dafe 100755 --- a/conductor/src/main/java/com/bluelinelabs/conductor/Controller.java +++ b/conductor/src/main/java/com/bluelinelabs/conductor/Controller.java @@ -10,6 +10,7 @@ import android.os.Parcelable; import android.support.annotation.IdRes; import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.text.TextUtils; import android.util.SparseArray; import android.view.LayoutInflater; @@ -90,17 +91,18 @@ public abstract class Controller { private final ControllerChangeListener childRouterChangeListener = new ControllerChangeListener() { @Override - public void onChangeStarted(Controller to, Controller from, boolean isPush, ViewGroup container, ControllerChangeHandler handler) { + public void onChangeStarted(@Nullable Controller to, @Nullable Controller from, boolean isPush, @NonNull ViewGroup container, @NonNull ControllerChangeHandler handler) { if (isPush) { onChildControllerPushed(to); } } @Override - public void onChangeCompleted(Controller to, Controller from, boolean isPush, ViewGroup container, ControllerChangeHandler handler) { } + public void onChangeCompleted(@Nullable Controller to, @Nullable Controller from, boolean isPush, @NonNull ViewGroup container, @NonNull ControllerChangeHandler handler) { } }; - static Controller newInstance(Bundle bundle) { + @NonNull + static Controller newInstance(@NonNull Bundle bundle) { final String className = bundle.getString(KEY_CLASS_NAME); //noinspection ConstantConditions Constructor[] constructors = ClassUtils.classForName(className, false).getConstructors(); @@ -134,7 +136,7 @@ protected Controller() { * * @param args Any arguments that need to be retained. */ - protected Controller(Bundle args) { + protected Controller(@Nullable Bundle args) { this.args = args; instanceId = UUID.randomUUID().toString(); ensureRequiredConstructor(); @@ -163,11 +165,19 @@ public final Router getRouter() { /** * Returns any arguments that were set in this Controller's constructor */ + @Nullable public Bundle getArgs() { return args; } - public final Router getChildRouter(@NonNull ViewGroup container, String tag) { + @NonNull + public final Router getChildRouter(@NonNull ViewGroup container) { + //noinspection deprecation + return getChildRouter(container, null); + } + + @Deprecated @NonNull + public final Router getChildRouter(@NonNull ViewGroup container, @Nullable String tag) { @IdRes final int containerId = container.getId(); ControllerHostedRouter childRouter = null; @@ -222,6 +232,7 @@ public final boolean isAttached() { /** * Return this Controller's View, if available. */ + @Nullable public final View getView() { return view; } @@ -229,6 +240,7 @@ public final View getView() { /** * Returns the host Activity of this Controller's {@link Router} */ + @Nullable public final Activity getActivity() { return router.getActivity(); } @@ -236,6 +248,7 @@ public final Activity getActivity() { /** * Returns the Resources from the host Activity */ + @Nullable public final Resources getResources() { Activity activity = getActivity(); return activity != null ? activity.getResources() : null; @@ -244,6 +257,7 @@ public final Resources getResources() { /** * Returns the Application Context derived from the host Activity */ + @Nullable public final Context getApplicationContext() { Activity activity = getActivity(); return activity != null ? activity.getApplicationContext() : null; @@ -252,6 +266,7 @@ public final Context getApplicationContext() { /** * Returns this Controller's parent Controller if it is a child Controller. */ + @Nullable public final Controller getParentController() { return parentController; } @@ -260,6 +275,7 @@ public final Controller getParentController() { * Returns this Controller's instance ID, which is generated when the instance is created and * retained across restarts. */ + @NonNull public final String getInstanceId() { return instanceId; } @@ -270,7 +286,8 @@ public final String getInstanceId() { * @param instanceId The instance ID being searched for * @return The matching Controller, if one exists */ - final Controller findController(String instanceId) { + @Nullable + final Controller findController(@NonNull String instanceId) { if (this.instanceId.equals(instanceId)) { return this; } @@ -287,6 +304,7 @@ final Controller findController(String instanceId) { /** * Returns all of this Controller's child Routers */ + @NonNull public final List getChildRouters() { List routers = new ArrayList<>(); for (Router router : childRouters) { @@ -302,7 +320,7 @@ public final List getChildRouters() { * * @param target The Controller that is the target of this one. */ - public void setTargetController(Controller target) { + public void setTargetController(@Nullable Controller target) { if (targetInstanceId != null) { throw new RuntimeException("Target controller already set. A controller's target may only be set once."); } @@ -315,6 +333,7 @@ public void setTargetController(Controller target) { * * @return This Controller's target */ + @Nullable public final Controller getTargetController() { if (targetInstanceId != null) { return router.getRootRouter().getControllerWithInstanceId(targetInstanceId); @@ -328,7 +347,7 @@ public final Controller getTargetController() { * * @param view The View to which this Controller should be bound. */ - protected void onDestroyView(View view) { } + protected void onDestroyView(@NonNull View view) { } /** * Called when this Controller begins the process of being swapped in or out of the host view. @@ -368,22 +387,22 @@ protected void onDestroy() { } /** * Called when this Controller's host Activity is started */ - protected void onActivityStarted(Activity activity) { } + protected void onActivityStarted(@NonNull Activity activity) { } /** * Called when this Controller's host Activity is resumed */ - protected void onActivityResumed(Activity activity) { } + protected void onActivityResumed(@NonNull Activity activity) { } /** * Called when this Controller's host Activity is paused */ - protected void onActivityPaused(Activity activity) { } + protected void onActivityPaused(@NonNull Activity activity) { } /** * Called when this Controller's host Activity is stopped */ - protected void onActivityStopped(Activity activity) { } + protected void onActivityStopped(@NonNull Activity activity) { } /** * Called to save this Controller's View state. As Views can be detached and destroyed as part of the @@ -422,7 +441,7 @@ protected void onRestoreInstanceState(@NonNull Bundle savedInstanceState) { } /** * Calls startActivity(Intent) from this Controller's host Activity. */ - public final void startActivity(final Intent intent) { + public final void startActivity(@NonNull final Intent intent) { executeWithRouter(new RouterRequiringFunc() { @Override public void execute() { router.startActivity(intent); } }); @@ -431,7 +450,7 @@ public final void startActivity(final Intent intent) { /** * Calls startActivityForResult(Intent, int) from this Controller's host Activity. */ - public final void startActivityForResult(final Intent intent, final int requestCode) { + public final void startActivityForResult(@NonNull final Intent intent, final int requestCode) { executeWithRouter(new RouterRequiringFunc() { @Override public void execute() { router.startActivityForResult(instanceId, intent, requestCode); } }); @@ -440,7 +459,7 @@ public final void startActivityForResult(final Intent intent, final int requestC /** * Calls startActivityForResult(Intent, int, Bundle) from this Controller's host Activity. */ - public final void startActivityForResult(final Intent intent, final int requestCode, final Bundle options) { + public final void startActivityForResult(@NonNull final Intent intent, final int requestCode, @Nullable final Bundle options) { executeWithRouter(new RouterRequiringFunc() { @Override public void execute() { router.startActivityForResult(instanceId, intent, requestCode, options); } }); @@ -466,7 +485,7 @@ public final void registerForActivityResult(final int requestCode) { * @param resultCode The resultCode that was returned to the host Activity's onActivityResult method * @param data The data Intent that was returned to the host Activity's onActivityResult method */ - public void onActivityResult(int requestCode, int resultCode, Intent data) { } + public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { } /** * Calls requestPermission(String[], int) from this Controller's host Activity. Results for this request, @@ -523,7 +542,7 @@ public boolean handleBack() { * * @param lifecycleListener The listener */ - public void addLifecycleListener(LifecycleListener lifecycleListener) { + public void addLifecycleListener(@NonNull LifecycleListener lifecycleListener) { if (!lifecycleListeners.contains(lifecycleListener)) { lifecycleListeners.add(lifecycleListener); } @@ -534,13 +553,14 @@ public void addLifecycleListener(LifecycleListener lifecycleListener) { * * @param lifecycleListener The listener to be removed */ - public void removeLifecycleListener(LifecycleListener lifecycleListener) { + public void removeLifecycleListener(@NonNull LifecycleListener lifecycleListener) { lifecycleListeners.remove(lifecycleListener); } /** * Returns this Controller's {@link RetainViewMode}. Defaults to {@link RetainViewMode#RELEASE_DETACH}. */ + @NonNull public RetainViewMode getRetainViewMode() { return retainViewMode; } @@ -549,8 +569,8 @@ public RetainViewMode getRetainViewMode() { * Sets this Controller's {@link RetainViewMode}, which will influence when its view will be released. * This is useful when a Controller's view hierarchy is expensive to tear down and rebuild. */ - public void setRetainViewMode(RetainViewMode retainViewMode) { - this.retainViewMode = retainViewMode; + public void setRetainViewMode(@NonNull RetainViewMode retainViewMode) { + this.retainViewMode = retainViewMode != null ? retainViewMode : RetainViewMode.RELEASE_DETACH; if (this.retainViewMode == RetainViewMode.RELEASE_DETACH && !attached) { removeViewReference(); } @@ -560,6 +580,7 @@ public void setRetainViewMode(RetainViewMode retainViewMode) { * Returns the {@link ControllerChangeHandler} that should be used for pushing this Controller, or null * if the handler from the {@link RouterTransaction} should be used instead. */ + @Nullable public final ControllerChangeHandler getOverriddenPushHandler() { return overriddenPushHandler; } @@ -568,7 +589,7 @@ public final ControllerChangeHandler getOverriddenPushHandler() { * Overrides the {@link ControllerChangeHandler} that should be used for pushing this Controller. If this is a * non-null value, it will be used instead of the handler from the {@link RouterTransaction}. */ - public void overridePushHandler(ControllerChangeHandler overriddenPushHandler) { + public void overridePushHandler(@Nullable ControllerChangeHandler overriddenPushHandler) { this.overriddenPushHandler = overriddenPushHandler; } @@ -576,6 +597,7 @@ public void overridePushHandler(ControllerChangeHandler overriddenPushHandler) { * Returns the {@link ControllerChangeHandler} that should be used for popping this Controller, or null * if the handler from the {@link RouterTransaction} should be used instead. */ + @Nullable public ControllerChangeHandler getOverriddenPopHandler() { return overriddenPopHandler; } @@ -584,7 +606,7 @@ public ControllerChangeHandler getOverriddenPopHandler() { * Overrides the {@link ControllerChangeHandler} that should be used for popping this Controller. If this is a * non-null value, it will be used instead of the handler from the {@link RouterTransaction}. */ - public void overridePopHandler(ControllerChangeHandler overriddenPopHandler) { + public void overridePopHandler(@Nullable ControllerChangeHandler overriddenPopHandler) { this.overriddenPopHandler = overriddenPopHandler; } @@ -628,7 +650,7 @@ public final void setOptionsMenuHidden(boolean optionsMenuHidden) { * @param menu The menu into which your options should be placed. * @param inflater The inflater that can be used to inflate your menu items. */ - public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { } + public void onCreateOptionsMenu(@Nullable Menu menu, @Nullable MenuInflater inflater) { } /** * Prepare the screen's options menu to be displayed. This is called directly before showing the @@ -636,7 +658,7 @@ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { } * * @param menu The menu that will be displayed */ - public void onPrepareOptionsMenu(Menu menu) { } + public void onPrepareOptionsMenu(@Nullable Menu menu) { } /** * Called when an option menu item has been selected by the user. @@ -644,7 +666,7 @@ public void onPrepareOptionsMenu(Menu menu) { } * @param item The selected item. * @return True if this event has been consumed, false if it has not. */ - public boolean onOptionsItemSelected(MenuItem item) { + public boolean onOptionsItemSelected(@Nullable MenuItem item) { return false; } @@ -696,11 +718,11 @@ final void executeWithRouter(@NonNull RouterRequiringFunc listener) { } } - final void activityStarted(Activity activity) { + final void activityStarted(@NonNull Activity activity) { onActivityStarted(activity); } - final void activityResumed(Activity activity) { + final void activityResumed(@NonNull Activity activity) { if (!attached && view != null && viewIsAttached) { attach(view); } else if (attached) { @@ -710,11 +732,11 @@ final void activityResumed(Activity activity) { onActivityResumed(activity); } - final void activityPaused(Activity activity) { + final void activityPaused(@NonNull Activity activity) { onActivityPaused(activity); } - final void activityStopped(Activity activity) { + final void activityStopped(@NonNull Activity activity) { onActivityStopped(activity); } @@ -1034,7 +1056,7 @@ private void performOnRestoreInstanceState() { } } - final void changeStarted(ControllerChangeHandler changeHandler, ControllerChangeType changeType) { + final void changeStarted(@NonNull ControllerChangeHandler changeHandler, @NonNull ControllerChangeType changeType) { if (!changeType.isEnter) { for (ControllerHostedRouter router : childRouters) { router.setDetachFrozen(true); @@ -1049,7 +1071,7 @@ final void changeStarted(ControllerChangeHandler changeHandler, ControllerChange } } - final void changeEnded(ControllerChangeHandler changeHandler, ControllerChangeType changeType) { + final void changeEnded(@NonNull ControllerChangeHandler changeHandler, @NonNull ControllerChangeType changeType) { if (!changeType.isEnter) { for (ControllerHostedRouter router : childRouters) { router.setDetachFrozen(false); @@ -1086,27 +1108,27 @@ final void setDetachFrozen(boolean frozen) { } } - final void createOptionsMenu(Menu menu, MenuInflater inflater) { + final void createOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) { if (attached && hasOptionsMenu && !optionsMenuHidden) { onCreateOptionsMenu(menu, inflater); } } - final void prepareOptionsMenu(Menu menu) { + final void prepareOptionsMenu(@NonNull Menu menu) { if (attached && hasOptionsMenu && !optionsMenuHidden) { onPrepareOptionsMenu(menu); } } - final boolean optionsItemSelected(MenuItem item) { + final boolean optionsItemSelected(@NonNull MenuItem item) { return attached && hasOptionsMenu && !optionsMenuHidden && onOptionsItemSelected(item); } - private void monitorChildRouter(ControllerHostedRouter childRouter) { + private void monitorChildRouter(@NonNull ControllerHostedRouter childRouter) { childRouter.addChangeListener(childRouterChangeListener); } - private void onChildControllerPushed(Controller controller) { + private void onChildControllerPushed(@NonNull Controller controller) { if (!childBackstack.contains(controller)) { childBackstack.add(controller); controller.addLifecycleListener(new LifecycleListener() { @@ -1118,7 +1140,7 @@ public void postDestroy(@NonNull Controller controller) { } } - final void setParentController(Controller controller) { + final void setParentController(@Nullable Controller controller) { parentController = controller; } @@ -1129,7 +1151,8 @@ private void ensureRequiredConstructor() { } } - private static Constructor getDefaultConstructor(Constructor[] constructors) { + @Nullable + private static Constructor getDefaultConstructor(@NonNull Constructor[] constructors) { for (Constructor constructor : constructors) { if (constructor.getParameterTypes().length == 0) { return constructor; @@ -1138,7 +1161,8 @@ private static Constructor getDefaultConstructor(Constructor[] constructors) { return null; } - private static Constructor getBundleConstructor(Constructor[] constructors) { + @Nullable + private static Constructor getBundleConstructor(@NonNull Constructor[] constructors) { for (Constructor constructor : constructors) { if (constructor.getParameterTypes().length == 1 && constructor.getParameterTypes()[0] == Bundle.class) { return constructor; diff --git a/conductor/src/main/java/com/bluelinelabs/conductor/ControllerChangeHandler.java b/conductor/src/main/java/com/bluelinelabs/conductor/ControllerChangeHandler.java index 1e137e5b..d4277f63 100644 --- a/conductor/src/main/java/com/bluelinelabs/conductor/ControllerChangeHandler.java +++ b/conductor/src/main/java/com/bluelinelabs/conductor/ControllerChangeHandler.java @@ -2,6 +2,7 @@ import android.os.Bundle; import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.view.View; import android.view.ViewGroup; import android.view.ViewParent; @@ -37,7 +38,7 @@ public abstract class ControllerChangeHandler { * @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); + public abstract void performChange(@NonNull ViewGroup container, @Nullable View from, @Nullable View to, boolean isPush, @NonNull ControllerChangeCompletedListener changeListener); public ControllerChangeHandler() { ensureDefaultConstructor(); @@ -64,7 +65,7 @@ public void restoreFromBundle(@NonNull Bundle bundle) { } * @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 */ - public void onAbortPush(@NonNull ControllerChangeHandler newHandler, Controller newTop) { } + public void onAbortPush(@NonNull ControllerChangeHandler newHandler, @Nullable Controller newTop) { } /** * Will be called on change handlers that push a controller if the controller being pushed is @@ -72,6 +73,7 @@ public void onAbortPush(@NonNull ControllerChangeHandler newHandler, Controller */ public void completeImmediately() { } + @NonNull final Bundle toBundle() { Bundle bundle = new Bundle(); bundle.putString(KEY_CLASS_NAME, getClass().getName()); @@ -83,6 +85,7 @@ final Bundle toBundle() { return bundle; } + @NonNull final ControllerChangeHandler copy() { return fromBundle(toBundle()); } @@ -95,7 +98,8 @@ private void ensureDefaultConstructor() { } } - public static ControllerChangeHandler fromBundle(Bundle bundle) { + @Nullable + public static ControllerChangeHandler fromBundle(@Nullable Bundle bundle) { if (bundle != null) { String className = bundle.getString(KEY_CLASS_NAME); ControllerChangeHandler changeHandler = ClassUtils.newInstance(className); @@ -107,7 +111,7 @@ public static ControllerChangeHandler fromBundle(Bundle bundle) { } } - static boolean completePushImmediately(String controllerInstanceId) { + static boolean completePushImmediately(@NonNull String controllerInstanceId) { ControllerChangeHandler changeHandler = inProgressPushHandlers.get(controllerInstanceId); if (changeHandler != null) { changeHandler.completeImmediately(); @@ -117,7 +121,7 @@ static boolean completePushImmediately(String controllerInstanceId) { return false; } - public static void abortPush(Controller toAbort, Controller newController, ControllerChangeHandler newChangeHandler) { + public static void abortPush(@NonNull Controller toAbort, @Nullable Controller newController, @NonNull ControllerChangeHandler newChangeHandler) { ControllerChangeHandler handlerForPush = inProgressPushHandlers.get(toAbort.getInstanceId()); if (handlerForPush != null) { handlerForPush.onAbortPush(newChangeHandler, newController); @@ -125,11 +129,11 @@ public static void abortPush(Controller toAbort, Controller newController, Contr } } - public static void executeChange(final Controller to, final Controller from, boolean isPush, ViewGroup container, ControllerChangeHandler inHandler) { + public static void executeChange(@Nullable final Controller to, @Nullable final Controller from, boolean isPush, @NonNull ViewGroup container, @NonNull ControllerChangeHandler inHandler) { executeChange(to, from, isPush, container, inHandler, new ArrayList()); } - public static void executeChange(final Controller to, final Controller from, final boolean isPush, final ViewGroup container, final ControllerChangeHandler inHandler, @NonNull final List listeners) { + public static void executeChange(@Nullable final Controller to, @Nullable final Controller from, final boolean isPush, @Nullable final ViewGroup container, @Nullable final ControllerChangeHandler inHandler, @NonNull final List listeners) { if (container != null) { final ControllerChangeHandler handler = inHandler != null ? inHandler : new SimpleSwapChangeHandler(); @@ -140,7 +144,7 @@ public static void executeChange(final Controller to, final Controller from, fin } for (ControllerChangeListener listener : listeners) { - listener.onChangeStarted(to, from, isPush, container, inHandler); + listener.onChangeStarted(to, from, isPush, container, handler); } final ControllerChangeType toChangeType = isPush ? ControllerChangeType.PUSH_ENTER : ControllerChangeType.POP_ENTER; @@ -175,7 +179,7 @@ public void onChangeCompleted() { } for (ControllerChangeListener listener : listeners) { - listener.onChangeCompleted(to, from, isPush, container, inHandler); + listener.onChangeCompleted(to, from, isPush, container, handler); } if (handler.forceRemoveViewOnPush && fromView != null) { @@ -210,7 +214,7 @@ public interface ControllerChangeListener { * @param container The containing ViewGroup * @param handler The change handler being used. */ - void onChangeStarted(Controller to, Controller from, boolean isPush, ViewGroup container, ControllerChangeHandler handler); + void onChangeStarted(@Nullable Controller to, @Nullable Controller from, boolean isPush, @NonNull ViewGroup container, @NonNull ControllerChangeHandler handler); /** * Called when a {@link ControllerChangeHandler} has completed changing {@link Controller}s @@ -221,7 +225,7 @@ public interface ControllerChangeListener { * @param container The containing ViewGroup * @param handler The change handler that was used. */ - void onChangeCompleted(Controller to, Controller from, boolean isPush, ViewGroup container, ControllerChangeHandler handler); + void onChangeCompleted(@Nullable Controller to, @Nullable Controller from, boolean isPush, @NonNull ViewGroup container, @NonNull ControllerChangeHandler handler); } /** diff --git a/conductor/src/main/java/com/bluelinelabs/conductor/ControllerHostedRouter.java b/conductor/src/main/java/com/bluelinelabs/conductor/ControllerHostedRouter.java index 50b42848..0b4e3283 100644 --- a/conductor/src/main/java/com/bluelinelabs/conductor/ControllerHostedRouter.java +++ b/conductor/src/main/java/com/bluelinelabs/conductor/ControllerHostedRouter.java @@ -5,6 +5,7 @@ import android.os.Bundle; import android.support.annotation.IdRes; import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.view.ViewGroup; import com.bluelinelabs.conductor.ControllerChangeHandler.ControllerChangeListener; @@ -24,7 +25,7 @@ class ControllerHostedRouter extends Router { ControllerHostedRouter() { } - ControllerHostedRouter(int hostId, String tag) { + ControllerHostedRouter(int hostId, @Nullable String tag) { this.hostId = hostId; this.tag = tag; } @@ -76,20 +77,20 @@ void destroy() { super.destroy(); } - @Override + @Override @Nullable public Activity getActivity() { return hostController != null ? hostController.getActivity() : null; } @Override - public void onActivityDestroyed(Activity activity) { + public void onActivityDestroyed(@NonNull Activity activity) { super.onActivityDestroyed(activity); removeHost(); } @Override - public void onActivityResult(int requestCode, int resultCode, Intent data) { + public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { if (hostController != null && hostController.getRouter() != null) { hostController.getRouter().onActivityResult(requestCode, resultCode, data); } @@ -103,42 +104,42 @@ public void invalidateOptionsMenu() { } @Override - void startActivity(Intent intent) { + void startActivity(@NonNull Intent intent) { if (hostController != null && hostController.getRouter() != null) { hostController.getRouter().startActivity(intent); } } @Override - void startActivityForResult(String instanceId, Intent intent, int requestCode) { + void startActivityForResult(@NonNull String instanceId, @NonNull Intent intent, int requestCode) { if (hostController != null && hostController.getRouter() != null) { hostController.getRouter().startActivityForResult(instanceId, intent, requestCode); } } @Override - void startActivityForResult(String instanceId, Intent intent, int requestCode, Bundle options) { + void startActivityForResult(@NonNull String instanceId, @NonNull Intent intent, int requestCode, @Nullable Bundle options) { if (hostController != null && hostController.getRouter() != null) { hostController.getRouter().startActivityForResult(instanceId, intent, requestCode, options); } } @Override - void registerForActivityResult(String instanceId, int requestCode) { + void registerForActivityResult(@NonNull String instanceId, int requestCode) { if (hostController != null && hostController.getRouter() != null) { hostController.getRouter().registerForActivityResult(instanceId, requestCode); } } @Override - void unregisterForActivityResults(String instanceId) { + void unregisterForActivityResults(@NonNull String instanceId) { if (hostController != null && hostController.getRouter() != null) { hostController.getRouter().unregisterForActivityResults(instanceId); } } @Override - void requestPermissions(String instanceId, String[] permissions, int requestCode) { + void requestPermissions(@NonNull String instanceId, @NonNull String[] permissions, int requestCode) { if (hostController != null && hostController.getRouter() != null) { hostController.getRouter().requestPermissions(instanceId, permissions, requestCode); } @@ -150,7 +151,7 @@ boolean hasHost() { } @Override - public void saveInstanceState(Bundle outState) { + public void saveInstanceState(@NonNull Bundle outState) { super.saveInstanceState(outState); outState.putInt(KEY_HOST_ID, hostId); @@ -158,7 +159,7 @@ public void saveInstanceState(Bundle outState) { } @Override - public void restoreInstanceState(Bundle savedInstanceState) { + public void restoreInstanceState(@NonNull Bundle savedInstanceState) { super.restoreInstanceState(savedInstanceState); hostId = savedInstanceState.getInt(KEY_HOST_ID); @@ -166,7 +167,7 @@ public void restoreInstanceState(Bundle savedInstanceState) { } @Override - void setControllerRouter(Controller controller) { + void setControllerRouter(@NonNull Controller controller) { super.setControllerRouter(controller); controller.setParentController(hostController); } @@ -175,11 +176,12 @@ int getHostId() { return hostId; } + @Nullable String getTag() { return tag; } - @Override + @Override @NonNull List getSiblingRouters() { List list = new ArrayList<>(); list.addAll(hostController.getChildRouters()); @@ -187,7 +189,7 @@ List getSiblingRouters() { return list; } - @Override + @Override @NonNull Router getRootRouter() { if (hostController != null && hostController.getRouter() != null) { return hostController.getRouter().getRootRouter(); diff --git a/conductor/src/main/java/com/bluelinelabs/conductor/RestoreViewOnCreateController.java b/conductor/src/main/java/com/bluelinelabs/conductor/RestoreViewOnCreateController.java index 1d9a2aa8..3b9160ef 100644 --- a/conductor/src/main/java/com/bluelinelabs/conductor/RestoreViewOnCreateController.java +++ b/conductor/src/main/java/com/bluelinelabs/conductor/RestoreViewOnCreateController.java @@ -26,12 +26,11 @@ protected RestoreViewOnCreateController() { * * @param args Any arguments that need to be retained. */ - protected RestoreViewOnCreateController(Bundle args) { + protected RestoreViewOnCreateController(@Nullable Bundle args) { super(args); } - @NonNull - @Override + @Override @NonNull protected final View onCreateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) { return onCreateView(inflater, container, viewState); } diff --git a/conductor/src/main/java/com/bluelinelabs/conductor/Router.java b/conductor/src/main/java/com/bluelinelabs/conductor/Router.java index 77aa2742..9c6f9002 100755 --- a/conductor/src/main/java/com/bluelinelabs/conductor/Router.java +++ b/conductor/src/main/java/com/bluelinelabs/conductor/Router.java @@ -4,6 +4,7 @@ import android.content.Intent; import android.os.Bundle; import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; @@ -41,6 +42,7 @@ public abstract class Router { /** * Returns this Router's host Activity */ + @Nullable public abstract Activity getActivity(); /** @@ -51,7 +53,7 @@ public abstract class Router { * @param resultCode The Activity's onActivityResult resultCode * @param data The Activity's onActivityResult data */ - public abstract void onActivityResult(int requestCode, int resultCode, Intent data); + public abstract void onActivityResult(int requestCode, int resultCode, @Nullable Intent data); /** * This should be called by the host Activity when its onRequestPermissionsResult method is called. The call will be forwarded @@ -62,7 +64,7 @@ public abstract class Router { * @param permissions The Activity's onRequestPermissionsResult permissions * @param grantResults The Activity's onRequestPermissionsResult grantResults */ - public void onRequestPermissionsResult(String instanceId, int requestCode, String[] permissions, int[] grantResults) { + public void onRequestPermissionsResult(@NonNull String instanceId, int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { Controller controller = getControllerWithInstanceId(instanceId); if (controller != null) { controller.requestPermissionsResult(requestCode, permissions, grantResults); @@ -77,6 +79,7 @@ public void onRequestPermissionsResult(String instanceId, int requestCode, Strin */ public boolean handleBack() { if (!backstack.isEmpty()) { + //noinspection ConstantConditions if (backstack.peek().controller.handleBack()) { return true; } else if (popCurrentController()) { @@ -93,7 +96,11 @@ public boolean handleBack() { * @return Whether or not this Router still has controllers remaining on it after popping. */ public boolean popCurrentController() { - return popController(backstack.peek().controller); + RouterTransaction transaction = backstack.peek(); + if (transaction == null) { + throw new IllegalStateException("Trying to pop the current controller when there are none on the backstack."); + } + return popController(transaction.controller); } /** @@ -102,7 +109,7 @@ public boolean popCurrentController() { * @param controller The controller that should be popped from this Router * @return Whether or not this Router still has controllers remaining on it after popping. */ - public boolean popController(Controller controller) { + public boolean popController(@NonNull Controller controller) { RouterTransaction topController = backstack.peek(); boolean poppingTopController = topController != null && topController.controller == controller; @@ -153,11 +160,13 @@ public void replaceTopController(@NonNull RouterTransaction transaction) { } 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()); + if (topTransaction != null) { + 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()); + } } } @@ -189,6 +198,7 @@ public int getContainerId() { * 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. */ + @NonNull public Router setPopsLastView(boolean popsLastView) { this.popsLastView = popsLastView; return this; @@ -209,8 +219,9 @@ public boolean popToRoot() { * @param changeHandler The {@link ControllerChangeHandler} to handle this transaction * @return Whether or not any {@link Controller}s were popped in order to get to the root transaction */ - public boolean popToRoot(ControllerChangeHandler changeHandler) { + public boolean popToRoot(@Nullable ControllerChangeHandler changeHandler) { if (backstack.size() > 1) { + //noinspection ConstantConditions popToTransaction(backstack.root(), changeHandler); return true; } else { @@ -235,7 +246,7 @@ public boolean popToTag(@NonNull String tag) { * @param changeHandler The {@link ControllerChangeHandler} to handle this transaction * @return Whether or not the {@link Controller} with the passed tag is now at the top */ - public boolean popToTag(@NonNull String tag, ControllerChangeHandler changeHandler) { + public boolean popToTag(@NonNull String tag, @Nullable ControllerChangeHandler changeHandler) { for (RouterTransaction transaction : backstack) { if (tag.equals(transaction.tag())) { popToTransaction(transaction, changeHandler); @@ -283,7 +294,8 @@ public void setRoot(@NonNull RouterTransaction transaction) { * @param instanceId The instance ID being searched for * @return The matching Controller, if one exists */ - public Controller getControllerWithInstanceId(String instanceId) { + @Nullable + public Controller getControllerWithInstanceId(@NonNull String instanceId) { for (RouterTransaction transaction : backstack) { Controller controllerWithId = transaction.controller.findController(instanceId); if (controllerWithId != null) { @@ -299,7 +311,8 @@ public Controller getControllerWithInstanceId(String instanceId) { * @param tag The tag being searched for * @return The matching Controller, if one exists */ - public Controller getControllerWithTag(String tag) { + @Nullable + public Controller getControllerWithTag(@NonNull String tag) { for (RouterTransaction transaction : backstack) { if (tag.equals(transaction.tag())) { return transaction.controller; @@ -318,6 +331,7 @@ public int getBackstackSize() { /** * Returns the current backstack, ordered from root to most recently pushed. */ + @NonNull public List getBackstack() { List list = new ArrayList<>(); Iterator backstackIterator = backstack.reverseIterator(); @@ -334,7 +348,7 @@ public List getBackstack() { * @param newBackstack The new backstack * @param changeHandler An optional change handler to be used to handle the root view of transition */ - public void setBackstack(@NonNull List newBackstack, ControllerChangeHandler changeHandler) { + public void setBackstack(@NonNull List newBackstack, @Nullable ControllerChangeHandler changeHandler) { List oldVisibleTransactions = getVisibleTransactions(backstack.iterator()); removeAllExceptVisibleAndUnowned(); @@ -370,7 +384,7 @@ public void setBackstack(@NonNull List newBackstack, Controll 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.copy()); + performControllerChange(transaction.controller, newVisibleTransactions.get(i - 1).controller, true, handler); } } } @@ -394,7 +408,7 @@ public boolean hasRootController() { * * @param changeListener The listener */ - public void addChangeListener(ControllerChangeListener changeListener) { + public void addChangeListener(@NonNull ControllerChangeListener changeListener) { if (!changeListeners.contains(changeListener)) { changeListeners.add(changeListener); } @@ -405,7 +419,7 @@ public void addChangeListener(ControllerChangeListener changeListener) { * * @param changeListener The listener to be removed */ - public void removeChangeListener(ControllerChangeListener changeListener) { + public void removeChangeListener(@NonNull ControllerChangeListener changeListener) { changeListeners.remove(changeListener); } @@ -423,14 +437,14 @@ public void rebindIfNeeded() { } } - public final void onActivityResult(String instanceId, int requestCode, int resultCode, Intent data) { + public final void onActivityResult(@NonNull String instanceId, int requestCode, int resultCode, @Nullable Intent data) { Controller controller = getControllerWithInstanceId(instanceId); if (controller != null) { controller.onActivityResult(requestCode, resultCode, data); } } - public final void onActivityStarted(Activity activity) { + public final void onActivityStarted(@NonNull Activity activity) { for (RouterTransaction transaction : backstack) { transaction.controller.activityStarted(activity); @@ -440,7 +454,7 @@ public final void onActivityStarted(Activity activity) { } } - public final void onActivityResumed(Activity activity) { + public final void onActivityResumed(@NonNull Activity activity) { for (RouterTransaction transaction : backstack) { transaction.controller.activityResumed(activity); @@ -450,7 +464,7 @@ public final void onActivityResumed(Activity activity) { } } - public final void onActivityPaused(Activity activity) { + public final void onActivityPaused(@NonNull Activity activity) { for (RouterTransaction transaction : backstack) { transaction.controller.activityPaused(activity); @@ -460,7 +474,7 @@ public final void onActivityPaused(Activity activity) { } } - public final void onActivityStopped(Activity activity) { + public final void onActivityStopped(@NonNull Activity activity) { for (RouterTransaction transaction : backstack) { transaction.controller.activityStopped(activity); @@ -470,7 +484,7 @@ public final void onActivityStopped(Activity activity) { } } - public void onActivityDestroyed(Activity activity) { + public void onActivityDestroyed(@NonNull Activity activity) { prepareForContainerRemoval(); changeListeners.clear(); @@ -503,7 +517,7 @@ public void prepareForHostDetach() { } } - public void saveInstanceState(Bundle outState) { + public void saveInstanceState(@NonNull Bundle outState) { prepareForHostDetach(); Bundle backstackState = new Bundle(); @@ -513,7 +527,7 @@ public void saveInstanceState(Bundle outState) { outState.putBoolean(KEY_POPS_LAST_VIEW, popsLastView); } - public void restoreInstanceState(Bundle savedInstanceState) { + public void restoreInstanceState(@NonNull Bundle savedInstanceState) { Bundle backstackBundle = savedInstanceState.getParcelable(KEY_BACKSTACK); backstack.restoreInstanceState(backstackBundle); popsLastView = savedInstanceState.getBoolean(KEY_POPS_LAST_VIEW); @@ -524,7 +538,7 @@ public void restoreInstanceState(Bundle savedInstanceState) { } } - public final void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + public final void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) { for (RouterTransaction transaction : backstack) { transaction.controller.createOptionsMenu(menu, inflater); @@ -534,7 +548,7 @@ public final void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { } } - public final void onPrepareOptionsMenu(Menu menu) { + public final void onPrepareOptionsMenu(@NonNull Menu menu) { for (RouterTransaction transaction : backstack) { transaction.controller.prepareOptionsMenu(menu); @@ -544,7 +558,7 @@ public final void onPrepareOptionsMenu(Menu menu) { } } - public final boolean onOptionsItemSelected(MenuItem item) { + public final boolean onOptionsItemSelected(@NonNull MenuItem item) { for (RouterTransaction transaction : backstack) { if (transaction.controller.optionsItemSelected(item)) { return true; @@ -559,7 +573,7 @@ public final boolean onOptionsItemSelected(MenuItem item) { return false; } - private void popToTransaction(@NonNull RouterTransaction transaction, ControllerChangeHandler changeHandler) { + private void popToTransaction(@NonNull RouterTransaction transaction, @Nullable ControllerChangeHandler changeHandler) { RouterTransaction topTransaction = backstack.peek(); List poppedTransactions = backstack.popTo(transaction); trackDestroyingControllers(poppedTransactions); @@ -579,6 +593,7 @@ void prepareForContainerRemoval() { } } + @NonNull final List getControllers() { List controllers = new ArrayList<>(); @@ -590,6 +605,7 @@ final List getControllers() { return controllers; } + @Nullable public final Boolean handleRequestedPermission(@NonNull String permission) { for (RouterTransaction transaction : backstack) { if (transaction.controller.didRequestPermission(permission)) { @@ -599,7 +615,7 @@ public final Boolean handleRequestedPermission(@NonNull String permission) { return null; } - private void performControllerChange(RouterTransaction to, RouterTransaction from, boolean isPush) { + private void performControllerChange(@Nullable RouterTransaction to, @Nullable RouterTransaction from, boolean isPush) { if (isPush && to != null) { to.onAttachedToRouter(); } @@ -620,7 +636,7 @@ private void performControllerChange(RouterTransaction to, RouterTransaction fro performControllerChange(toController, fromController, isPush, changeHandler); } - private void performControllerChange(final Controller to, final Controller from, boolean isPush, @NonNull ControllerChangeHandler changeHandler) { + private void performControllerChange(@Nullable final Controller to, @Nullable final Controller from, boolean isPush, @Nullable ControllerChangeHandler changeHandler) { if (to != null) { setControllerRouter(to); } else if (backstack.size() == 0 && !popsLastView) { @@ -636,7 +652,7 @@ private void pushToBackstack(@NonNull RouterTransaction entry) { backstack.push(entry); } - private void trackDestroyingController(RouterTransaction transaction) { + private void trackDestroyingController(@NonNull RouterTransaction transaction) { if (!transaction.controller.isDestroyed()) { destroyingControllers.add(transaction.controller); @@ -649,7 +665,7 @@ public void postDestroy(@NonNull Controller controller) { } } - private void trackDestroyingControllers(List transactions) { + private void trackDestroyingControllers(@NonNull List transactions) { for (RouterTransaction transaction : transactions) { trackDestroyingController(transaction); } @@ -679,7 +695,7 @@ private void removeAllExceptVisibleAndUnowned() { } } - private void addRouterViewsToList(Router router, List list) { + private void addRouterViewsToList(@NonNull Router router, @NonNull List list) { for (Controller controller : router.getControllers()) { if (controller.getView() != null) { list.add(controller.getView()); @@ -691,7 +707,7 @@ private void addRouterViewsToList(Router router, List list) { } } - private List getVisibleTransactions(Iterator backstackIterator) { + private List getVisibleTransactions(@NonNull Iterator backstackIterator) { List transactions = new ArrayList<>(); while (backstackIterator.hasNext()) { RouterTransaction transaction = backstackIterator.next(); @@ -706,18 +722,18 @@ private List getVisibleTransactions(Iterator getSiblingRouters(); - abstract Router getRootRouter(); + @NonNull abstract List getSiblingRouters(); + @NonNull abstract Router getRootRouter(); } diff --git a/conductor/src/main/java/com/bluelinelabs/conductor/RouterTransaction.java b/conductor/src/main/java/com/bluelinelabs/conductor/RouterTransaction.java index d45cbbf4..67a5813f 100644 --- a/conductor/src/main/java/com/bluelinelabs/conductor/RouterTransaction.java +++ b/conductor/src/main/java/com/bluelinelabs/conductor/RouterTransaction.java @@ -2,6 +2,7 @@ import android.os.Bundle; import android.support.annotation.NonNull; +import android.support.annotation.Nullable; /** * Metadata used for adding {@link Controller}s to a {@link Router}. @@ -21,6 +22,7 @@ public class RouterTransaction { private ControllerChangeHandler popControllerChangeHandler; private boolean attachedToRouter; + @NonNull public static RouterTransaction with(@NonNull Controller controller) { return new RouterTransaction(controller); } @@ -41,15 +43,18 @@ void onAttachedToRouter() { attachedToRouter = true; } + @NonNull public Controller controller() { return controller; } + @Nullable public String tag() { return tag; } - public RouterTransaction tag(String tag) { + @NonNull + public RouterTransaction tag(@Nullable String tag) { if (!attachedToRouter) { this.tag = tag; return this; @@ -58,6 +63,7 @@ public RouterTransaction tag(String tag) { } } + @Nullable public ControllerChangeHandler pushChangeHandler() { ControllerChangeHandler handler = controller.getOverriddenPushHandler(); if (handler == null) { @@ -66,7 +72,8 @@ public ControllerChangeHandler pushChangeHandler() { return handler; } - public RouterTransaction pushChangeHandler(ControllerChangeHandler handler) { + @NonNull + public RouterTransaction pushChangeHandler(@Nullable ControllerChangeHandler handler) { if (!attachedToRouter) { pushControllerChangeHandler = handler; return this; @@ -75,6 +82,7 @@ public RouterTransaction pushChangeHandler(ControllerChangeHandler handler) { } } + @Nullable public ControllerChangeHandler popChangeHandler() { ControllerChangeHandler handler = controller.getOverriddenPopHandler(); if (handler == null) { @@ -83,7 +91,8 @@ public ControllerChangeHandler popChangeHandler() { return handler; } - public RouterTransaction popChangeHandler(ControllerChangeHandler handler) { + @NonNull + public RouterTransaction popChangeHandler(@Nullable ControllerChangeHandler handler) { if (!attachedToRouter) { popControllerChangeHandler = handler; return this; @@ -95,6 +104,7 @@ public RouterTransaction popChangeHandler(ControllerChangeHandler handler) { /** * Used to serialize this transaction into a Bundle */ + @NonNull public Bundle saveInstanceState() { Bundle bundle = new Bundle(); diff --git a/conductor/src/main/java/com/bluelinelabs/conductor/changehandler/AnimatorChangeHandler.java b/conductor/src/main/java/com/bluelinelabs/conductor/changehandler/AnimatorChangeHandler.java index 9bb7328f..c86be644 100755 --- a/conductor/src/main/java/com/bluelinelabs/conductor/changehandler/AnimatorChangeHandler.java +++ b/conductor/src/main/java/com/bluelinelabs/conductor/changehandler/AnimatorChangeHandler.java @@ -5,6 +5,7 @@ import android.animation.AnimatorListenerAdapter; import android.os.Bundle; import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.view.View; import android.view.ViewGroup; import android.view.ViewTreeObserver; @@ -61,7 +62,7 @@ public void restoreFromBundle(@NonNull Bundle bundle) { } @Override - public void onAbortPush(@NonNull ControllerChangeHandler newHandler, Controller newTop) { + public void onAbortPush(@NonNull ControllerChangeHandler newHandler, @Nullable Controller newTop) { super.onAbortPush(newHandler, newTop); canceled = true; @@ -98,7 +99,8 @@ public boolean removesFromViewOnPush() { * @param isPush True if this is a push transaction, false if it's a pop. * @param toAddedToContainer True if the "to" view was added to the container as a part of this ChangeHandler. False if it was already in the hierarchy. */ - protected abstract Animator getAnimator(@NonNull ViewGroup container, View from, View to, boolean isPush, boolean toAddedToContainer); + @NonNull + protected abstract Animator getAnimator(@NonNull ViewGroup container, @Nullable View from, @Nullable View to, boolean isPush, boolean toAddedToContainer); /** * Will be called after the animation is complete to reset the View that was removed to its pre-animation state. @@ -106,7 +108,7 @@ public boolean removesFromViewOnPush() { protected abstract void resetFromView(@NonNull View from); @Override - public final void performChange(@NonNull final ViewGroup container, final View from, final View to, final boolean isPush, @NonNull final ControllerChangeCompletedListener changeListener) { + public final void performChange(@NonNull final ViewGroup container, @Nullable final View from, @Nullable final View to, final boolean isPush, @NonNull final ControllerChangeCompletedListener changeListener) { boolean readyToAnimate = true; final boolean addingToView = to != null && to.getParent() == null; @@ -138,7 +140,7 @@ public boolean onPreDraw() { } } - private void complete(ControllerChangeCompletedListener changeListener, AnimatorListener animatorListener) { + private void complete(@NonNull ControllerChangeCompletedListener changeListener, @Nullable AnimatorListener animatorListener) { if (!completed) { completed = true; changeListener.onChangeCompleted(); @@ -152,7 +154,7 @@ private void complete(ControllerChangeCompletedListener changeListener, Animator } } - private void performAnimation(@NonNull final ViewGroup container, final View from, final View to, final boolean isPush, final boolean toAddedToContainer, @NonNull final ControllerChangeCompletedListener changeListener) { + private void performAnimation(@NonNull final ViewGroup container, @Nullable final View from, @Nullable final View to, final boolean isPush, final boolean toAddedToContainer, @NonNull final ControllerChangeCompletedListener changeListener) { if (canceled) { complete(changeListener, null); return; diff --git a/conductor/src/main/java/com/bluelinelabs/conductor/changehandler/AutoTransitionChangeHandler.java b/conductor/src/main/java/com/bluelinelabs/conductor/changehandler/AutoTransitionChangeHandler.java index f19ff60a..72a47913 100755 --- a/conductor/src/main/java/com/bluelinelabs/conductor/changehandler/AutoTransitionChangeHandler.java +++ b/conductor/src/main/java/com/bluelinelabs/conductor/changehandler/AutoTransitionChangeHandler.java @@ -3,6 +3,7 @@ import android.annotation.TargetApi; import android.os.Build; import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.transition.AutoTransition; import android.transition.Transition; import android.view.View; @@ -14,9 +15,8 @@ @TargetApi(Build.VERSION_CODES.LOLLIPOP) public class AutoTransitionChangeHandler extends TransitionChangeHandler { - @Override - @NonNull - protected Transition getTransition(@NonNull ViewGroup container, View from, View to, boolean isPush) { + @Override @NonNull + protected Transition getTransition(@NonNull ViewGroup container, @Nullable View from, @Nullable View to, boolean isPush) { return new AutoTransition(); } diff --git a/conductor/src/main/java/com/bluelinelabs/conductor/changehandler/FadeChangeHandler.java b/conductor/src/main/java/com/bluelinelabs/conductor/changehandler/FadeChangeHandler.java index e91c23d3..beba8665 100755 --- a/conductor/src/main/java/com/bluelinelabs/conductor/changehandler/FadeChangeHandler.java +++ b/conductor/src/main/java/com/bluelinelabs/conductor/changehandler/FadeChangeHandler.java @@ -4,6 +4,7 @@ import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.view.View; import android.view.ViewGroup; @@ -26,8 +27,8 @@ public FadeChangeHandler(long duration, boolean removesFromViewOnPush) { super(duration, removesFromViewOnPush); } - @Override - protected Animator getAnimator(@NonNull ViewGroup container, View from, View to, boolean isPush, boolean toAddedToContainer) { + @Override @NonNull + protected Animator getAnimator(@NonNull ViewGroup container, @Nullable View from, @Nullable View to, boolean isPush, boolean toAddedToContainer) { AnimatorSet animator = new AnimatorSet(); if (to != null && toAddedToContainer) { animator.play(ObjectAnimator.ofFloat(to, View.ALPHA, 0, 1)); diff --git a/conductor/src/main/java/com/bluelinelabs/conductor/changehandler/HorizontalChangeHandler.java b/conductor/src/main/java/com/bluelinelabs/conductor/changehandler/HorizontalChangeHandler.java index e8886acf..06f1d438 100755 --- a/conductor/src/main/java/com/bluelinelabs/conductor/changehandler/HorizontalChangeHandler.java +++ b/conductor/src/main/java/com/bluelinelabs/conductor/changehandler/HorizontalChangeHandler.java @@ -4,6 +4,7 @@ import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.view.View; import android.view.ViewGroup; @@ -26,8 +27,8 @@ public HorizontalChangeHandler(long duration, boolean removesFromViewOnPush) { super(duration, removesFromViewOnPush); } - @Override - protected Animator getAnimator(@NonNull ViewGroup container, View from, View to, boolean isPush, boolean toAddedToContainer) { + @Override @NonNull + protected Animator getAnimator(@NonNull ViewGroup container, @Nullable View from, @Nullable View to, boolean isPush, boolean toAddedToContainer) { AnimatorSet animatorSet = new AnimatorSet(); if (isPush) { diff --git a/conductor/src/main/java/com/bluelinelabs/conductor/changehandler/SimpleSwapChangeHandler.java b/conductor/src/main/java/com/bluelinelabs/conductor/changehandler/SimpleSwapChangeHandler.java index f8b12c5c..b39a5b38 100755 --- a/conductor/src/main/java/com/bluelinelabs/conductor/changehandler/SimpleSwapChangeHandler.java +++ b/conductor/src/main/java/com/bluelinelabs/conductor/changehandler/SimpleSwapChangeHandler.java @@ -2,6 +2,7 @@ import android.os.Bundle; import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.view.View; import android.view.View.OnAttachStateChangeListener; import android.view.ViewGroup; @@ -44,7 +45,7 @@ public void restoreFromBundle(@NonNull Bundle bundle) { } @Override - public void onAbortPush(@NonNull ControllerChangeHandler newHandler, Controller newTop) { + public void onAbortPush(@NonNull ControllerChangeHandler newHandler, @Nullable Controller newTop) { super.onAbortPush(newHandler, newTop); canceled = true; @@ -62,7 +63,7 @@ public void completeImmediately() { } @Override - public void performChange(@NonNull ViewGroup container, View from, View to, boolean isPush, @NonNull ControllerChangeCompletedListener changeListener) { + public void performChange(@NonNull ViewGroup container, @Nullable View from, @Nullable View to, boolean isPush, @NonNull ControllerChangeCompletedListener changeListener) { if (!canceled) { if (from != null && (!isPush || removesFromViewOnPush)) { container.removeView(from); @@ -89,7 +90,7 @@ public boolean removesFromViewOnPush() { } @Override - public void onViewAttachedToWindow(View v) { + public void onViewAttachedToWindow(@NonNull View v) { v.removeOnAttachStateChangeListener(this); if (changeListener != null) { @@ -100,5 +101,5 @@ public void onViewAttachedToWindow(View v) { } @Override - public void onViewDetachedFromWindow(View v) { } + public void onViewDetachedFromWindow(@NonNull View v) { } } diff --git a/conductor/src/main/java/com/bluelinelabs/conductor/changehandler/TransitionChangeHandler.java b/conductor/src/main/java/com/bluelinelabs/conductor/changehandler/TransitionChangeHandler.java index 630dac7a..ebdb416a 100644 --- a/conductor/src/main/java/com/bluelinelabs/conductor/changehandler/TransitionChangeHandler.java +++ b/conductor/src/main/java/com/bluelinelabs/conductor/changehandler/TransitionChangeHandler.java @@ -3,6 +3,7 @@ import android.annotation.TargetApi; import android.os.Build; import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.transition.Transition; import android.transition.Transition.TransitionListener; import android.transition.TransitionManager; @@ -29,17 +30,17 @@ public abstract class TransitionChangeHandler extends ControllerChangeHandler { * @param isPush True if this is a push transaction, false if it's a pop. */ @NonNull - protected abstract Transition getTransition(@NonNull ViewGroup container, View from, View to, boolean isPush); + protected abstract Transition getTransition(@NonNull ViewGroup container, @Nullable View from, @Nullable View to, boolean isPush); @Override - public void onAbortPush(@NonNull ControllerChangeHandler newHandler, Controller newTop) { + public void onAbortPush(@NonNull ControllerChangeHandler newHandler, @Nullable Controller newTop) { super.onAbortPush(newHandler, newTop); canceled = true; } @Override - public void performChange(@NonNull final ViewGroup container, View from, View to, boolean isPush, @NonNull final ControllerChangeCompletedListener changeListener) { + public void performChange(@NonNull final ViewGroup container, @Nullable View from, @Nullable View to, boolean isPush, @NonNull final ControllerChangeCompletedListener changeListener) { if (canceled) { changeListener.onChangeCompleted(); return; diff --git a/conductor/src/main/java/com/bluelinelabs/conductor/changehandler/TransitionChangeHandlerCompat.java b/conductor/src/main/java/com/bluelinelabs/conductor/changehandler/TransitionChangeHandlerCompat.java index 39699dfe..6a00d179 100644 --- a/conductor/src/main/java/com/bluelinelabs/conductor/changehandler/TransitionChangeHandlerCompat.java +++ b/conductor/src/main/java/com/bluelinelabs/conductor/changehandler/TransitionChangeHandlerCompat.java @@ -3,6 +3,7 @@ import android.os.Build; import android.os.Bundle; import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.view.View; import android.view.ViewGroup; @@ -15,13 +16,10 @@ */ public class TransitionChangeHandlerCompat extends ControllerChangeHandler { - private static final String KEY_TRANSITION_HANDLER_CLASS = "TransitionChangeHandlerCompat.transitionChangeHandler.class"; - private static final String KEY_FALLBACK_HANDLER_CLASS = "TransitionChangeHandlerCompat.fallbackChangeHandler.class"; - private static final String KEY_TRANSITION_HANDLER_STATE = "TransitionChangeHandlerCompat.transitionChangeHandler.state"; - private static final String KEY_FALLBACK_HANDLER_STATE = "TransitionChangeHandlerCompat.fallbackChangeHandler.state"; + private static final String KEY_CHANGE_HANDLER_CLASS = "TransitionChangeHandlerCompat.changeHandler.class"; + private static final String KEY_HANDLER_STATE = "TransitionChangeHandlerCompat.changeHandler.state"; - private TransitionChangeHandler transitionChangeHandler; - private ControllerChangeHandler fallbackChangeHandler; + private ControllerChangeHandler changeHandler; public TransitionChangeHandlerCompat() { } @@ -32,57 +30,43 @@ public TransitionChangeHandlerCompat() { } * @param transitionChangeHandler The change handler that will be used on API 21 and above * @param fallbackChangeHandler The change handler that will be used on APIs below 21 */ - public TransitionChangeHandlerCompat(TransitionChangeHandler transitionChangeHandler, ControllerChangeHandler fallbackChangeHandler) { - this.transitionChangeHandler = transitionChangeHandler; - this.fallbackChangeHandler = fallbackChangeHandler; - } - - @Override - public void performChange(@NonNull final ViewGroup container, View from, View to, boolean isPush, @NonNull final ControllerChangeCompletedListener changeListener) { + public TransitionChangeHandlerCompat(@NonNull TransitionChangeHandler transitionChangeHandler, @NonNull ControllerChangeHandler fallbackChangeHandler) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - transitionChangeHandler.performChange(container, from, to, isPush, changeListener); + changeHandler = transitionChangeHandler; } else { - fallbackChangeHandler.performChange(container, from, to, isPush, changeListener); + changeHandler = fallbackChangeHandler; } } + @Override + public void performChange(@NonNull final ViewGroup container, @Nullable View from, @Nullable View to, boolean isPush, @NonNull final ControllerChangeCompletedListener changeListener) { + changeHandler.performChange(container, from, to, isPush, changeListener); + } + @Override public void saveToBundle(@NonNull Bundle bundle) { super.saveToBundle(bundle); - bundle.putString(KEY_TRANSITION_HANDLER_CLASS, transitionChangeHandler.getClass().getName()); - bundle.putString(KEY_FALLBACK_HANDLER_CLASS, fallbackChangeHandler.getClass().getName()); - - Bundle transitionBundle = new Bundle(); - transitionChangeHandler.saveToBundle(transitionBundle); - bundle.putBundle(KEY_TRANSITION_HANDLER_STATE, transitionBundle); + bundle.putString(KEY_CHANGE_HANDLER_CLASS, changeHandler.getClass().getName()); - Bundle fallbackBundle = new Bundle(); - fallbackChangeHandler.saveToBundle(fallbackBundle); - bundle.putBundle(KEY_FALLBACK_HANDLER_STATE, fallbackBundle); + Bundle stateBundle = new Bundle(); + changeHandler.saveToBundle(stateBundle); + bundle.putBundle(KEY_HANDLER_STATE, stateBundle); } @Override public void restoreFromBundle(@NonNull Bundle bundle) { super.restoreFromBundle(bundle); - String transitionClassName = bundle.getString(KEY_TRANSITION_HANDLER_CLASS); - transitionChangeHandler = ClassUtils.newInstance(transitionClassName); + String className = bundle.getString(KEY_CHANGE_HANDLER_CLASS); + changeHandler = ClassUtils.newInstance(className); //noinspection ConstantConditions - transitionChangeHandler.restoreFromBundle(bundle.getBundle(KEY_TRANSITION_HANDLER_STATE)); - - String fallbackClassName = bundle.getString(KEY_FALLBACK_HANDLER_CLASS); - fallbackChangeHandler = ClassUtils.newInstance(fallbackClassName); - //noinspection ConstantConditions - fallbackChangeHandler.restoreFromBundle(bundle.getBundle(KEY_FALLBACK_HANDLER_STATE)); + changeHandler.restoreFromBundle(bundle.getBundle(KEY_HANDLER_STATE)); } @Override public boolean removesFromViewOnPush() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - return transitionChangeHandler.removesFromViewOnPush(); - } else { - return fallbackChangeHandler.removesFromViewOnPush(); - } + return changeHandler.removesFromViewOnPush(); } + } diff --git a/conductor/src/main/java/com/bluelinelabs/conductor/changehandler/VerticalChangeHandler.java b/conductor/src/main/java/com/bluelinelabs/conductor/changehandler/VerticalChangeHandler.java index 4030cc4b..7322858b 100755 --- a/conductor/src/main/java/com/bluelinelabs/conductor/changehandler/VerticalChangeHandler.java +++ b/conductor/src/main/java/com/bluelinelabs/conductor/changehandler/VerticalChangeHandler.java @@ -4,6 +4,7 @@ import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.view.View; import android.view.ViewGroup; @@ -30,8 +31,8 @@ public VerticalChangeHandler(long duration, boolean removesFromViewOnPush) { super(duration, removesFromViewOnPush); } - @Override - protected Animator getAnimator(@NonNull ViewGroup container, View from, View to, boolean isPush, boolean toAddedToContainer) { + @Override @NonNull + protected Animator getAnimator(@NonNull ViewGroup container, @Nullable View from, @Nullable View to, boolean isPush, boolean toAddedToContainer) { AnimatorSet animator = new AnimatorSet(); List viewAnimators = new ArrayList<>(); diff --git a/conductor/src/main/java/com/bluelinelabs/conductor/internal/ClassUtils.java b/conductor/src/main/java/com/bluelinelabs/conductor/internal/ClassUtils.java index 34ee53f6..de6dccc3 100644 --- a/conductor/src/main/java/com/bluelinelabs/conductor/internal/ClassUtils.java +++ b/conductor/src/main/java/com/bluelinelabs/conductor/internal/ClassUtils.java @@ -1,16 +1,13 @@ package com.bluelinelabs.conductor.internal; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.text.TextUtils; public class ClassUtils { - @SuppressWarnings("unchecked") - public static Class classForName(String className) { - return classForName(className, true); - } - - @SuppressWarnings("unchecked") - public static Class classForName(String className, boolean allowEmptyName) { + @Nullable @SuppressWarnings("unchecked") + public static Class classForName(@NonNull String className, boolean allowEmptyName) { if (allowEmptyName && TextUtils.isEmpty(className)) { return null; } @@ -22,10 +19,10 @@ public static Class classForName(String className, boolean allo } } - @SuppressWarnings("unchecked") - public static T newInstance(String className) { + @Nullable @SuppressWarnings("unchecked") + public static T newInstance(@NonNull String className) { try { - Class cls = classForName(className); + Class cls = classForName(className, true); return cls != null ? cls.newInstance() : null; } catch (Exception e) { throw new RuntimeException("An exception occurred while creating a new instance of " + className + ". " + e.getMessage()); diff --git a/conductor/src/main/java/com/bluelinelabs/conductor/internal/LifecycleHandler.java b/conductor/src/main/java/com/bluelinelabs/conductor/internal/LifecycleHandler.java index 1c23da7c..456b2ec6 100644 --- a/conductor/src/main/java/com/bluelinelabs/conductor/internal/LifecycleHandler.java +++ b/conductor/src/main/java/com/bluelinelabs/conductor/internal/LifecycleHandler.java @@ -11,6 +11,7 @@ import android.os.Parcel; import android.os.Parcelable; import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.util.SparseArray; import android.view.Menu; import android.view.MenuInflater; @@ -50,7 +51,8 @@ public LifecycleHandler() { setHasOptionsMenu(true); } - private static LifecycleHandler findInActivity(Activity activity) { + @Nullable + private static LifecycleHandler findInActivity(@NonNull Activity activity) { LifecycleHandler lifecycleHandler = (LifecycleHandler)activity.getFragmentManager().findFragmentByTag(FRAGMENT_TAG); if (lifecycleHandler != null) { lifecycleHandler.registerActivityListener(activity); @@ -58,7 +60,8 @@ private static LifecycleHandler findInActivity(Activity activity) { return lifecycleHandler; } - public static LifecycleHandler install(Activity activity) { + @NonNull + public static LifecycleHandler install(@NonNull Activity activity) { LifecycleHandler lifecycleHandler = findInActivity(activity); if (lifecycleHandler == null) { lifecycleHandler = new LifecycleHandler(); @@ -68,7 +71,8 @@ public static LifecycleHandler install(Activity activity) { return lifecycleHandler; } - public Router getRouter(ViewGroup container, Bundle savedInstanceState) { + @NonNull + public Router getRouter(@NonNull ViewGroup container, @Nullable Bundle savedInstanceState) { ActivityHostedRouter router = routerMap.get(getRouterHashKey(container)); if (router == null) { router = new ActivityHostedRouter(); @@ -88,19 +92,21 @@ public Router getRouter(ViewGroup container, Bundle savedInstanceState) { return router; } + @NonNull public List getRouters() { return new ArrayList(routerMap.values()); } + @Nullable public Activity getLifecycleActivity() { return activity; } - private static int getRouterHashKey(ViewGroup viewGroup) { + private static int getRouterHashKey(@NonNull ViewGroup viewGroup) { return viewGroup.getId(); } - private void registerActivityListener(Activity activity) { + private void registerActivityListener(@NonNull Activity activity) { this.activity = activity; if (!hasRegisteredCallbacks) { @@ -255,11 +261,11 @@ public boolean onOptionsItemSelected(MenuItem item) { return super.onOptionsItemSelected(item); } - public void registerForActivityResult(String instanceId, int requestCode) { + public void registerForActivityResult(@NonNull String instanceId, int requestCode) { activityRequestMap.put(requestCode, instanceId); } - public void unregisterForActivityResults(String instanceId) { + public void unregisterForActivityResults(@NonNull String instanceId) { for (int i = activityRequestMap.size() - 1; i >= 0; i--) { if (instanceId.equals(activityRequestMap.get(activityRequestMap.keyAt(i)))) { activityRequestMap.removeAt(i); @@ -267,18 +273,18 @@ public void unregisterForActivityResults(String instanceId) { } } - public void startActivityForResult(String instanceId, Intent intent, int requestCode) { + public void startActivityForResult(@NonNull String instanceId, @NonNull Intent intent, int requestCode) { registerForActivityResult(instanceId, requestCode); startActivityForResult(intent, requestCode); } - public void startActivityForResult(String instanceId, Intent intent, int requestCode, Bundle options) { + public void startActivityForResult(@NonNull String instanceId, @NonNull Intent intent, int requestCode, @Nullable Bundle options) { registerForActivityResult(instanceId, requestCode); startActivityForResult(intent, requestCode, options); } @TargetApi(Build.VERSION_CODES.M) - public void requestPermissions(String instanceId, String[] permissions, int requestCode) { + public void requestPermissions(@NonNull String instanceId, @NonNull String[] permissions, int requestCode) { if (attached) { permissionRequestMap.put(requestCode, instanceId); requestPermissions(permissions, requestCode); @@ -349,7 +355,7 @@ private static class PendingPermissionRequest implements Parcelable { final String[] permissions; final int requestCode; - public PendingPermissionRequest(String instanceId, String[] permissions, int requestCode) { + PendingPermissionRequest(@NonNull String instanceId, @NonNull String[] permissions, int requestCode) { this.instanceId = instanceId; this.permissions = permissions; this.requestCode = requestCode; @@ -361,10 +367,12 @@ private PendingPermissionRequest(Parcel in) { requestCode = in.readInt(); } + @Override public int describeContents() { return 0; } + @Override public void writeToParcel(Parcel out, int flags) { out.writeString(instanceId); out.writeStringArray(permissions); @@ -372,10 +380,12 @@ public void writeToParcel(Parcel out, int flags) { } public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + @Override public PendingPermissionRequest createFromParcel(Parcel in) { return new PendingPermissionRequest(in); } + @Override public PendingPermissionRequest[] newArray(int size) { return new PendingPermissionRequest[size]; } diff --git a/conductor/src/main/java/com/bluelinelabs/conductor/internal/NoOpControllerChangeHandler.java b/conductor/src/main/java/com/bluelinelabs/conductor/internal/NoOpControllerChangeHandler.java index bc438b25..77d7fd44 100644 --- a/conductor/src/main/java/com/bluelinelabs/conductor/internal/NoOpControllerChangeHandler.java +++ b/conductor/src/main/java/com/bluelinelabs/conductor/internal/NoOpControllerChangeHandler.java @@ -1,6 +1,7 @@ package com.bluelinelabs.conductor.internal; import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.view.View; import android.view.ViewGroup; @@ -9,7 +10,7 @@ public class NoOpControllerChangeHandler extends ControllerChangeHandler { @Override - public void performChange(@NonNull ViewGroup container, @NonNull View from, @NonNull View to, boolean isPush, @NonNull ControllerChangeCompletedListener changeListener) { + public void performChange(@NonNull ViewGroup container, @Nullable View from, @Nullable View to, boolean isPush, @NonNull ControllerChangeCompletedListener changeListener) { changeListener.onChangeCompleted(); } diff --git a/conductor/src/main/java/com/bluelinelabs/conductor/internal/StringSparseArrayParceler.java b/conductor/src/main/java/com/bluelinelabs/conductor/internal/StringSparseArrayParceler.java index 6681df3e..9735f655 100644 --- a/conductor/src/main/java/com/bluelinelabs/conductor/internal/StringSparseArrayParceler.java +++ b/conductor/src/main/java/com/bluelinelabs/conductor/internal/StringSparseArrayParceler.java @@ -2,17 +2,18 @@ import android.os.Parcel; import android.os.Parcelable; +import android.support.annotation.NonNull; import android.util.SparseArray; public class StringSparseArrayParceler implements Parcelable { private final SparseArray stringSparseArray; - public StringSparseArrayParceler(SparseArray stringSparseArray) { + public StringSparseArrayParceler(@NonNull SparseArray stringSparseArray) { this.stringSparseArray = stringSparseArray; } - private StringSparseArrayParceler(Parcel in) { + private StringSparseArrayParceler(@NonNull Parcel in) { stringSparseArray = new SparseArray<>(); final int size = in.readInt(); @@ -22,14 +23,17 @@ private StringSparseArrayParceler(Parcel in) { } } + @NonNull public SparseArray getStringSparseArray() { return stringSparseArray; } + @Override public int describeContents() { return 0; } + @Override public void writeToParcel(Parcel out, int flags) { final int size = stringSparseArray.size(); @@ -44,10 +48,12 @@ public void writeToParcel(Parcel out, int flags) { } public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + @Override public StringSparseArrayParceler createFromParcel(Parcel in) { return new StringSparseArrayParceler(in); } + @Override public StringSparseArrayParceler[] newArray(int size) { return new StringSparseArrayParceler[size]; } diff --git a/conductor/src/test/java/com/bluelinelabs/conductor/TestController.java b/conductor/src/test/java/com/bluelinelabs/conductor/TestController.java index 87e26e23..a06e3d50 100644 --- a/conductor/src/test/java/com/bluelinelabs/conductor/TestController.java +++ b/conductor/src/test/java/com/bluelinelabs/conductor/TestController.java @@ -68,7 +68,7 @@ protected void onDetach(@NonNull View view) { } @Override - protected void onDestroyView(View view) { + protected void onDestroyView(@NonNull View view) { super.onDestroyView(view); currentCallState.destroyViewCalls++; } diff --git a/demo/src/main/java/com/bluelinelabs/conductor/demo/changehandler/CircularRevealChangeHandler.java b/demo/src/main/java/com/bluelinelabs/conductor/demo/changehandler/CircularRevealChangeHandler.java index 6c1151d2..78b48c57 100644 --- a/demo/src/main/java/com/bluelinelabs/conductor/demo/changehandler/CircularRevealChangeHandler.java +++ b/demo/src/main/java/com/bluelinelabs/conductor/demo/changehandler/CircularRevealChangeHandler.java @@ -120,7 +120,7 @@ public CircularRevealChangeHandler(int cx, int cy, long duration, boolean remove this.cy = cy; } - @Override + @Override @NonNull protected Animator getAnimator(@NonNull ViewGroup container, View from, View to, boolean isPush, boolean toAddedToContainer) { final float radius = (float) Math.hypot(cx, cy); Animator animator = null; diff --git a/demo/src/main/java/com/bluelinelabs/conductor/demo/changehandler/CircularRevealChangeHandlerCompat.java b/demo/src/main/java/com/bluelinelabs/conductor/demo/changehandler/CircularRevealChangeHandlerCompat.java index ab525bb2..ea9cd0f8 100644 --- a/demo/src/main/java/com/bluelinelabs/conductor/demo/changehandler/CircularRevealChangeHandlerCompat.java +++ b/demo/src/main/java/com/bluelinelabs/conductor/demo/changehandler/CircularRevealChangeHandlerCompat.java @@ -16,7 +16,7 @@ public CircularRevealChangeHandlerCompat(@NonNull View fromView, @NonNull View c super(fromView, containerView); } - @Override + @Override @NonNull protected Animator getAnimator(@NonNull ViewGroup container, View from, View to, boolean isPush, boolean toAddedToContainer) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { return super.getAnimator(container, from, to, isPush, toAddedToContainer); diff --git a/demo/src/main/java/com/bluelinelabs/conductor/demo/changehandler/FlipChangeHandler.java b/demo/src/main/java/com/bluelinelabs/conductor/demo/changehandler/FlipChangeHandler.java index a61d3e43..cc6e4524 100755 --- a/demo/src/main/java/com/bluelinelabs/conductor/demo/changehandler/FlipChangeHandler.java +++ b/demo/src/main/java/com/bluelinelabs/conductor/demo/changehandler/FlipChangeHandler.java @@ -52,7 +52,7 @@ public FlipChangeHandler(FlipDirection flipDirection, long animationDuration) { this.animationDuration = animationDuration; } - @Override + @Override @NonNull protected Animator getAnimator(@NonNull ViewGroup container, View from, View to, boolean isPush, boolean toAddedToContainer) { AnimatorSet animatorSet = new AnimatorSet(); diff --git a/demo/src/main/java/com/bluelinelabs/conductor/demo/changehandler/ScaleFadeChangeHandler.java b/demo/src/main/java/com/bluelinelabs/conductor/demo/changehandler/ScaleFadeChangeHandler.java index 60632577..193010da 100644 --- a/demo/src/main/java/com/bluelinelabs/conductor/demo/changehandler/ScaleFadeChangeHandler.java +++ b/demo/src/main/java/com/bluelinelabs/conductor/demo/changehandler/ScaleFadeChangeHandler.java @@ -15,7 +15,7 @@ public ScaleFadeChangeHandler() { super(DEFAULT_ANIMATION_DURATION, true); } - @Override + @Override @NonNull protected Animator getAnimator(@NonNull ViewGroup container, View from, View to, boolean isPush, boolean toAddedToContainer) { AnimatorSet animator = new AnimatorSet(); if (to != null && toAddedToContainer) { diff --git a/demo/src/main/java/com/bluelinelabs/conductor/demo/controllers/DragDismissController.java b/demo/src/main/java/com/bluelinelabs/conductor/demo/controllers/DragDismissController.java index 63dcf0ec..2fe84dac 100644 --- a/demo/src/main/java/com/bluelinelabs/conductor/demo/controllers/DragDismissController.java +++ b/demo/src/main/java/com/bluelinelabs/conductor/demo/controllers/DragDismissController.java @@ -46,7 +46,7 @@ protected void onViewBound(@NonNull View view) { } @Override - protected void onDestroyView(View view) { + protected void onDestroyView(@NonNull View view) { super.onDestroyView(view); ((ElasticDragDismissFrameLayout)view).removeListener(dragDismissListener); diff --git a/demo/src/main/java/com/bluelinelabs/conductor/demo/controllers/PagerController.java b/demo/src/main/java/com/bluelinelabs/conductor/demo/controllers/PagerController.java index fae5a1bb..fec0430a 100644 --- a/demo/src/main/java/com/bluelinelabs/conductor/demo/controllers/PagerController.java +++ b/demo/src/main/java/com/bluelinelabs/conductor/demo/controllers/PagerController.java @@ -12,6 +12,8 @@ import com.bluelinelabs.conductor.demo.controllers.base.BaseController; import com.bluelinelabs.conductor.support.ControllerPagerAdapter; +import java.util.Locale; + import butterknife.BindView; public class PagerController extends BaseController { @@ -27,7 +29,7 @@ public PagerController() { pagerAdapter = new ControllerPagerAdapter(this, false) { @Override public Controller getItem(int position) { - return new ChildController(String.format("Child #%d (Swipe to see more)", position), PAGE_COLORS[position], true); + return new ChildController(String.format(Locale.getDefault(), "Child #%d (Swipe to see more)", position), PAGE_COLORS[position], true); } @Override @@ -50,7 +52,7 @@ protected void onViewBound(@NonNull View view) { } @Override - protected void onDestroyView(View view) { + protected void onDestroyView(@NonNull View view) { viewPager.setAdapter(null); super.onDestroyView(view); } diff --git a/demo/src/main/java/com/bluelinelabs/conductor/demo/controllers/RxLifecycleController.java b/demo/src/main/java/com/bluelinelabs/conductor/demo/controllers/RxLifecycleController.java index 6329edf0..1dc132f3 100755 --- a/demo/src/main/java/com/bluelinelabs/conductor/demo/controllers/RxLifecycleController.java +++ b/demo/src/main/java/com/bluelinelabs/conductor/demo/controllers/RxLifecycleController.java @@ -92,7 +92,7 @@ public void call(Long num) { } @Override - protected void onDestroyView(View view) { + protected void onDestroyView(@NonNull View view) { super.onDestroyView(view); Log.i(TAG, "onDestroyView() called"); diff --git a/demo/src/main/java/com/bluelinelabs/conductor/demo/controllers/base/ButterKnifeController.java b/demo/src/main/java/com/bluelinelabs/conductor/demo/controllers/base/ButterKnifeController.java index b56e873b..610af12a 100644 --- a/demo/src/main/java/com/bluelinelabs/conductor/demo/controllers/base/ButterKnifeController.java +++ b/demo/src/main/java/com/bluelinelabs/conductor/demo/controllers/base/ButterKnifeController.java @@ -34,7 +34,7 @@ protected View onCreateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup protected void onViewBound(@NonNull View view) { } @Override - protected void onDestroyView(View view) { + protected void onDestroyView(@NonNull View view) { super.onDestroyView(view); unbinder.unbind(); unbinder = null; diff --git a/demo/src/main/res/values/material_colors.xml b/demo/src/main/res/values/material_colors.xml index 10af6fc3..20d778d6 100644 --- a/demo/src/main/res/values/material_colors.xml +++ b/demo/src/main/res/values/material_colors.xml @@ -1,23 +1,23 @@ - @color/red_300 - @color/light_green_300 - @color/teal_300 - @color/pink_300 - @color/brown_300 - @color/purple_300 - @color/lime_300 - @color/blue_grey_300 - @color/deep_purple_300 - @color/green_300 - @color/indigo_300 - @color/yellow_300 - @color/blue_300 - @color/orange_300 - @color/light_blue_300 - @color/deep_orange_300 - @color/cyan_300 - @color/grey_300 + @color/red_300 + @color/light_green_300 + @color/teal_300 + @color/pink_300 + @color/brown_300 + @color/purple_300 + @color/lime_300 + @color/blue_grey_300 + @color/deep_purple_300 + @color/green_300 + @color/indigo_300 + @color/yellow_300 + @color/blue_300 + @color/orange_300 + @color/light_blue_300 + @color/deep_orange_300 + @color/cyan_300 + @color/grey_300 \ No newline at end of file From 07a579b9390c34f422e687e5804f580995ac85c7 Mon Sep 17 00:00:00 2001 From: Eric Kuck Date: Wed, 9 Nov 2016 11:07:41 -0600 Subject: [PATCH 04/75] Fixed incorrect child router backstack handling if controllers were VERY rapidly added --- .../bluelinelabs/conductor/Controller.java | 13 ++++------ .../conductor/ControllerChangeHandler.java | 4 +++ .../changehandler/AnimatorChangeHandler.java | 2 +- .../controllers/NavigationDemoController.java | 25 +++++++++++++++++++ 4 files changed, 35 insertions(+), 9 deletions(-) diff --git a/conductor/src/main/java/com/bluelinelabs/conductor/Controller.java b/conductor/src/main/java/com/bluelinelabs/conductor/Controller.java index 4ca9dafe..b519a3de 100755 --- a/conductor/src/main/java/com/bluelinelabs/conductor/Controller.java +++ b/conductor/src/main/java/com/bluelinelabs/conductor/Controller.java @@ -27,11 +27,9 @@ import java.lang.ref.WeakReference; import java.lang.reflect.Constructor; -import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; -import java.util.Deque; -import java.util.Iterator; +import java.util.LinkedList; import java.util.List; import java.util.UUID; @@ -86,7 +84,7 @@ public abstract class Controller { private final List lifecycleListeners = new ArrayList<>(); private final ArrayList requestedPermissions = new ArrayList<>(); private final ArrayList onRouterSetListeners = new ArrayList<>(); - private final Deque childBackstack = new ArrayDeque<>(); + private final List childBackstack = new LinkedList<>(); private WeakReference destroyedView; private final ControllerChangeListener childRouterChangeListener = new ControllerChangeListener() { @@ -526,9 +524,8 @@ public void onRequestPermissionsResult(int requestCode, @NonNull String[] permis * @return True if this Controller has consumed the back button press, otherwise false */ public boolean handleBack() { - Iterator childIterator = childBackstack.descendingIterator(); - while (childIterator.hasNext()) { - Controller childController = childIterator.next(); + for (int i = childBackstack.size() - 1; i >= 0; i--) { + Controller childController = childBackstack.get(i); if (childController.isAttached() && childController.getRouter().handleBack()) { return true; } @@ -931,7 +928,7 @@ private void destroy(boolean removeViews) { if (!attached) { removeViewReference(); } else if (removeViews) { - detach(view, false); + detach(view, true); } } diff --git a/conductor/src/main/java/com/bluelinelabs/conductor/ControllerChangeHandler.java b/conductor/src/main/java/com/bluelinelabs/conductor/ControllerChangeHandler.java index d4277f63..f2196ac7 100644 --- a/conductor/src/main/java/com/bluelinelabs/conductor/ControllerChangeHandler.java +++ b/conductor/src/main/java/com/bluelinelabs/conductor/ControllerChangeHandler.java @@ -139,6 +139,10 @@ public static void executeChange(@Nullable final Controller to, @Nullable final if (isPush && to != null) { inProgressPushHandlers.put(to.getInstanceId(), handler); + + if (from != null) { + completePushImmediately(from.getInstanceId()); + } } else if (!isPush && from != null) { abortPush(from, to, handler); } diff --git a/conductor/src/main/java/com/bluelinelabs/conductor/changehandler/AnimatorChangeHandler.java b/conductor/src/main/java/com/bluelinelabs/conductor/changehandler/AnimatorChangeHandler.java index c86be644..e046fe7c 100755 --- a/conductor/src/main/java/com/bluelinelabs/conductor/changehandler/AnimatorChangeHandler.java +++ b/conductor/src/main/java/com/bluelinelabs/conductor/changehandler/AnimatorChangeHandler.java @@ -77,7 +77,7 @@ public void completeImmediately() { needsImmediateCompletion = true; if (animator != null) { - animator.cancel(); + animator.end(); } } diff --git a/demo/src/main/java/com/bluelinelabs/conductor/demo/controllers/NavigationDemoController.java b/demo/src/main/java/com/bluelinelabs/conductor/demo/controllers/NavigationDemoController.java index 61351529..e03a4dea 100644 --- a/demo/src/main/java/com/bluelinelabs/conductor/demo/controllers/NavigationDemoController.java +++ b/demo/src/main/java/com/bluelinelabs/conductor/demo/controllers/NavigationDemoController.java @@ -7,6 +7,8 @@ import android.view.ViewGroup; import android.widget.TextView; +import com.bluelinelabs.conductor.ControllerChangeHandler; +import com.bluelinelabs.conductor.ControllerChangeType; import com.bluelinelabs.conductor.RouterTransaction; import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler; import com.bluelinelabs.conductor.demo.R; @@ -60,11 +62,34 @@ protected void onViewBound(@NonNull View view) { tvTitle.setText(getResources().getString(R.string.navigation_title, index)); } + @Override + protected void onChangeEnded(@NonNull ControllerChangeHandler changeHandler, @NonNull ControllerChangeType changeType) { + super.onChangeEnded(changeHandler, changeType); + + setButtonsEnabled(true); + } + + @Override + protected void onChangeStarted(@NonNull ControllerChangeHandler changeHandler, @NonNull ControllerChangeType changeType) { + super.onChangeStarted(changeHandler, changeType); + + setButtonsEnabled(false); + } + @Override protected String getTitle() { return "Navigation Demos"; } + private void setButtonsEnabled(boolean enabled) { + final View view = getView(); + if (view != null) { + view.findViewById(R.id.btn_next).setEnabled(enabled); + view.findViewById(R.id.btn_up).setEnabled(enabled); + view.findViewById(R.id.btn_pop_to_root).setEnabled(enabled); + } + } + @OnClick(R.id.btn_next) void onNextClicked() { getRouter().pushController(RouterTransaction.with(new NavigationDemoController(index + 1, displayUp)) .pushChangeHandler(new HorizontalChangeHandler()) From 9948cb46528fa10b7581d81272d438dc65b4b7f2 Mon Sep 17 00:00:00 2001 From: TMTron Date: Wed, 9 Nov 2016 21:54:27 +0100 Subject: [PATCH 05/75] Navigation Demos: "GO UP" is hidden in "Controller #0" - closes #158 (#159) * Navigation Demos: "GO UP" is hidden in "Controller #0" - this closes #158 * Multiple Child Routers: "GO UP" is hidden for all - #158 --- .../demo/controllers/HomeController.java | 4 ++- .../MultipleChildRouterController.java | 4 ++- .../controllers/NavigationDemoController.java | 30 ++++++++++++++----- 3 files changed, 29 insertions(+), 9 deletions(-) diff --git a/demo/src/main/java/com/bluelinelabs/conductor/demo/controllers/HomeController.java b/demo/src/main/java/com/bluelinelabs/conductor/demo/controllers/HomeController.java index 5b9f3b1f..ab7a3d0a 100644 --- a/demo/src/main/java/com/bluelinelabs/conductor/demo/controllers/HomeController.java +++ b/demo/src/main/java/com/bluelinelabs/conductor/demo/controllers/HomeController.java @@ -33,6 +33,8 @@ import butterknife.ButterKnife; import butterknife.OnClick; +import static com.bluelinelabs.conductor.demo.controllers.NavigationDemoController.DisplayUpMode.SHOW_FOR_CHILDREN_ONLY; + public class HomeController extends BaseController { public enum HomeDemoModel { @@ -133,7 +135,7 @@ protected String getTitle() { void onModelRowClick(HomeDemoModel model) { switch (model) { case NAVIGATION: - getRouter().pushController(RouterTransaction.with(new NavigationDemoController(0, true)) + getRouter().pushController(RouterTransaction.with(new NavigationDemoController(0, SHOW_FOR_CHILDREN_ONLY)) .pushChangeHandler(new FadeChangeHandler()) .popChangeHandler(new FadeChangeHandler()) .tag(NavigationDemoController.TAG_UP_TRANSACTION) diff --git a/demo/src/main/java/com/bluelinelabs/conductor/demo/controllers/MultipleChildRouterController.java b/demo/src/main/java/com/bluelinelabs/conductor/demo/controllers/MultipleChildRouterController.java index b504b5c2..4073246a 100644 --- a/demo/src/main/java/com/bluelinelabs/conductor/demo/controllers/MultipleChildRouterController.java +++ b/demo/src/main/java/com/bluelinelabs/conductor/demo/controllers/MultipleChildRouterController.java @@ -12,6 +12,8 @@ import butterknife.BindViews; +import static com.bluelinelabs.conductor.demo.controllers.NavigationDemoController.DisplayUpMode.HIDE; + public class MultipleChildRouterController extends BaseController { @BindViews({R.id.container_0, R.id.container_1, R.id.container_2}) ViewGroup[] childContainers; @@ -28,7 +30,7 @@ protected void onViewBound(@NonNull View view) { for (ViewGroup childContainer : childContainers) { Router childRouter = getChildRouter(childContainer, null).setPopsLastView(false); if (!childRouter.hasRootController()) { - childRouter.setRoot(RouterTransaction.with(new NavigationDemoController(0, false))); + childRouter.setRoot(RouterTransaction.with(new NavigationDemoController(0, HIDE))); } } } diff --git a/demo/src/main/java/com/bluelinelabs/conductor/demo/controllers/NavigationDemoController.java b/demo/src/main/java/com/bluelinelabs/conductor/demo/controllers/NavigationDemoController.java index e03a4dea..14d95813 100644 --- a/demo/src/main/java/com/bluelinelabs/conductor/demo/controllers/NavigationDemoController.java +++ b/demo/src/main/java/com/bluelinelabs/conductor/demo/controllers/NavigationDemoController.java @@ -19,29 +19,45 @@ import butterknife.BindView; import butterknife.OnClick; +import static com.bluelinelabs.conductor.demo.controllers.NavigationDemoController.DisplayUpMode.SHOW; + public class NavigationDemoController extends BaseController { + public enum DisplayUpMode { + SHOW, + SHOW_FOR_CHILDREN_ONLY, + HIDE; + + private DisplayUpMode getDisplayUpModeForChild() { + switch (this) { + case HIDE: return HIDE; + default: return SHOW; + } + } + } + public static final String TAG_UP_TRANSACTION = "NavigationDemoController.up"; private static final String KEY_INDEX = "NavigationDemoController.index"; - private static final String KEY_DISPLAY_UP = "NavigationDemoController.displayUp"; + private static final String KEY_DISPLAY_UP_MODE = "NavigationDemoController.displayUpMode"; @BindView(R.id.tv_title) TextView tvTitle; private int index; - private boolean displayUp; + private DisplayUpMode displayUpMode; - public NavigationDemoController(int index, boolean displayUpButton) { + public NavigationDemoController(int index, DisplayUpMode displayUpMode) { this(new BundleBuilder(new Bundle()) .putInt(KEY_INDEX, index) - .putBoolean(KEY_DISPLAY_UP, displayUpButton) + .putInt(KEY_DISPLAY_UP_MODE, displayUpMode.ordinal()) .build()); } public NavigationDemoController(Bundle args) { super(args); index = args.getInt(KEY_INDEX); - displayUp = args.getBoolean(KEY_DISPLAY_UP); + int displayUpModeOridnal = args.getInt(KEY_DISPLAY_UP_MODE); + displayUpMode = DisplayUpMode.values()[displayUpModeOridnal]; } @NonNull @@ -54,7 +70,7 @@ protected View inflateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup protected void onViewBound(@NonNull View view) { super.onViewBound(view); - if (!displayUp) { + if (displayUpMode != SHOW) { view.findViewById(R.id.btn_up).setVisibility(View.GONE); } @@ -91,7 +107,7 @@ private void setButtonsEnabled(boolean enabled) { } @OnClick(R.id.btn_next) void onNextClicked() { - getRouter().pushController(RouterTransaction.with(new NavigationDemoController(index + 1, displayUp)) + getRouter().pushController(RouterTransaction.with(new NavigationDemoController(index + 1, displayUpMode.getDisplayUpModeForChild())) .pushChangeHandler(new HorizontalChangeHandler()) .popChangeHandler(new HorizontalChangeHandler())); } From 803c20e0931b118d315076e36d09c2adaf4994ae Mon Sep 17 00:00:00 2001 From: Eric Kuck Date: Wed, 9 Nov 2016 15:25:28 -0600 Subject: [PATCH 06/75] Added UiThread annotations - closes #145 --- .../java/com/bluelinelabs/conductor/Conductor.java | 3 ++- .../java/com/bluelinelabs/conductor/Router.java | 13 +++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/conductor/src/main/java/com/bluelinelabs/conductor/Conductor.java b/conductor/src/main/java/com/bluelinelabs/conductor/Conductor.java index 63ff6850..d9226b1e 100644 --- a/conductor/src/main/java/com/bluelinelabs/conductor/Conductor.java +++ b/conductor/src/main/java/com/bluelinelabs/conductor/Conductor.java @@ -4,6 +4,7 @@ import android.os.Bundle; import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import android.support.annotation.UiThread; import android.view.ViewGroup; import com.bluelinelabs.conductor.internal.LifecycleHandler; @@ -27,7 +28,7 @@ private Conductor() {} * for restoring the Router's state if possible. * @return A fully configured {@link Router} instance for use with this Activity/ViewGroup pair. */ - @NonNull + @NonNull @UiThread public static Router attachRouter(@NonNull Activity activity, @NonNull ViewGroup container, @Nullable Bundle savedInstanceState) { LifecycleHandler lifecycleHandler = LifecycleHandler.install(activity); diff --git a/conductor/src/main/java/com/bluelinelabs/conductor/Router.java b/conductor/src/main/java/com/bluelinelabs/conductor/Router.java index 9c6f9002..ed3cf197 100755 --- a/conductor/src/main/java/com/bluelinelabs/conductor/Router.java +++ b/conductor/src/main/java/com/bluelinelabs/conductor/Router.java @@ -5,6 +5,7 @@ import android.os.Bundle; import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import android.support.annotation.UiThread; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; @@ -77,6 +78,7 @@ public void onRequestPermissionsResult(@NonNull String instanceId, int requestCo * * @return Whether or not a back action was handled by the Router */ + @UiThread public boolean handleBack() { if (!backstack.isEmpty()) { //noinspection ConstantConditions @@ -95,6 +97,7 @@ public boolean handleBack() { * * @return Whether or not this Router still has controllers remaining on it after popping. */ + @UiThread public boolean popCurrentController() { RouterTransaction transaction = backstack.peek(); if (transaction == null) { @@ -109,6 +112,7 @@ public boolean popCurrentController() { * @param controller The controller that should be popped from this Router * @return Whether or not this Router still has controllers remaining on it after popping. */ + @UiThread public boolean popController(@NonNull Controller controller) { RouterTransaction topController = backstack.peek(); boolean poppingTopController = topController != null && topController.controller == controller; @@ -141,6 +145,7 @@ public boolean popController(@NonNull Controller controller) { * @param transaction The transaction detailing what should be pushed, including the {@link Controller}, * and its push and pop {@link ControllerChangeHandler}, and its tag. */ + @UiThread public void pushController(@NonNull RouterTransaction transaction) { RouterTransaction from = backstack.peek(); pushToBackstack(transaction); @@ -153,6 +158,7 @@ public void pushController(@NonNull RouterTransaction transaction) { * @param transaction The transaction detailing what should be pushed, including the {@link Controller}, * and its push and pop {@link ControllerChangeHandler}, and its tag. */ + @UiThread public void replaceTopController(@NonNull RouterTransaction transaction) { RouterTransaction topTransaction = backstack.peek(); if (!backstack.isEmpty()) { @@ -209,6 +215,7 @@ public Router setPopsLastView(boolean popsLastView) { * * @return Whether or not any {@link Controller}s were popped in order to get to the root transaction */ + @UiThread public boolean popToRoot() { return popToRoot(null); } @@ -219,6 +226,7 @@ public boolean popToRoot() { * @param changeHandler The {@link ControllerChangeHandler} to handle this transaction * @return Whether or not any {@link Controller}s were popped in order to get to the root transaction */ + @UiThread public boolean popToRoot(@Nullable ControllerChangeHandler changeHandler) { if (backstack.size() > 1) { //noinspection ConstantConditions @@ -235,6 +243,7 @@ public boolean popToRoot(@Nullable ControllerChangeHandler changeHandler) { * @param tag The tag being popped to * @return Whether or not any {@link Controller}s were popped in order to get to the transaction with the passed tag */ + @UiThread public boolean popToTag(@NonNull String tag) { return popToTag(tag, null); } @@ -246,6 +255,7 @@ public boolean popToTag(@NonNull String tag) { * @param changeHandler The {@link ControllerChangeHandler} to handle this transaction * @return Whether or not the {@link Controller} with the passed tag is now at the top */ + @UiThread public boolean popToTag(@NonNull String tag, @Nullable ControllerChangeHandler changeHandler) { for (RouterTransaction transaction : backstack) { if (tag.equals(transaction.tag())) { @@ -262,6 +272,7 @@ public boolean popToTag(@NonNull String tag, @Nullable ControllerChangeHandler c * @param transaction The transaction detailing what should be pushed, including the {@link Controller}, * and its push and pop {@link ControllerChangeHandler}, and its tag. */ + @UiThread public void setRoot(@NonNull RouterTransaction transaction) { ControllerChangeHandler newHandler = transaction.pushChangeHandler() != null ? transaction.pushChangeHandler() : new SimpleSwapChangeHandler(); @@ -348,6 +359,7 @@ public List getBackstack() { * @param newBackstack The new backstack * @param changeHandler An optional change handler to be used to handle the root view of transition */ + @UiThread public void setBackstack(@NonNull List newBackstack, @Nullable ControllerChangeHandler changeHandler) { List oldVisibleTransactions = getVisibleTransactions(backstack.iterator()); @@ -426,6 +438,7 @@ public void removeChangeListener(@NonNull ControllerChangeListener changeListene /** * Attaches this Router's existing backstack to its container if one exists. */ + @UiThread public void rebindIfNeeded() { Iterator backstackIterator = backstack.reverseIterator(); while (backstackIterator.hasNext()) { From 11185458b3af7adf2a32d68d4ce54027a452b5dc Mon Sep 17 00:00:00 2001 From: Eric Kuck Date: Wed, 9 Nov 2016 16:03:20 -0600 Subject: [PATCH 07/75] Fixed nullable annotations for menu callbacks --- .../main/java/com/bluelinelabs/conductor/Controller.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/conductor/src/main/java/com/bluelinelabs/conductor/Controller.java b/conductor/src/main/java/com/bluelinelabs/conductor/Controller.java index b519a3de..5db8bea1 100755 --- a/conductor/src/main/java/com/bluelinelabs/conductor/Controller.java +++ b/conductor/src/main/java/com/bluelinelabs/conductor/Controller.java @@ -647,7 +647,7 @@ public final void setOptionsMenuHidden(boolean optionsMenuHidden) { * @param menu The menu into which your options should be placed. * @param inflater The inflater that can be used to inflate your menu items. */ - public void onCreateOptionsMenu(@Nullable Menu menu, @Nullable MenuInflater inflater) { } + public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) { } /** * Prepare the screen's options menu to be displayed. This is called directly before showing the @@ -655,7 +655,7 @@ public void onCreateOptionsMenu(@Nullable Menu menu, @Nullable MenuInflater infl * * @param menu The menu that will be displayed */ - public void onPrepareOptionsMenu(@Nullable Menu menu) { } + public void onPrepareOptionsMenu(@NonNull Menu menu) { } /** * Called when an option menu item has been selected by the user. @@ -663,7 +663,7 @@ public void onPrepareOptionsMenu(@Nullable Menu menu) { } * @param item The selected item. * @return True if this event has been consumed, false if it has not. */ - public boolean onOptionsItemSelected(@Nullable MenuItem item) { + public boolean onOptionsItemSelected(@NonNull MenuItem item) { return false; } From db359d906b6520efc660e7123a855065a3bb4afe Mon Sep 17 00:00:00 2001 From: Eric Kuck Date: Thu, 10 Nov 2016 13:34:13 -0600 Subject: [PATCH 08/75] Un-deprecated getChildRouter with a tag. Fixes #160. --- .../support/ControllerPagerAdapter.java | 2 +- .../com/bluelinelabs/conductor/Controller.java | 17 ++++++++++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/conductor-support/src/main/java/com/bluelinelabs/conductor/support/ControllerPagerAdapter.java b/conductor-support/src/main/java/com/bluelinelabs/conductor/support/ControllerPagerAdapter.java index e3be113a..3af9a3c6 100644 --- a/conductor-support/src/main/java/com/bluelinelabs/conductor/support/ControllerPagerAdapter.java +++ b/conductor-support/src/main/java/com/bluelinelabs/conductor/support/ControllerPagerAdapter.java @@ -40,7 +40,7 @@ public ControllerPagerAdapter(Controller host, boolean saveControllerState) { public Object instantiateItem(ViewGroup container, int position) { final String name = makeControllerName(container.getId(), getItemId(position)); - Router router = host.getChildRouter(container); + Router router = host.getChildRouter(container, name); if (savesState && !router.hasRootController()) { Bundle routerSavedState = savedPages.get(position); diff --git a/conductor/src/main/java/com/bluelinelabs/conductor/Controller.java b/conductor/src/main/java/com/bluelinelabs/conductor/Controller.java index 5db8bea1..dab8c3fe 100755 --- a/conductor/src/main/java/com/bluelinelabs/conductor/Controller.java +++ b/conductor/src/main/java/com/bluelinelabs/conductor/Controller.java @@ -168,13 +168,28 @@ public Bundle getArgs() { return args; } + /** + * Retrieves the child {@link Router} for the given container. If no child router for this container + * exists yet, it will be created. + * + * @param container The ViewGroup that hosts the child Router + */ @NonNull public final Router getChildRouter(@NonNull ViewGroup container) { //noinspection deprecation return getChildRouter(container, null); } - @Deprecated @NonNull + /** + * Retrieves the child {@link Router} for the given container/tag combination. If no child router for + * this container exists yet, it will be created. Note that multiple routers should not exist + * in the same container unless a lot of care is taken to maintain order between them. Avoid using + * the same container unless you have a great reason to do so (ex: ViewPagers). + * + * @param container The ViewGroup that hosts the child Router + * @param tag The router's tag + */ + @NonNull public final Router getChildRouter(@NonNull ViewGroup container, @Nullable String tag) { @IdRes final int containerId = container.getId(); From 96e068d34895690427ad3e904ab1fc72bfd0d777 Mon Sep 17 00:00:00 2001 From: Eric Kuck Date: Fri, 11 Nov 2016 10:44:37 -0600 Subject: [PATCH 09/75] Fixes a controller's internal backstack when `setBackstack` is used on a child router --- .../bluelinelabs/conductor/Controller.java | 15 +++++---------- .../com/bluelinelabs/conductor/Router.java | 19 +++++++++++++++++++ .../demo/controllers/HomeController.java | 4 ++-- 3 files changed, 26 insertions(+), 12 deletions(-) diff --git a/conductor/src/main/java/com/bluelinelabs/conductor/Controller.java b/conductor/src/main/java/com/bluelinelabs/conductor/Controller.java index dab8c3fe..27abb791 100755 --- a/conductor/src/main/java/com/bluelinelabs/conductor/Controller.java +++ b/conductor/src/main/java/com/bluelinelabs/conductor/Controller.java @@ -21,7 +21,7 @@ import android.view.View.OnAttachStateChangeListener; import android.view.ViewGroup; -import com.bluelinelabs.conductor.ControllerChangeHandler.ControllerChangeListener; +import com.bluelinelabs.conductor.Router.OnControllerPushedListener; import com.bluelinelabs.conductor.internal.ClassUtils; import com.bluelinelabs.conductor.internal.RouterRequiringFunc; @@ -87,16 +87,11 @@ public abstract class Controller { private final List childBackstack = new LinkedList<>(); private WeakReference destroyedView; - private final ControllerChangeListener childRouterChangeListener = new ControllerChangeListener() { + private final OnControllerPushedListener onControllerPushedListener = new OnControllerPushedListener() { @Override - public void onChangeStarted(@Nullable Controller to, @Nullable Controller from, boolean isPush, @NonNull ViewGroup container, @NonNull ControllerChangeHandler handler) { - if (isPush) { - onChildControllerPushed(to); - } + public void onControllerPushed(Controller controller) { + onChildControllerPushed(controller); } - - @Override - public void onChangeCompleted(@Nullable Controller to, @Nullable Controller from, boolean isPush, @NonNull ViewGroup container, @NonNull ControllerChangeHandler handler) { } }; @NonNull @@ -1137,7 +1132,7 @@ final boolean optionsItemSelected(@NonNull MenuItem item) { } private void monitorChildRouter(@NonNull ControllerHostedRouter childRouter) { - childRouter.addChangeListener(childRouterChangeListener); + childRouter.setOnControllerPushedListener(onControllerPushedListener); } private void onChildControllerPushed(@NonNull Controller controller) { diff --git a/conductor/src/main/java/com/bluelinelabs/conductor/Router.java b/conductor/src/main/java/com/bluelinelabs/conductor/Router.java index ed3cf197..c2de6ee7 100755 --- a/conductor/src/main/java/com/bluelinelabs/conductor/Router.java +++ b/conductor/src/main/java/com/bluelinelabs/conductor/Router.java @@ -33,6 +33,7 @@ public abstract class Router { private static final String KEY_POPS_LAST_VIEW = "Router.popsLastView"; protected final Backstack backstack = new Backstack(); + private OnControllerPushedListener onControllerPushedListener; private final List changeListeners = new ArrayList<>(); final List destroyingControllers = new ArrayList<>(); @@ -406,6 +407,12 @@ public void setBackstack(@NonNull List newBackstack, @Nullabl } backstack.setBackstack(newBackstack); + + if (onControllerPushedListener != null) { + for (RouterTransaction transaction : newBackstack) { + onControllerPushedListener.onControllerPushed(transaction.controller); + } + } } /** @@ -600,6 +607,10 @@ private void popToTransaction(@NonNull RouterTransaction transaction, @Nullable } } + final void setOnControllerPushedListener(OnControllerPushedListener listener) { + onControllerPushedListener = listener; + } + void prepareForContainerRemoval() { if (container != null) { container.setOnHierarchyChangeListener(null); @@ -663,6 +674,10 @@ private void performControllerChange(@Nullable final Controller to, @Nullable fi private void pushToBackstack(@NonNull RouterTransaction entry) { backstack.push(entry); + + if (onControllerPushedListener != null) { + onControllerPushedListener.onControllerPushed(entry.controller); + } } private void trackDestroyingController(@NonNull RouterTransaction transaction) { @@ -749,4 +764,8 @@ void setControllerRouter(@NonNull Controller controller) { abstract boolean hasHost(); @NonNull abstract List getSiblingRouters(); @NonNull abstract Router getRootRouter(); + + interface OnControllerPushedListener { + void onControllerPushed(Controller controller); + } } diff --git a/demo/src/main/java/com/bluelinelabs/conductor/demo/controllers/HomeController.java b/demo/src/main/java/com/bluelinelabs/conductor/demo/controllers/HomeController.java index 46cb0b08..14316b86 100644 --- a/demo/src/main/java/com/bluelinelabs/conductor/demo/controllers/HomeController.java +++ b/demo/src/main/java/com/bluelinelabs/conductor/demo/controllers/HomeController.java @@ -80,7 +80,7 @@ protected void onViewBound(@NonNull View view) { } @Override - public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) { super.onCreateOptionsMenu(menu, inflater); inflater.inflate(R.menu.home, menu); } @@ -95,7 +95,7 @@ protected void onChangeStarted(@NonNull ControllerChangeHandler changeHandler, @ } @Override - public boolean onOptionsItemSelected(MenuItem item) { + public boolean onOptionsItemSelected(@NonNull MenuItem item) { if (item.getItemId() == R.id.about) { SpannableString details = new SpannableString("A small, yet full-featured framework that allows building View-based Android applications"); details.setSpan(new AbsoluteSizeSpan(16, true), 0, details.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); From 638b2ad311b136d09e50c7616df6966d36b53541 Mon Sep 17 00:00:00 2001 From: Eric Kuck Date: Fri, 11 Nov 2016 11:10:35 -0600 Subject: [PATCH 10/75] Version bump --- README.md | 12 ++++++------ gradle.properties | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 7bf1d020..2047dd86 100644 --- a/README.md +++ b/README.md @@ -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.3' +compile 'com.bluelinelabs:conductor:2.0.4' // If you want the components that go along with // Android's support libraries (currently just a PagerAdapter): -compile 'com.bluelinelabs:conductor-support:2.0.3' +compile 'com.bluelinelabs:conductor-support:2.0.4' // If you want RxJava/RxAndroid lifecycle support: -compile 'com.bluelinelabs:conductor-rxlifecycle:2.0.3' +compile 'com.bluelinelabs:conductor-rxlifecycle:2.0.4' ``` SNAPSHOT: ```gradle -compile 'com.bluelinelabs:conductor:2.0.4-SNAPSHOT' -compile 'com.bluelinelabs:conductor-support:2.0.4-SNAPSHOT' -compile 'com.bluelinelabs:conductor-rxlifecycle:2.0.4-SNAPSHOT' +compile 'com.bluelinelabs:conductor:2.0.5-SNAPSHOT' +compile 'com.bluelinelabs:conductor-support:2.0.5-SNAPSHOT' +compile 'com.bluelinelabs:conductor-rxlifecycle:2.0.5-SNAPSHOT' ``` You also have to add the url to the snapshot repository: diff --git a/gradle.properties b/gradle.properties index e05304fd..8de7c1a8 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ -VERSION_NAME=2.0.4-SNAPSHOT +VERSION_NAME=2.0.5-SNAPSHOT VERSION_CODE=2 GROUP=com.bluelinelabs From 1ba0bf56f45641cc5de2d6fc0f749c963d8f00bd Mon Sep 17 00:00:00 2001 From: TMTron Date: Sat, 12 Nov 2016 11:39:48 +0100 Subject: [PATCH 11/75] the deep link navigation will now also work, when the activity is already open --- .../conductor/demo/MainActivity.java | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/demo/src/main/java/com/bluelinelabs/conductor/demo/MainActivity.java b/demo/src/main/java/com/bluelinelabs/conductor/demo/MainActivity.java index 7febe218..2d8349c5 100755 --- a/demo/src/main/java/com/bluelinelabs/conductor/demo/MainActivity.java +++ b/demo/src/main/java/com/bluelinelabs/conductor/demo/MainActivity.java @@ -36,13 +36,24 @@ protected void onCreate(Bundle savedInstanceState) { router = Conductor.attachRouter(this, container, savedInstanceState); if (!router.hasRootController()) { - HomeController homeController = new HomeController(); router.setRoot(RouterTransaction.with(homeController)); - handleIntentDataUri(homeController); + handleIntentDataUri(homeController, getIntent()); } } + private HomeController getHomeController() { + RouterTransaction rootRouterTransaction = router.getBackstack().get(0); + return (HomeController) rootRouterTransaction.controller(); + } + + @Override + protected void onNewIntent(Intent intent) { + super.onNewIntent(intent); + HomeController homeController = getHomeController(); + handleIntentDataUri(homeController, intent); + } + /** * will handle the data URI of the intent and when it is valid, navigate to the matching * controller @@ -58,8 +69,7 @@ protected void onCreate(Bundle savedInstanceState) { * * @param homeController the {@link HomeController} will handle the actual navigation */ - private void handleIntentDataUri(HomeController homeController) { - final Intent intent = getIntent(); + private void handleIntentDataUri(HomeController homeController, Intent intent) { Uri intentDataUri = intent.getData(); if (intentDataUri == null || intentDataUri.getPath() == null) return; From ae42ee1674a9c6ff6c2b287d57f85d2959a69cd5 Mon Sep 17 00:00:00 2001 From: Eric Kuck Date: Thu, 1 Dec 2016 17:28:15 -0600 Subject: [PATCH 12/75] Attempted fix for #165 --- .../bluelinelabs/conductor/Controller.java | 28 +++++++++++-------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/conductor/src/main/java/com/bluelinelabs/conductor/Controller.java b/conductor/src/main/java/com/bluelinelabs/conductor/Controller.java index 27abb791..16e35a6f 100755 --- a/conductor/src/main/java/com/bluelinelabs/conductor/Controller.java +++ b/conductor/src/main/java/com/bluelinelabs/conductor/Controller.java @@ -895,11 +895,27 @@ public void onViewDetachedFromWindow(View v) { }; view.addOnAttachStateChangeListener(onAttachStateChangeListener); + } else if (retainViewMode == RetainViewMode.RETAIN_DETACH) { + restoreChildControllerHosts(); } return view; } + private void restoreChildControllerHosts() { + for (ControllerHostedRouter childRouter : childRouters) { + if (!childRouter.hasHost()) { + View containerView = view.findViewById(childRouter.getHostId()); + + if (containerView != null && containerView instanceof ViewGroup) { + childRouter.setHost(this, (ViewGroup)containerView); + monitorChildRouter(childRouter); + childRouter.rebindIfNeeded(); + } + } + } + } + private void performDestroy() { if (!destroyed) { List listeners = new ArrayList<>(lifecycleListeners); @@ -966,17 +982,7 @@ private void restoreViewState(@NonNull View view) { view.restoreHierarchyState(viewState.getSparseParcelableArray(KEY_VIEW_STATE_HIERARCHY)); onRestoreViewState(view, viewState.getBundle(KEY_VIEW_STATE_BUNDLE)); - for (ControllerHostedRouter childRouter : childRouters) { - if (!childRouter.hasHost()) { - View containerView = view.findViewById(childRouter.getHostId()); - - if (containerView != null && containerView instanceof ViewGroup) { - childRouter.setHost(this, (ViewGroup)containerView); - monitorChildRouter(childRouter); - childRouter.rebindIfNeeded(); - } - } - } + restoreChildControllerHosts(); List listeners = new ArrayList<>(lifecycleListeners); for (LifecycleListener lifecycleListener : listeners) { From 285eb59da0e6e6a6b9efd3f02d745676eae85c7e Mon Sep 17 00:00:00 2001 From: Vishnu Rajeevan Date: Thu, 1 Dec 2016 18:31:52 -0500 Subject: [PATCH 13/75] add rxlifecycle2 support, fixes #148 (#171) --- conductor-rxlifecycle2/build.gradle | 35 +++++++++++++ conductor-rxlifecycle2/gradle.properties | 3 ++ .../src/main/AndroidManifest.xml | 4 ++ .../rxlifecycle2/ControllerEvent.java | 12 +++++ .../ControllerLifecycleSubjectHelper.java | 44 +++++++++++++++++ .../conductor/rxlifecycle2/RxController.java | 49 +++++++++++++++++++ .../rxlifecycle2/RxControllerLifecycle.java | 41 ++++++++++++++++ .../RxRestoreViewOnCreateController.java | 45 +++++++++++++++++ dependencies.gradle | 6 +++ settings.gradle | 9 ++-- 10 files changed, 244 insertions(+), 4 deletions(-) create mode 100644 conductor-rxlifecycle2/build.gradle create mode 100644 conductor-rxlifecycle2/gradle.properties create mode 100644 conductor-rxlifecycle2/src/main/AndroidManifest.xml create mode 100644 conductor-rxlifecycle2/src/main/java/com/bluelinelabs/conductor/rxlifecycle2/ControllerEvent.java create mode 100644 conductor-rxlifecycle2/src/main/java/com/bluelinelabs/conductor/rxlifecycle2/ControllerLifecycleSubjectHelper.java create mode 100644 conductor-rxlifecycle2/src/main/java/com/bluelinelabs/conductor/rxlifecycle2/RxController.java create mode 100644 conductor-rxlifecycle2/src/main/java/com/bluelinelabs/conductor/rxlifecycle2/RxControllerLifecycle.java create mode 100644 conductor-rxlifecycle2/src/main/java/com/bluelinelabs/conductor/rxlifecycle2/RxRestoreViewOnCreateController.java diff --git a/conductor-rxlifecycle2/build.gradle b/conductor-rxlifecycle2/build.gradle new file mode 100644 index 00000000..e2e6eb4e --- /dev/null +++ b/conductor-rxlifecycle2/build.gradle @@ -0,0 +1,35 @@ +apply from: rootProject.file('dependencies.gradle') +apply from: rootProject.file('gradle-mvn-push.gradle') + +apply plugin: 'com.android.library' + +android { + compileSdkVersion rootProject.ext.compileSdkVersion + buildToolsVersion rootProject.ext.buildToolsVersion + + lintOptions { + abortOnError false + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_7 + targetCompatibility JavaVersion.VERSION_1_7 + } + + defaultConfig { + minSdkVersion rootProject.ext.minSdkVersion + targetSdkVersion rootProject.ext.targetSdkVersion + versionCode Integer.parseInt(project.VERSION_CODE) + versionName project.VERSION_NAME + } +} + +dependencies { + compile rootProject.ext.rxJava2 + compile rootProject.ext.rxLifecycle2 + compile rootProject.ext.rxLifecycleAndroid2 + + compile project(':conductor') +} + +ext.artifactId = 'conductor-rxlifecycle2' diff --git a/conductor-rxlifecycle2/gradle.properties b/conductor-rxlifecycle2/gradle.properties new file mode 100644 index 00000000..217f8814 --- /dev/null +++ b/conductor-rxlifecycle2/gradle.properties @@ -0,0 +1,3 @@ +POM_NAME=Conductor RxLifecycle2 Extensions +POM_ARTIFACT_ID=conductor-rxlifecycle2 +POM_PACKAGING=aar diff --git a/conductor-rxlifecycle2/src/main/AndroidManifest.xml b/conductor-rxlifecycle2/src/main/AndroidManifest.xml new file mode 100644 index 00000000..ab33109b --- /dev/null +++ b/conductor-rxlifecycle2/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + + diff --git a/conductor-rxlifecycle2/src/main/java/com/bluelinelabs/conductor/rxlifecycle2/ControllerEvent.java b/conductor-rxlifecycle2/src/main/java/com/bluelinelabs/conductor/rxlifecycle2/ControllerEvent.java new file mode 100644 index 00000000..940d5309 --- /dev/null +++ b/conductor-rxlifecycle2/src/main/java/com/bluelinelabs/conductor/rxlifecycle2/ControllerEvent.java @@ -0,0 +1,12 @@ +package com.bluelinelabs.conductor.rxlifecycle2; + +public enum ControllerEvent { + + CREATE, + CREATE_VIEW, + ATTACH, + DETACH, + DESTROY_VIEW, + DESTROY + +} diff --git a/conductor-rxlifecycle2/src/main/java/com/bluelinelabs/conductor/rxlifecycle2/ControllerLifecycleSubjectHelper.java b/conductor-rxlifecycle2/src/main/java/com/bluelinelabs/conductor/rxlifecycle2/ControllerLifecycleSubjectHelper.java new file mode 100644 index 00000000..f028b9f5 --- /dev/null +++ b/conductor-rxlifecycle2/src/main/java/com/bluelinelabs/conductor/rxlifecycle2/ControllerLifecycleSubjectHelper.java @@ -0,0 +1,44 @@ +package com.bluelinelabs.conductor.rxlifecycle2; + +import android.support.annotation.NonNull; +import android.view.View; +import com.bluelinelabs.conductor.Controller; +import io.reactivex.subjects.BehaviorSubject; + +public class ControllerLifecycleSubjectHelper { + private ControllerLifecycleSubjectHelper() { + } + + public static BehaviorSubject create(Controller controller){ + final BehaviorSubject subject = BehaviorSubject.createDefault(ControllerEvent.CREATE); + + controller.addLifecycleListener(new Controller.LifecycleListener() { + @Override + public void preCreateView(@NonNull Controller controller) { + subject.onNext(ControllerEvent.CREATE_VIEW); + } + + @Override + public void preAttach(@NonNull Controller controller, @NonNull View view) { + subject.onNext(ControllerEvent.ATTACH); + } + + @Override + public void preDetach(@NonNull Controller controller, @NonNull View view) { + subject.onNext(ControllerEvent.DETACH); + } + + @Override + public void preDestroyView(@NonNull Controller controller, @NonNull View view) { + subject.onNext(ControllerEvent.DESTROY_VIEW); + } + + @Override + public void preDestroy(@NonNull Controller controller) { + subject.onNext(ControllerEvent.DESTROY); + } + }); + + return subject; + } +} diff --git a/conductor-rxlifecycle2/src/main/java/com/bluelinelabs/conductor/rxlifecycle2/RxController.java b/conductor-rxlifecycle2/src/main/java/com/bluelinelabs/conductor/rxlifecycle2/RxController.java new file mode 100644 index 00000000..899c0d1c --- /dev/null +++ b/conductor-rxlifecycle2/src/main/java/com/bluelinelabs/conductor/rxlifecycle2/RxController.java @@ -0,0 +1,49 @@ +package com.bluelinelabs.conductor.rxlifecycle2; + +import android.os.Bundle; +import android.support.annotation.CheckResult; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import com.bluelinelabs.conductor.Controller; +import com.trello.rxlifecycle2.LifecycleProvider; +import com.trello.rxlifecycle2.LifecycleTransformer; +import com.trello.rxlifecycle2.RxLifecycle; +import io.reactivex.Observable; +import io.reactivex.subjects.BehaviorSubject; + +/** + * A base {@link Controller} that can be used to expose lifecycle events using RxJava + */ +public abstract class RxController extends Controller implements LifecycleProvider { + private final BehaviorSubject lifecycleSubject; + + public RxController(){ + this(null); + } + + public RxController(@Nullable Bundle args) { + super(args); + lifecycleSubject = ControllerLifecycleSubjectHelper.create(this); + } + + @Override + @NonNull + @CheckResult + public final Observable lifecycle() { + return lifecycleSubject.hide(); + } + + @Override + @NonNull + @CheckResult + public final LifecycleTransformer bindUntilEvent(@NonNull ControllerEvent event) { + return RxLifecycle.bindUntilEvent(lifecycleSubject, event); + } + + @Override + @NonNull + @CheckResult + public final LifecycleTransformer bindToLifecycle() { + return RxControllerLifecycle.bindController(lifecycleSubject); + } +} diff --git a/conductor-rxlifecycle2/src/main/java/com/bluelinelabs/conductor/rxlifecycle2/RxControllerLifecycle.java b/conductor-rxlifecycle2/src/main/java/com/bluelinelabs/conductor/rxlifecycle2/RxControllerLifecycle.java new file mode 100644 index 00000000..adfe6a51 --- /dev/null +++ b/conductor-rxlifecycle2/src/main/java/com/bluelinelabs/conductor/rxlifecycle2/RxControllerLifecycle.java @@ -0,0 +1,41 @@ +package com.bluelinelabs.conductor.rxlifecycle2; + +import android.support.annotation.NonNull; +import com.trello.rxlifecycle2.LifecycleTransformer; +import com.trello.rxlifecycle2.OutsideLifecycleException; +import com.trello.rxlifecycle2.RxLifecycle; +import io.reactivex.Observable; +import io.reactivex.functions.Function; + +public class RxControllerLifecycle { + + /** + * Binds the given source to a Controller lifecycle. This is the Controller version of + * {@link com.trello.rxlifecycle2.android.RxLifecycleAndroid#bindFragment(Observable)}. + * + * @param lifecycle the lifecycle sequence of a Controller + * @return a reusable {@link io.reactivex.ObservableTransformer} that unsubscribes the source during the Controller lifecycle + */ + public static LifecycleTransformer bindController(@NonNull final Observable lifecycle) { + return RxLifecycle.bind(lifecycle, CONTROLLER_LIFECYCLE); + } + + private static final Function CONTROLLER_LIFECYCLE = + new Function() { + @Override + public ControllerEvent apply(ControllerEvent lastEvent) { + switch (lastEvent) { + case CREATE: + return ControllerEvent.DESTROY; + case ATTACH: + return ControllerEvent.DETACH; + case CREATE_VIEW: + return ControllerEvent.DESTROY_VIEW; + case DETACH: + return ControllerEvent.DESTROY; + default: + throw new OutsideLifecycleException("Cannot bind to Controller lifecycle when outside of it."); + } + } + }; +} diff --git a/conductor-rxlifecycle2/src/main/java/com/bluelinelabs/conductor/rxlifecycle2/RxRestoreViewOnCreateController.java b/conductor-rxlifecycle2/src/main/java/com/bluelinelabs/conductor/rxlifecycle2/RxRestoreViewOnCreateController.java new file mode 100644 index 00000000..c95ddc9a --- /dev/null +++ b/conductor-rxlifecycle2/src/main/java/com/bluelinelabs/conductor/rxlifecycle2/RxRestoreViewOnCreateController.java @@ -0,0 +1,45 @@ +package com.bluelinelabs.conductor.rxlifecycle2; + +import android.os.Bundle; +import android.support.annotation.CheckResult; +import android.support.annotation.NonNull; +import com.bluelinelabs.conductor.RestoreViewOnCreateController; +import com.trello.rxlifecycle2.LifecycleProvider; +import com.trello.rxlifecycle2.LifecycleTransformer; +import com.trello.rxlifecycle2.RxLifecycle; +import io.reactivex.Observable; +import io.reactivex.subjects.BehaviorSubject; + +public abstract class RxRestoreViewOnCreateController extends RestoreViewOnCreateController implements LifecycleProvider { + private final BehaviorSubject lifecycleSubject; + + public RxRestoreViewOnCreateController() { + this(null); + } + + public RxRestoreViewOnCreateController(Bundle args) { + super(args); + lifecycleSubject = ControllerLifecycleSubjectHelper.create(this); + } + + @Override + @NonNull + @CheckResult + public final Observable lifecycle() { + return lifecycleSubject.hide(); + } + + @Override + @NonNull + @CheckResult + public final LifecycleTransformer bindUntilEvent(@NonNull ControllerEvent event) { + return RxLifecycle.bindUntilEvent(lifecycleSubject, event); + } + + @Override + @NonNull + @CheckResult + public final LifecycleTransformer bindToLifecycle() { + return RxControllerLifecycle.bindController(lifecycleSubject); + } +} diff --git a/dependencies.gradle b/dependencies.gradle index 002d6455..4c8cf2b1 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -18,10 +18,16 @@ ext { leakCanaryNoOp = 'com.squareup.leakcanary:leakcanary-android-no-op:1.4' rxJava = 'io.reactivex:rxjava:1.2.0' + rxJava2 = "io.reactivex.rxjava2:rxjava:2.0.1" + rxAndroid = 'io.reactivex:rxandroid:1.2.1' + rxLifecycle = 'com.trello:rxlifecycle:0.8.0' rxLifecycleAndroid = 'com.trello:rxlifecycle-android:0.8.0' + rxLifecycle2 = "com.trello.rxlifecycle2:rxlifecycle:2.0" + rxLifecycleAndroid2 = "com.trello.rxlifecycle2:rxlifecycle-android:2.0" + junit = 'junit:junit:4.11' roboelectric = 'org.robolectric:robolectric:3.0' diff --git a/settings.gradle b/settings.gradle index a4115356..914116c7 100755 --- a/settings.gradle +++ b/settings.gradle @@ -1,5 +1,6 @@ include ':conductor' -include':conductor-support' -include':conductor-rxlifecycle' -include':conductor-lint' -include':demo' \ No newline at end of file +include ':conductor-support' +include ':conductor-rxlifecycle' +include ':conductor-rxlifecycle2' +include ':conductor-lint' +include ':demo' From 2388fa2d06e51bc64d21cc6a91271c9bbe4b5f8d Mon Sep 17 00:00:00 2001 From: Eric Kuck Date: Thu, 1 Dec 2016 17:56:43 -0600 Subject: [PATCH 14/75] Added a demo for the new RxLifecycle2Controller --- demo/build.gradle | 5 + .../demo/controllers/HomeController.java | 6 + .../controllers/RxLifecycle2Controller.java | 163 ++++++++++++++++++ .../controllers/RxLifecycleController.java | 44 +++-- .../base/ButterKnifeController.java | 4 +- 5 files changed, 209 insertions(+), 13 deletions(-) create mode 100755 demo/src/main/java/com/bluelinelabs/conductor/demo/controllers/RxLifecycle2Controller.java diff --git a/demo/build.gradle b/demo/build.gradle index 5c288407..49cabbd8 100755 --- a/demo/build.gradle +++ b/demo/build.gradle @@ -37,6 +37,10 @@ android { proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } + + packagingOptions { + exclude 'META-INF/rxjava.properties' + } } dependencies { @@ -49,6 +53,7 @@ dependencies { compile project(':conductor-support') compile project(':conductor-rxlifecycle') + compile project(':conductor-rxlifecycle2') debugCompile rootProject.ext.leakCanary releaseCompile rootProject.ext.leakCanaryNoOp diff --git a/demo/src/main/java/com/bluelinelabs/conductor/demo/controllers/HomeController.java b/demo/src/main/java/com/bluelinelabs/conductor/demo/controllers/HomeController.java index 14316b86..326849d2 100644 --- a/demo/src/main/java/com/bluelinelabs/conductor/demo/controllers/HomeController.java +++ b/demo/src/main/java/com/bluelinelabs/conductor/demo/controllers/HomeController.java @@ -46,6 +46,7 @@ public enum HomeDemoModel { MASTER_DETAIL("Master Detail", R.color.grey_300), DRAG_DISMISS("Drag Dismiss", R.color.lime_300), RX_LIFECYCLE("Rx Lifecycle", R.color.teal_300), + RX_LIFECYCLE_2("Rx Lifecycle 2", R.color.brown_300), OVERLAY("Overlay Controller", R.color.purple_300); String title; @@ -176,6 +177,11 @@ void onModelRowClick(HomeDemoModel model) { .pushChangeHandler(new FadeChangeHandler()) .popChangeHandler(new FadeChangeHandler())); break; + case RX_LIFECYCLE_2: + getRouter().pushController(RouterTransaction.with(new RxLifecycle2Controller()) + .pushChangeHandler(new FadeChangeHandler()) + .popChangeHandler(new FadeChangeHandler())); + break; case MULTIPLE_CHILD_ROUTERS: getRouter().pushController(RouterTransaction.with(new MultipleChildRouterController()) .pushChangeHandler(new FadeChangeHandler()) diff --git a/demo/src/main/java/com/bluelinelabs/conductor/demo/controllers/RxLifecycle2Controller.java b/demo/src/main/java/com/bluelinelabs/conductor/demo/controllers/RxLifecycle2Controller.java new file mode 100755 index 00000000..7d0de436 --- /dev/null +++ b/demo/src/main/java/com/bluelinelabs/conductor/demo/controllers/RxLifecycle2Controller.java @@ -0,0 +1,163 @@ +package com.bluelinelabs.conductor.demo.controllers; + +import android.support.annotation.NonNull; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import com.bluelinelabs.conductor.ControllerChangeHandler; +import com.bluelinelabs.conductor.ControllerChangeType; +import com.bluelinelabs.conductor.RouterTransaction; +import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler; +import com.bluelinelabs.conductor.demo.ActionBarProvider; +import com.bluelinelabs.conductor.demo.DemoApplication; +import com.bluelinelabs.conductor.demo.R; +import com.bluelinelabs.conductor.rxlifecycle2.ControllerEvent; +import com.bluelinelabs.conductor.rxlifecycle2.RxController; + +import java.util.concurrent.TimeUnit; + +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.OnClick; +import butterknife.Unbinder; +import io.reactivex.Observable; +import io.reactivex.functions.Action; +import io.reactivex.functions.Consumer; + +// Shamelessly borrowed from the official RxLifecycle demo by Trello and adapted for Conductor Controllers +// instead of Activities or Fragments. +public class RxLifecycle2Controller extends RxController { + + private static final String TAG = "RxLifecycleController"; + + @BindView(R.id.tv_title) TextView tvTitle; + + private Unbinder unbinder; + private boolean hasExited; + + public RxLifecycle2Controller() { + Observable.interval(1, TimeUnit.SECONDS) + .doOnDispose(new Action() { + @Override + public void run() { + Log.i(TAG, "Disposing from constructor"); + } + }) + .compose(this.bindUntilEvent(ControllerEvent.DESTROY)) + .subscribe(new Consumer() { + @Override + public void accept(Long num) { + Log.i(TAG, "Started in constructor, running until onDestroy(): " + num); + } + }); + } + + @NonNull + @Override + protected View onCreateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) { + Log.i(TAG, "onCreateView() called"); + + View view = inflater.inflate(R.layout.controller_rxlifecycle, container, false); + unbinder = ButterKnife.bind(this, view); + + tvTitle.setText(getResources().getString(R.string.rxlifecycle_title, TAG)); + + Observable.interval(1, TimeUnit.SECONDS) + .doOnDispose(new Action() { + @Override + public void run() { + Log.i(TAG, "Disposing from onCreateView)"); + } + }) + .compose(this.bindUntilEvent(ControllerEvent.DESTROY_VIEW)) + .subscribe(new Consumer() { + @Override + public void accept(Long num) { + Log.i(TAG, "Started in onCreateView(), running until onDestroyView(): " + num); + } + }); + + return view; + } + + @Override + protected void onAttach(@NonNull View view) { + super.onAttach(view); + + Log.i(TAG, "onAttach() called"); + + (((ActionBarProvider)getActivity()).getSupportActionBar()).setTitle("RxLifecycle2 Demo"); + + Observable.interval(1, TimeUnit.SECONDS) + .doOnDispose(new Action() { + @Override + public void run() { + Log.i(TAG, "Disposing from onAttach()"); + } + }) + .compose(this.bindUntilEvent(ControllerEvent.DETACH)) + .subscribe(new Consumer() { + @Override + public void accept(Long num) { + Log.i(TAG, "Started in onAttach(), running until onDetach(): " + num); + } + }); + } + + @Override + protected void onDestroyView(@NonNull View view) { + super.onDestroyView(view); + + Log.i(TAG, "onDestroyView() called"); + + unbinder.unbind(); + unbinder = null; + } + + @Override + protected void onDetach(@NonNull View view) { + super.onDetach(view); + + Log.i(TAG, "onDetach() called"); + } + + @Override + public void onDestroy() { + super.onDestroy(); + + Log.i(TAG, "onDestroy() called"); + + 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); + } + } + + @OnClick(R.id.btn_next_release_view) void onNextWithReleaseClicked() { + setRetainViewMode(RetainViewMode.RELEASE_DETACH); + + getRouter().pushController(RouterTransaction.with(new TextController("Logcat should now report that the observables from onAttach() and onViewBound() have been disposed of, while the constructor observable is still running.")) + .pushChangeHandler(new HorizontalChangeHandler()) + .popChangeHandler(new HorizontalChangeHandler())); + } + + @OnClick(R.id.btn_next_retain_view) void onNextWithRetainClicked() { + setRetainViewMode(RetainViewMode.RETAIN_DETACH); + + getRouter().pushController(RouterTransaction.with(new TextController("Logcat should now report that the observables from onAttach() has been disposed of, while the constructor and onViewBound() observables are still running.")) + .pushChangeHandler(new HorizontalChangeHandler()) + .popChangeHandler(new HorizontalChangeHandler())); + } +} diff --git a/demo/src/main/java/com/bluelinelabs/conductor/demo/controllers/RxLifecycleController.java b/demo/src/main/java/com/bluelinelabs/conductor/demo/controllers/RxLifecycleController.java index 1dc132f3..746de6c7 100755 --- a/demo/src/main/java/com/bluelinelabs/conductor/demo/controllers/RxLifecycleController.java +++ b/demo/src/main/java/com/bluelinelabs/conductor/demo/controllers/RxLifecycleController.java @@ -7,30 +7,38 @@ import android.view.ViewGroup; import android.widget.TextView; +import com.bluelinelabs.conductor.ControllerChangeHandler; +import com.bluelinelabs.conductor.ControllerChangeType; import com.bluelinelabs.conductor.RouterTransaction; import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler; +import com.bluelinelabs.conductor.demo.ActionBarProvider; +import com.bluelinelabs.conductor.demo.DemoApplication; import com.bluelinelabs.conductor.demo.R; -import com.bluelinelabs.conductor.demo.controllers.base.BaseController; import com.bluelinelabs.conductor.rxlifecycle.ControllerEvent; +import com.bluelinelabs.conductor.rxlifecycle.RxController; import java.util.concurrent.TimeUnit; import butterknife.BindView; +import butterknife.ButterKnife; import butterknife.OnClick; +import butterknife.Unbinder; import rx.Observable; import rx.functions.Action0; import rx.functions.Action1; // Shamelessly borrowed from the official RxLifecycle demo by Trello and adapted for Conductor Controllers // instead of Activities or Fragments. -public class RxLifecycleController extends BaseController { +public class RxLifecycleController extends RxController { private static final String TAG = "RxLifecycleController"; @BindView(R.id.tv_title) TextView tvTitle; - public RxLifecycleController() { + private Unbinder unbinder; + private boolean hasExited; + public RxLifecycleController() { Observable.interval(1, TimeUnit.SECONDS) .doOnUnsubscribe(new Action0() { @Override @@ -47,10 +55,14 @@ public void call(Long num) { }); } + @NonNull @Override - public void onViewBound(@NonNull View view) { + protected View onCreateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) { Log.i(TAG, "onCreateView() called"); + View view = inflater.inflate(R.layout.controller_rxlifecycle, container, false); + unbinder = ButterKnife.bind(this, view); + tvTitle.setText(getResources().getString(R.string.rxlifecycle_title, TAG)); Observable.interval(1, TimeUnit.SECONDS) @@ -67,6 +79,8 @@ public void call(Long num) { Log.i(TAG, "Started in onCreateView(), running until onDestroyView(): " + num); } }); + + return view; } @Override @@ -75,6 +89,8 @@ protected void onAttach(@NonNull View view) { Log.i(TAG, "onAttach() called"); + (((ActionBarProvider)getActivity()).getSupportActionBar()).setTitle("RxLifecycle Demo"); + Observable.interval(1, TimeUnit.SECONDS) .doOnUnsubscribe(new Action0() { @Override @@ -96,6 +112,9 @@ protected void onDestroyView(@NonNull View view) { super.onDestroyView(view); Log.i(TAG, "onDestroyView() called"); + + unbinder.unbind(); + unbinder = null; } @Override @@ -110,17 +129,20 @@ public void onDestroy() { super.onDestroy(); Log.i(TAG, "onDestroy() called"); - } - @Override - protected String getTitle() { - return "RxLifecycle Demo"; + if (hasExited) { + DemoApplication.refWatcher.watch(this); + } } - @NonNull @Override - protected View inflateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) { - return inflater.inflate(R.layout.controller_rxlifecycle, container, false); + protected void onChangeEnded(@NonNull ControllerChangeHandler changeHandler, @NonNull ControllerChangeType changeType) { + super.onChangeEnded(changeHandler, changeType); + + hasExited = !changeType.isEnter; + if (isDestroyed()) { + DemoApplication.refWatcher.watch(this); + } } @OnClick(R.id.btn_next_release_view) void onNextWithReleaseClicked() { diff --git a/demo/src/main/java/com/bluelinelabs/conductor/demo/controllers/base/ButterKnifeController.java b/demo/src/main/java/com/bluelinelabs/conductor/demo/controllers/base/ButterKnifeController.java index 610af12a..f30a9603 100644 --- a/demo/src/main/java/com/bluelinelabs/conductor/demo/controllers/base/ButterKnifeController.java +++ b/demo/src/main/java/com/bluelinelabs/conductor/demo/controllers/base/ButterKnifeController.java @@ -6,12 +6,12 @@ import android.view.View; import android.view.ViewGroup; -import com.bluelinelabs.conductor.rxlifecycle.RxController; +import com.bluelinelabs.conductor.Controller; import butterknife.ButterKnife; import butterknife.Unbinder; -public abstract class ButterKnifeController extends RxController { +public abstract class ButterKnifeController extends Controller { private Unbinder unbinder; From 95baa8baa33132696d5c4dd7e0d64c4b7fe6fb20 Mon Sep 17 00:00:00 2001 From: Eric Kuck Date: Thu, 1 Dec 2016 18:00:06 -0600 Subject: [PATCH 15/75] Fixes #172 --- .../src/main/java/com/bluelinelabs/conductor/Controller.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/conductor/src/main/java/com/bluelinelabs/conductor/Controller.java b/conductor/src/main/java/com/bluelinelabs/conductor/Controller.java index 16e35a6f..320bd697 100755 --- a/conductor/src/main/java/com/bluelinelabs/conductor/Controller.java +++ b/conductor/src/main/java/com/bluelinelabs/conductor/Controller.java @@ -130,7 +130,7 @@ protected Controller() { * @param args Any arguments that need to be retained. */ protected Controller(@Nullable Bundle args) { - this.args = args; + this.args = args != null ? args : new Bundle(); instanceId = UUID.randomUUID().toString(); ensureRequiredConstructor(); } @@ -158,7 +158,7 @@ public final Router getRouter() { /** * Returns any arguments that were set in this Controller's constructor */ - @Nullable + @NonNull public Bundle getArgs() { return args; } From acce9b170211b8c1c02e38688450a2516c737d70 Mon Sep 17 00:00:00 2001 From: Eric Kuck Date: Wed, 7 Dec 2016 16:17:32 -0600 Subject: [PATCH 16/75] Simplified setRoot implementation --- .../com/bluelinelabs/conductor/Backstack.java | 36 +++++++++---------- .../com/bluelinelabs/conductor/Router.java | 26 ++------------ 2 files changed, 21 insertions(+), 41 deletions(-) diff --git a/conductor/src/main/java/com/bluelinelabs/conductor/Backstack.java b/conductor/src/main/java/com/bluelinelabs/conductor/Backstack.java index 2563966c..11d786c2 100755 --- a/conductor/src/main/java/com/bluelinelabs/conductor/Backstack.java +++ b/conductor/src/main/java/com/bluelinelabs/conductor/Backstack.java @@ -15,37 +15,37 @@ class Backstack implements Iterable { private static final String KEY_ENTRIES = "Backstack.entries"; - private final Deque backStack = new ArrayDeque<>(); + private final Deque backstack = new ArrayDeque<>(); @SuppressWarnings("BooleanMethodIsAlwaysInverted") public boolean isEmpty() { - return backStack.isEmpty(); + return backstack.isEmpty(); } public int size() { - return backStack.size(); + return backstack.size(); } @Nullable public RouterTransaction root() { - return backStack.size() > 0 ? backStack.getLast() : null; + return backstack.size() > 0 ? backstack.getLast() : null; } @Override @NonNull public Iterator iterator() { - return backStack.iterator(); + return backstack.iterator(); } @NonNull public Iterator reverseIterator() { - return backStack.descendingIterator(); + return backstack.descendingIterator(); } @NonNull public List popTo(@NonNull RouterTransaction transaction) { List popped = new ArrayList<>(); - if (backStack.contains(transaction)) { - while (backStack.peek() != transaction) { + if (backstack.contains(transaction)) { + while (backstack.peek() != transaction) { RouterTransaction poppedTransaction = pop(); popped.add(poppedTransaction); } @@ -57,22 +57,22 @@ public List popTo(@NonNull RouterTransaction transaction) { @NonNull public RouterTransaction pop() { - RouterTransaction popped = backStack.pop(); + RouterTransaction popped = backstack.pop(); popped.controller.destroy(); return popped; } @Nullable public RouterTransaction peek() { - return backStack.peek(); + return backstack.peek(); } public void remove(@NonNull RouterTransaction transaction) { - backStack.removeFirstOccurrence(transaction); + backstack.removeFirstOccurrence(transaction); } public void push(@NonNull RouterTransaction transaction) { - backStack.push(transaction); + backstack.push(transaction); } @NonNull @@ -85,7 +85,7 @@ public List popAll() { } public void setBackstack(@NonNull List backstack) { - for (RouterTransaction existingTransaction : backStack) { + for (RouterTransaction existingTransaction : this.backstack) { boolean contains = false; for (RouterTransaction newTransaction : backstack) { if (existingTransaction.controller == newTransaction.controller) { @@ -99,15 +99,15 @@ public void setBackstack(@NonNull List backstack) { } } - backStack.clear(); + this.backstack.clear(); for (RouterTransaction transaction : backstack) { - backStack.push(transaction); + this.backstack.push(transaction); } } public void saveInstanceState(@NonNull Bundle outState) { - ArrayList entryBundles = new ArrayList<>(backStack.size()); - for (RouterTransaction entry : backStack) { + ArrayList entryBundles = new ArrayList<>(backstack.size()); + for (RouterTransaction entry : backstack) { entryBundles.add(entry.saveInstanceState()); } @@ -119,7 +119,7 @@ public void restoreInstanceState(@NonNull Bundle savedInstanceState) { if (entryBundles != null) { Collections.reverse(entryBundles); for (Bundle transactionBundle : entryBundles) { - backStack.push(new RouterTransaction(transactionBundle)); + backstack.push(new RouterTransaction(transactionBundle)); } } } diff --git a/conductor/src/main/java/com/bluelinelabs/conductor/Router.java b/conductor/src/main/java/com/bluelinelabs/conductor/Router.java index c2de6ee7..16ce9b4d 100755 --- a/conductor/src/main/java/com/bluelinelabs/conductor/Router.java +++ b/conductor/src/main/java/com/bluelinelabs/conductor/Router.java @@ -275,29 +275,9 @@ public boolean popToTag(@NonNull String tag, @Nullable ControllerChangeHandler c */ @UiThread public void setRoot(@NonNull RouterTransaction transaction) { - ControllerChangeHandler newHandler = transaction.pushChangeHandler() != null ? transaction.pushChangeHandler() : new SimpleSwapChangeHandler(); - - List visibleTransactions = getVisibleTransactions(backstack.iterator()); - RouterTransaction rootTransaction = visibleTransactions.size() > 0 ? visibleTransactions.get(0) : null; - - removeAllExceptVisibleAndUnowned(); - - trackDestroyingControllers(backstack.popAll()); - - pushToBackstack(transaction); - - 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); + List transactions = new ArrayList<>(); + transactions.add(transaction); + setBackstack(transactions, transaction.pushChangeHandler()); } /** From 9655170bd2fcd2f0fc61de9cb4d324780b6e8737 Mon Sep 17 00:00:00 2001 From: Eric Kuck Date: Wed, 7 Dec 2016 16:34:38 -0600 Subject: [PATCH 17/75] Fixes tests --- conductor/src/main/java/com/bluelinelabs/conductor/Router.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conductor/src/main/java/com/bluelinelabs/conductor/Router.java b/conductor/src/main/java/com/bluelinelabs/conductor/Router.java index 16ce9b4d..e92a6c02 100755 --- a/conductor/src/main/java/com/bluelinelabs/conductor/Router.java +++ b/conductor/src/main/java/com/bluelinelabs/conductor/Router.java @@ -365,7 +365,7 @@ public void setBackstack(@NonNull List newBackstack, @Nullabl ControllerChangeHandler handler = changeHandler != null ? changeHandler : new SimpleSwapChangeHandler(); Controller rootController = oldVisibleTransactions.size() > 0 ? oldVisibleTransactions.get(0).controller : null; - performControllerChange(newVisibleTransactions.get(0).controller, rootController, true, handler.copy()); + performControllerChange(newVisibleTransactions.get(0).controller, rootController, true, handler); for (int i = oldVisibleTransactions.size() - 1; i > 0; i--) { RouterTransaction transaction = oldVisibleTransactions.get(i); From 7ea4872ff8298e86b5b9e283659cf4887e579763 Mon Sep 17 00:00:00 2001 From: Eric Kuck Date: Thu, 8 Dec 2016 13:25:11 -0600 Subject: [PATCH 18/75] Updated ordering of calls in backstack to be in line with other backstack-affecting calls --- .../main/java/com/bluelinelabs/conductor/Router.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/conductor/src/main/java/com/bluelinelabs/conductor/Router.java b/conductor/src/main/java/com/bluelinelabs/conductor/Router.java index e92a6c02..1375f3f2 100755 --- a/conductor/src/main/java/com/bluelinelabs/conductor/Router.java +++ b/conductor/src/main/java/com/bluelinelabs/conductor/Router.java @@ -344,6 +344,12 @@ public List getBackstack() { public void setBackstack(@NonNull List newBackstack, @Nullable ControllerChangeHandler changeHandler) { List oldVisibleTransactions = getVisibleTransactions(backstack.iterator()); + backstack.setBackstack(newBackstack); + + for (RouterTransaction transaction : backstack) { + transaction.onAttachedToRouter(); + } + removeAllExceptVisibleAndUnowned(); if (newBackstack.size() > 0) { @@ -382,12 +388,6 @@ public void setBackstack(@NonNull List newBackstack, @Nullabl } } - for (RouterTransaction transaction : backstack) { - transaction.onAttachedToRouter(); - } - - backstack.setBackstack(newBackstack); - if (onControllerPushedListener != null) { for (RouterTransaction transaction : newBackstack) { onControllerPushedListener.onControllerPushed(transaction.controller); From 7334ed530061647611c719ee9748b23706341d06 Mon Sep 17 00:00:00 2001 From: Eric Kuck Date: Mon, 12 Dec 2016 12:24:26 -0600 Subject: [PATCH 19/75] ControllerPagerAdapter updates to enable using a per-page router if needed. --- .../support/ControllerPagerAdapter.java | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/conductor-support/src/main/java/com/bluelinelabs/conductor/support/ControllerPagerAdapter.java b/conductor-support/src/main/java/com/bluelinelabs/conductor/support/ControllerPagerAdapter.java index 3af9a3c6..b54eb141 100644 --- a/conductor-support/src/main/java/com/bluelinelabs/conductor/support/ControllerPagerAdapter.java +++ b/conductor-support/src/main/java/com/bluelinelabs/conductor/support/ControllerPagerAdapter.java @@ -11,6 +11,8 @@ import com.bluelinelabs.conductor.Router; import com.bluelinelabs.conductor.RouterTransaction; +import java.util.List; + /** * An adapter for ViewPagers that will handle adding and removing Controllers */ @@ -38,7 +40,7 @@ public ControllerPagerAdapter(Controller host, boolean saveControllerState) { @Override public Object instantiateItem(ViewGroup container, int position) { - final String name = makeControllerName(container.getId(), getItemId(position)); + final String name = makeRouterName(container.getId(), getItemId(position)); Router router = host.getChildRouter(container, name); if (savesState && !router.hasRootController()) { @@ -56,12 +58,12 @@ public Object instantiateItem(ViewGroup container, int position) { router.rebindIfNeeded(); } - return router.getControllerWithTag(name); + return router; } @Override public void destroyItem(ViewGroup container, int position, Object object) { - Router router = ((Controller)object).getRouter(); + Router router = (Router)object; if (savesState) { Bundle savedState = new Bundle(); @@ -74,7 +76,14 @@ public void destroyItem(ViewGroup container, int position, Object object) { @Override public boolean isViewFromObject(View view, Object object) { - return ((Controller)object).getView() == view; + Router router = (Router)object; + final List backstack = router.getBackstack(); + for (RouterTransaction transaction : backstack) { + if (transaction.controller().getView() == view) { + return true; + } + } + return false; } @Override @@ -98,7 +107,7 @@ public long getItemId(int position) { return position; } - private static String makeControllerName(int viewId, long id) { + private static String makeRouterName(int viewId, long id) { return viewId + ":" + id; } From 43c825f7c26235bc075c56d568877db4993f9918 Mon Sep 17 00:00:00 2001 From: Eric Kuck Date: Mon, 12 Dec 2016 13:09:29 -0600 Subject: [PATCH 20/75] - Child backstack is now properly restored when Android kills the process - Added a RouterPagerAdapter, which allows the use of Routers as pages --- .../support/ControllerPagerAdapter.java | 22 +--- .../conductor/support/RouterPagerAdapter.java | 112 ++++++++++++++++++ .../bluelinelabs/conductor/Controller.java | 15 +++ 3 files changed, 133 insertions(+), 16 deletions(-) create mode 100644 conductor-support/src/main/java/com/bluelinelabs/conductor/support/RouterPagerAdapter.java diff --git a/conductor-support/src/main/java/com/bluelinelabs/conductor/support/ControllerPagerAdapter.java b/conductor-support/src/main/java/com/bluelinelabs/conductor/support/ControllerPagerAdapter.java index b54eb141..f139bf86 100644 --- a/conductor-support/src/main/java/com/bluelinelabs/conductor/support/ControllerPagerAdapter.java +++ b/conductor-support/src/main/java/com/bluelinelabs/conductor/support/ControllerPagerAdapter.java @@ -11,8 +11,6 @@ import com.bluelinelabs.conductor.Router; import com.bluelinelabs.conductor.RouterTransaction; -import java.util.List; - /** * An adapter for ViewPagers that will handle adding and removing Controllers */ @@ -40,7 +38,7 @@ public ControllerPagerAdapter(Controller host, boolean saveControllerState) { @Override public Object instantiateItem(ViewGroup container, int position) { - final String name = makeRouterName(container.getId(), getItemId(position)); + final String name = makeControllerName(container.getId(), getItemId(position)); Router router = host.getChildRouter(container, name); if (savesState && !router.hasRootController()) { @@ -52,18 +50,17 @@ public Object instantiateItem(ViewGroup container, int position) { } if (!router.hasRootController()) { - router.setRoot(RouterTransaction.with(getItem(position)) - .tag(name)); + router.setRoot(RouterTransaction.with(getItem(position)).tag(name)); } else { router.rebindIfNeeded(); } - return router; + return router.getControllerWithTag(name); } @Override public void destroyItem(ViewGroup container, int position, Object object) { - Router router = (Router)object; + Router router = ((Controller)object).getRouter(); if (savesState) { Bundle savedState = new Bundle(); @@ -76,14 +73,7 @@ public void destroyItem(ViewGroup container, int position, Object object) { @Override public boolean isViewFromObject(View view, Object object) { - Router router = (Router)object; - final List backstack = router.getBackstack(); - for (RouterTransaction transaction : backstack) { - if (transaction.controller().getView() == view) { - return true; - } - } - return false; + return ((Controller)object).getView() == view; } @Override @@ -107,7 +97,7 @@ public long getItemId(int position) { return position; } - private static String makeRouterName(int viewId, long id) { + private static String makeControllerName(int viewId, long id) { return viewId + ":" + id; } diff --git a/conductor-support/src/main/java/com/bluelinelabs/conductor/support/RouterPagerAdapter.java b/conductor-support/src/main/java/com/bluelinelabs/conductor/support/RouterPagerAdapter.java new file mode 100644 index 00000000..d8f09adc --- /dev/null +++ b/conductor-support/src/main/java/com/bluelinelabs/conductor/support/RouterPagerAdapter.java @@ -0,0 +1,112 @@ +package com.bluelinelabs.conductor.support; + +import android.os.Bundle; +import android.os.Parcelable; +import android.support.v4.view.PagerAdapter; +import android.util.SparseArray; +import android.view.View; +import android.view.ViewGroup; + +import com.bluelinelabs.conductor.Controller; +import com.bluelinelabs.conductor.Router; +import com.bluelinelabs.conductor.RouterTransaction; + +import java.util.List; + +/** + * An adapter for ViewPagers that uses Routers as pages + */ +public abstract class RouterPagerAdapter extends PagerAdapter { + + private static final String KEY_SAVED_PAGES = "RouterPagerAdapter.savedStates"; + private static final String KEY_SAVES_STATE = "RouterPagerAdapter.savesState"; + + private final Controller host; + private boolean savesState; + private SparseArray savedPages = new SparseArray<>(); + + /** + * Creates a new RouterPagerAdapter using the passed host. + */ + public RouterPagerAdapter(Controller host, boolean saveRouterState) { + this.host = host; + savesState = saveRouterState; + } + + /** + * Called when a router is instantiated. Here the router's root should be set if needed. + * + * @param router The router used for the page + * @param position The page position to be instantiated. + */ + public abstract void configureRouter(Router router, int position); + + @Override + public Object instantiateItem(ViewGroup container, int position) { + final String name = makeRouterName(container.getId(), getItemId(position)); + + Router router = host.getChildRouter(container, name); + if (savesState && !router.hasRootController()) { + Bundle routerSavedState = savedPages.get(position); + + if (routerSavedState != null) { + router.restoreInstanceState(routerSavedState); + } + } + + router.rebindIfNeeded(); + configureRouter(router, position); + return router; + } + + @Override + public void destroyItem(ViewGroup container, int position, Object object) { + Router router = (Router)object; + + if (savesState) { + Bundle savedState = new Bundle(); + router.saveInstanceState(savedState); + savedPages.put(position, savedState); + } + + host.removeChildRouter(router); + } + + @Override + public boolean isViewFromObject(View view, Object object) { + Router router = (Router)object; + final List backstack = router.getBackstack(); + for (RouterTransaction transaction : backstack) { + if (transaction.controller().getView() == view) { + return true; + } + } + return false; + } + + @Override + public Parcelable saveState() { + Bundle bundle = new Bundle(); + bundle.putBoolean(KEY_SAVES_STATE, savesState); + bundle.putSparseParcelableArray(KEY_SAVED_PAGES, savedPages); + return bundle; + } + + @Override + public void restoreState(Parcelable state, ClassLoader loader) { + Bundle bundle = (Bundle)state; + if (state != null) { + savesState = bundle.getBoolean(KEY_SAVES_STATE, false); + savedPages = bundle.getSparseParcelableArray(KEY_SAVED_PAGES); + } + } + + public long getItemId(int position) { + return position; + } + + private static String makeRouterName(int viewId, long id) { + return viewId + ":" + id; + } + +} \ No newline at end of file diff --git a/conductor/src/main/java/com/bluelinelabs/conductor/Controller.java b/conductor/src/main/java/com/bluelinelabs/conductor/Controller.java index 320bd697..ad841b45 100755 --- a/conductor/src/main/java/com/bluelinelabs/conductor/Controller.java +++ b/conductor/src/main/java/com/bluelinelabs/conductor/Controller.java @@ -44,6 +44,7 @@ public abstract class Controller { private static final String KEY_CLASS_NAME = "Controller.className"; private static final String KEY_VIEW_STATE = "Controller.viewState"; private static final String KEY_CHILD_ROUTERS = "Controller.childRouters"; + private static final String KEY_CHILD_BACKSTACK = "Controller.childBackstack"; private static final String KEY_SAVED_STATE = "Controller.savedState"; private static final String KEY_INSTANCE_ID = "Controller.instanceId"; private static final String KEY_TARGET_INSTANCE_ID = "Controller.target.instanceId"; @@ -1021,6 +1022,12 @@ final Bundle saveInstanceState() { } outState.putParcelableArrayList(KEY_CHILD_ROUTERS, childBundles); + ArrayList childBackstack = new ArrayList<>(); + for (Controller controller : this.childBackstack) { + childBackstack.add(controller.instanceId); + } + outState.putStringArrayList(KEY_CHILD_BACKSTACK, childBackstack); + Bundle savedState = new Bundle(); onSaveInstanceState(savedState); @@ -1052,6 +1059,14 @@ private void restoreInstanceState(@NonNull Bundle savedInstanceState) { childRouters.add(childRouter); } + List childBackstackIds = savedInstanceState.getStringArrayList(KEY_CHILD_BACKSTACK); + for (String controllerId : childBackstackIds) { + Controller childController = findController(controllerId); + if (childController != null) { + childBackstack.add(childController); + } + } + this.savedInstanceState = savedInstanceState.getBundle(KEY_SAVED_STATE); performOnRestoreInstanceState(); } From c8640af1ac95504167e272bc10e15241026e1dc1 Mon Sep 17 00:00:00 2001 From: Eric Kuck Date: Mon, 12 Dec 2016 13:25:57 -0600 Subject: [PATCH 21/75] Remove saveState option for RouterPagerAdapters, as users can configure the page before display anyway. --- .../conductor/support/RouterPagerAdapter.java | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/conductor-support/src/main/java/com/bluelinelabs/conductor/support/RouterPagerAdapter.java b/conductor-support/src/main/java/com/bluelinelabs/conductor/support/RouterPagerAdapter.java index d8f09adc..bad3a52c 100644 --- a/conductor-support/src/main/java/com/bluelinelabs/conductor/support/RouterPagerAdapter.java +++ b/conductor-support/src/main/java/com/bluelinelabs/conductor/support/RouterPagerAdapter.java @@ -19,18 +19,15 @@ public abstract class RouterPagerAdapter extends PagerAdapter { private static final String KEY_SAVED_PAGES = "RouterPagerAdapter.savedStates"; - private static final String KEY_SAVES_STATE = "RouterPagerAdapter.savesState"; private final Controller host; - private boolean savesState; private SparseArray savedPages = new SparseArray<>(); /** * Creates a new RouterPagerAdapter using the passed host. */ - public RouterPagerAdapter(Controller host, boolean saveRouterState) { + public RouterPagerAdapter(Controller host) { this.host = host; - savesState = saveRouterState; } /** @@ -46,7 +43,7 @@ public Object instantiateItem(ViewGroup container, int position) { final String name = makeRouterName(container.getId(), getItemId(position)); Router router = host.getChildRouter(container, name); - if (savesState && !router.hasRootController()) { + if (!router.hasRootController()) { Bundle routerSavedState = savedPages.get(position); if (routerSavedState != null) { @@ -63,11 +60,9 @@ public Object instantiateItem(ViewGroup container, int position) { public void destroyItem(ViewGroup container, int position, Object object) { Router router = (Router)object; - if (savesState) { - Bundle savedState = new Bundle(); - router.saveInstanceState(savedState); - savedPages.put(position, savedState); - } + Bundle savedState = new Bundle(); + router.saveInstanceState(savedState); + savedPages.put(position, savedState); host.removeChildRouter(router); } @@ -87,7 +82,6 @@ public boolean isViewFromObject(View view, Object object) { @Override public Parcelable saveState() { Bundle bundle = new Bundle(); - bundle.putBoolean(KEY_SAVES_STATE, savesState); bundle.putSparseParcelableArray(KEY_SAVED_PAGES, savedPages); return bundle; } @@ -96,7 +90,6 @@ public Parcelable saveState() { public void restoreState(Parcelable state, ClassLoader loader) { Bundle bundle = (Bundle)state; if (state != null) { - savesState = bundle.getBoolean(KEY_SAVES_STATE, false); savedPages = bundle.getSparseParcelableArray(KEY_SAVED_PAGES); } } From 9cd225e704944802467ece32802cf2c001a3f483 Mon Sep 17 00:00:00 2001 From: Eric Kuck Date: Mon, 12 Dec 2016 14:31:22 -0600 Subject: [PATCH 22/75] Controllers now throw an exception when the user forgets to pass false for LayoutInflater.inflate's attachToRoot parameter. --- .../src/main/java/com/bluelinelabs/conductor/Controller.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/conductor/src/main/java/com/bluelinelabs/conductor/Controller.java b/conductor/src/main/java/com/bluelinelabs/conductor/Controller.java index ad841b45..95d1e96b 100755 --- a/conductor/src/main/java/com/bluelinelabs/conductor/Controller.java +++ b/conductor/src/main/java/com/bluelinelabs/conductor/Controller.java @@ -866,6 +866,9 @@ final View inflate(@NonNull ViewGroup parent) { } view = onCreateView(LayoutInflater.from(parent.getContext()), parent); + if (view == parent) { + throw new IllegalStateException("Controller's onCreateView method returned the parent ViewGroup. Perhaps you forgot to pass false for LayoutInflater.inflate's attachToRoot parameter?"); + } listeners = new ArrayList<>(lifecycleListeners); for (LifecycleListener lifecycleListener : listeners) { From 4a814afb5f2eb78767c12aad6d2bb736bdd763e6 Mon Sep 17 00:00:00 2001 From: Eric Kuck Date: Tue, 13 Dec 2016 16:56:22 -0600 Subject: [PATCH 23/75] Fixes #166 --- build.gradle | 2 +- .../support/ControllerPagerAdapter.java | 40 ++++++++++++++++++- .../conductor/support/RouterPagerAdapter.java | 14 +++++++ .../bluelinelabs/conductor/Controller.java | 26 +++++++++--- 4 files changed, 75 insertions(+), 7 deletions(-) diff --git a/build.gradle b/build.gradle index 82d92403..6806c9fb 100755 --- a/build.gradle +++ b/build.gradle @@ -3,7 +3,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:2.2.2' + classpath 'com.android.tools.build:gradle:2.2.3' } } diff --git a/conductor-support/src/main/java/com/bluelinelabs/conductor/support/ControllerPagerAdapter.java b/conductor-support/src/main/java/com/bluelinelabs/conductor/support/ControllerPagerAdapter.java index f139bf86..b162a180 100644 --- a/conductor-support/src/main/java/com/bluelinelabs/conductor/support/ControllerPagerAdapter.java +++ b/conductor-support/src/main/java/com/bluelinelabs/conductor/support/ControllerPagerAdapter.java @@ -2,6 +2,7 @@ import android.os.Bundle; import android.os.Parcelable; +import android.support.annotation.Nullable; import android.support.v4.view.PagerAdapter; import android.util.SparseArray; import android.view.View; @@ -18,10 +19,13 @@ public abstract class ControllerPagerAdapter extends PagerAdapter { private static final String KEY_SAVED_PAGES = "ControllerPagerAdapter.savedStates"; private static final String KEY_SAVES_STATE = "ControllerPagerAdapter.savesState"; + private static final String KEY_VISIBLE_PAGE_IDS_KEYS = "ControllerPagerAdapter.visiblePageIds.keys"; + private static final String KEY_VISIBLE_PAGE_IDS_VALUES = "ControllerPagerAdapter.visiblePageIds.values"; private final Controller host; private boolean savesState; private SparseArray savedPages = new SparseArray<>(); + private SparseArray visiblePageIds = new SparseArray<>(); /** * Creates a new ControllerPagerAdapter using the passed host. @@ -50,7 +54,9 @@ public Object instantiateItem(ViewGroup container, int position) { } if (!router.hasRootController()) { - router.setRoot(RouterTransaction.with(getItem(position)).tag(name)); + Controller controller = getItem(position); + router.setRoot(RouterTransaction.with(controller).tag(name)); + visiblePageIds.put(position, controller.getInstanceId()); } else { router.rebindIfNeeded(); } @@ -68,6 +74,8 @@ public void destroyItem(ViewGroup container, int position, Object object) { savedPages.put(position, savedState); } + visiblePageIds.remove(position); + host.removeChildRouter(router); } @@ -81,6 +89,16 @@ public Parcelable saveState() { Bundle bundle = new Bundle(); bundle.putBoolean(KEY_SAVES_STATE, savesState); bundle.putSparseParcelableArray(KEY_SAVED_PAGES, savedPages); + + int[] visiblePageIdsKeys = new int[visiblePageIds.size()]; + String[] visiblePageIdsValues = new String[visiblePageIds.size()]; + for (int i = 0; i < visiblePageIds.size(); i++) { + visiblePageIdsKeys[i] = visiblePageIds.keyAt(i); + visiblePageIdsValues[i] = visiblePageIds.valueAt(i); + } + bundle.putIntArray(KEY_VISIBLE_PAGE_IDS_KEYS, visiblePageIdsKeys); + bundle.putStringArray(KEY_VISIBLE_PAGE_IDS_VALUES, visiblePageIdsValues); + return bundle; } @@ -90,6 +108,26 @@ public void restoreState(Parcelable state, ClassLoader loader) { if (state != null) { savesState = bundle.getBoolean(KEY_SAVES_STATE, false); savedPages = bundle.getSparseParcelableArray(KEY_SAVED_PAGES); + + int[] visiblePageIdsKeys = bundle.getIntArray(KEY_VISIBLE_PAGE_IDS_KEYS); + String[] visiblePageIdsValues = bundle.getStringArray(KEY_VISIBLE_PAGE_IDS_VALUES); + visiblePageIds = new SparseArray<>(visiblePageIdsKeys.length); + for (int i = 0; i < visiblePageIdsKeys.length; i++) { + visiblePageIds.put(visiblePageIdsKeys[i], visiblePageIdsValues[i]); + } + } + } + + /** + * Returns the already instantiated Controller in the specified position, if available. + */ + @Nullable + public Controller getController(int position) { + String instanceId = visiblePageIds.get(position); + if (instanceId != null) { + return host.getRouter().getControllerWithInstanceId(instanceId); + } else { + return null; } } diff --git a/conductor-support/src/main/java/com/bluelinelabs/conductor/support/RouterPagerAdapter.java b/conductor-support/src/main/java/com/bluelinelabs/conductor/support/RouterPagerAdapter.java index bad3a52c..cc01025f 100644 --- a/conductor-support/src/main/java/com/bluelinelabs/conductor/support/RouterPagerAdapter.java +++ b/conductor-support/src/main/java/com/bluelinelabs/conductor/support/RouterPagerAdapter.java @@ -2,6 +2,7 @@ import android.os.Bundle; import android.os.Parcelable; +import android.support.annotation.Nullable; import android.support.v4.view.PagerAdapter; import android.util.SparseArray; import android.view.View; @@ -22,6 +23,7 @@ public abstract class RouterPagerAdapter extends PagerAdapter { private final Controller host; private SparseArray savedPages = new SparseArray<>(); + private SparseArray visibleRouters = new SparseArray<>(); /** * Creates a new RouterPagerAdapter using the passed host. @@ -53,6 +55,8 @@ public Object instantiateItem(ViewGroup container, int position) { router.rebindIfNeeded(); configureRouter(router, position); + + visibleRouters.put(position, router); return router; } @@ -65,6 +69,8 @@ public void destroyItem(ViewGroup container, int position, Object object) { savedPages.put(position, savedState); host.removeChildRouter(router); + + visibleRouters.remove(position); } @Override @@ -94,6 +100,14 @@ public void restoreState(Parcelable state, ClassLoader loader) { } } + /** + * Returns the already instantiated Router in the specified position, if available. + */ + @Nullable + public Router getRouter(int position) { + return visibleRouters.get(position); + } + public long getItemId(int position) { return position; } diff --git a/conductor/src/main/java/com/bluelinelabs/conductor/Controller.java b/conductor/src/main/java/com/bluelinelabs/conductor/Controller.java index 95d1e96b..851a7ccb 100755 --- a/conductor/src/main/java/com/bluelinelabs/conductor/Controller.java +++ b/conductor/src/main/java/com/bluelinelabs/conductor/Controller.java @@ -172,7 +172,6 @@ public Bundle getArgs() { */ @NonNull public final Router getChildRouter(@NonNull ViewGroup container) { - //noinspection deprecation return getChildRouter(container, null); } @@ -187,6 +186,21 @@ public final Router getChildRouter(@NonNull ViewGroup container) { */ @NonNull public final Router getChildRouter(@NonNull ViewGroup container, @Nullable String tag) { + //noinspection ConstantConditions + return getChildRouter(container, tag, true); + } + + /** + * Retrieves the child {@link Router} for the given container/tag combination. Note that multiple + * routers should not exist in the same container unless a lot of care is taken to maintain order + * between them. Avoid using the same container unless you have a great reason to do so (ex: ViewPagers). + * + * @param container The ViewGroup that hosts the child Router + * @param tag The router's tag + * @param createIfNeeded If true, a router will be created if one does not yet exist. Else false will be returned in this case. + */ + @Nullable + public final Router getChildRouter(@NonNull ViewGroup container, @Nullable String tag, boolean createIfNeeded) { @IdRes final int containerId = container.getId(); ControllerHostedRouter childRouter = null; @@ -198,10 +212,12 @@ public final Router getChildRouter(@NonNull ViewGroup container, @Nullable Strin } if (childRouter == null) { - childRouter = new ControllerHostedRouter(container.getId(), tag); - monitorChildRouter(childRouter); - childRouter.setHost(this, container); - childRouters.add(childRouter); + if (createIfNeeded) { + childRouter = new ControllerHostedRouter(container.getId(), tag); + monitorChildRouter(childRouter); + childRouter.setHost(this, container); + childRouters.add(childRouter); + } } else if (!childRouter.hasHost()) { childRouter.setHost(this, container); monitorChildRouter(childRouter); From 48dc4abcbebdfd55283e3e3a95f5f7a5ff8af19f Mon Sep 17 00:00:00 2001 From: Eric Kuck Date: Tue, 13 Dec 2016 17:09:19 -0600 Subject: [PATCH 24/75] Version bump --- README.md | 12 ++++++------ gradle.properties | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 2047dd86..ce993e52 100644 --- a/README.md +++ b/README.md @@ -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.4' +compile 'com.bluelinelabs:conductor:2.0.5' // If you want the components that go along with // Android's support libraries (currently just a PagerAdapter): -compile 'com.bluelinelabs:conductor-support:2.0.4' +compile 'com.bluelinelabs:conductor-support:2.0.5' // If you want RxJava/RxAndroid lifecycle support: -compile 'com.bluelinelabs:conductor-rxlifecycle:2.0.4' +compile 'com.bluelinelabs:conductor-rxlifecycle:2.0.5' ``` SNAPSHOT: ```gradle -compile 'com.bluelinelabs:conductor:2.0.5-SNAPSHOT' -compile 'com.bluelinelabs:conductor-support:2.0.5-SNAPSHOT' -compile 'com.bluelinelabs:conductor-rxlifecycle:2.0.5-SNAPSHOT' +compile 'com.bluelinelabs:conductor:2.0.6-SNAPSHOT' +compile 'com.bluelinelabs:conductor-support:2.0.6-SNAPSHOT' +compile 'com.bluelinelabs:conductor-rxlifecycle:2.0.6-SNAPSHOT' ``` You also have to add the url to the snapshot repository: diff --git a/gradle.properties b/gradle.properties index 8de7c1a8..1ca257eb 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ -VERSION_NAME=2.0.5-SNAPSHOT +VERSION_NAME=2.0.6-SNAPSHOT VERSION_CODE=2 GROUP=com.bluelinelabs From 553bae0be57e47b01efa21327805e2f3c9f26b00 Mon Sep 17 00:00:00 2001 From: Eric Kuck Date: Wed, 14 Dec 2016 17:53:45 -0600 Subject: [PATCH 25/75] Updated readme to include mention of RxLifecycle2 --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index ce993e52..3fce89df 100644 --- a/README.md +++ b/README.md @@ -26,8 +26,11 @@ compile 'com.bluelinelabs:conductor:2.0.5' // Android's support libraries (currently just a PagerAdapter): compile 'com.bluelinelabs:conductor-support:2.0.5' -// If you want RxJava/RxAndroid lifecycle support: +// If you want RxJava lifecycle support: compile 'com.bluelinelabs:conductor-rxlifecycle:2.0.5' + +// If you want RxJava2 lifecycle support: +compile 'com.bluelinelabs:conductor-rxlifecycle2:2.0.5' ``` SNAPSHOT: @@ -36,6 +39,7 @@ SNAPSHOT: compile 'com.bluelinelabs:conductor:2.0.6-SNAPSHOT' compile 'com.bluelinelabs:conductor-support:2.0.6-SNAPSHOT' compile 'com.bluelinelabs:conductor-rxlifecycle:2.0.6-SNAPSHOT' +compile 'com.bluelinelabs:conductor-rxlifecycle2:2.0.6-SNAPSHOT' ``` You also have to add the url to the snapshot repository: From 01df673a340b015ca5cc50bf0184d17eb0f61356 Mon Sep 17 00:00:00 2001 From: Eric Kuck Date: Wed, 14 Dec 2016 19:41:41 -0600 Subject: [PATCH 26/75] Switched lint checks to the new PSI based api (closes #184) --- .travis.yml | 4 +- build.gradle | 6 +- conductor-lint/build.gradle | 6 ++ .../ControllerChangeHandlerIssueDetector.java | 75 +++++---------- .../lint/ControllerIssueDetector.java | 86 ++++++----------- .../ControllerChangeHandlerDetectorTest.java | 96 +++++++++++++++++++ .../lint/ControllerDetectorTest.java | 96 +++++++++++++++++++ conductor-rxlifecycle/build.gradle | 5 - conductor-rxlifecycle2/build.gradle | 4 - conductor-support/build.gradle | 4 - conductor/build.gradle | 4 - demo/build.gradle | 9 +- dependencies.gradle | 59 +++++++----- 13 files changed, 294 insertions(+), 160 deletions(-) create mode 100644 conductor-lint/src/test/java/com/bluelinelabs/conductor/lint/ControllerChangeHandlerDetectorTest.java create mode 100644 conductor-lint/src/test/java/com/bluelinelabs/conductor/lint/ControllerDetectorTest.java diff --git a/.travis.yml b/.travis.yml index 036aca54..1ba3259c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,8 +3,8 @@ language: android android: components: - tools - - build-tools-23.0.2 - - android-23 + - build-tools-23.0.3 + - android-25 - extra-android-m2repository script: diff --git a/build.gradle b/build.gradle index 6806c9fb..5e003996 100755 --- a/build.gradle +++ b/build.gradle @@ -10,12 +10,8 @@ buildscript { allprojects { repositories { jcenter() - mavenLocal() + mavenCentral() } } -task wrapper(type: Wrapper) { - gradleVersion = '2.10' -} - apply from: rootProject.file('dependencies.gradle') diff --git a/conductor-lint/build.gradle b/conductor-lint/build.gradle index 06ca3154..f333be25 100644 --- a/conductor-lint/build.gradle +++ b/conductor-lint/build.gradle @@ -1,5 +1,8 @@ apply plugin: 'java' +targetCompatibility = JavaVersion.VERSION_1_7 +sourceCompatibility = JavaVersion.VERSION_1_7 + configurations { lintChecks } @@ -8,6 +11,9 @@ dependencies { compile rootProject.ext.lintapi compile rootProject.ext.lintchecks + testCompile rootProject.ext.lint + testCompile rootProject.ext.lintTests + lintChecks files(jar) } diff --git a/conductor-lint/src/main/java/com/bluelinelabs/conductor/lint/ControllerChangeHandlerIssueDetector.java b/conductor-lint/src/main/java/com/bluelinelabs/conductor/lint/ControllerChangeHandlerIssueDetector.java index 8fb48df3..e40e08cb 100644 --- a/conductor-lint/src/main/java/com/bluelinelabs/conductor/lint/ControllerChangeHandlerIssueDetector.java +++ b/conductor-lint/src/main/java/com/bluelinelabs/conductor/lint/ControllerChangeHandlerIssueDetector.java @@ -1,7 +1,6 @@ package com.bluelinelabs.conductor.lint; -import com.android.annotations.NonNull; -import com.android.tools.lint.client.api.JavaParser.ResolvedClass; +import com.android.tools.lint.client.api.JavaEvaluator; import com.android.tools.lint.detector.api.Category; import com.android.tools.lint.detector.api.Detector; import com.android.tools.lint.detector.api.Implementation; @@ -9,21 +8,13 @@ import com.android.tools.lint.detector.api.JavaContext; import com.android.tools.lint.detector.api.Scope; import com.android.tools.lint.detector.api.Severity; -import com.android.tools.lint.detector.api.Speed; +import com.intellij.psi.PsiClass; +import com.intellij.psi.PsiMethod; -import java.lang.reflect.Modifier; import java.util.Collections; import java.util.List; -import lombok.ast.ClassDeclaration; -import lombok.ast.ConstructorDeclaration; -import lombok.ast.Node; -import lombok.ast.NormalTypeBody; -import lombok.ast.StrictListAccessor; -import lombok.ast.TypeMember; -import lombok.ast.VariableDefinition; - -public final class ControllerChangeHandlerIssueDetector extends Detector implements Detector.JavaScanner, Detector.ClassScanner { +public final class ControllerChangeHandlerIssueDetector extends Detector implements Detector.JavaPsiScanner { public static final Issue ISSUE = Issue.create("ValidControllerChangeHandler", "ControllerChangeHandler not instantiatable", @@ -34,67 +25,45 @@ public final class ControllerChangeHandlerIssueDetector extends Detector impleme public ControllerChangeHandlerIssueDetector() { } - @NonNull - @Override - public Speed getSpeed() { - return Speed.FAST; - } - @Override public List applicableSuperClasses() { return Collections.singletonList("com.bluelinelabs.conductor.ControllerChangeHandler"); } @Override - public void checkClass(@NonNull JavaContext context, ClassDeclaration node, - @NonNull Node declarationOrAnonymous, @NonNull ResolvedClass cls) { - - if (node == null) { + public void checkClass(JavaContext context, PsiClass declaration) { + final JavaEvaluator evaluator = context.getEvaluator(); + if (evaluator.isAbstract(declaration)) { return; } - final int flags = node.astModifiers().getEffectiveModifierFlags(); - if ((flags & Modifier.ABSTRACT) != 0) { + if (!evaluator.isPublic(declaration)) { + String message = String.format("This ControllerChangeHandler class should be public (%1$s)", declaration.getQualifiedName()); + context.report(ISSUE, declaration, context.getLocation(declaration), message); return; } - if ((flags & Modifier.PUBLIC) == 0) { - String message = String.format("This ControllerChangeHandler class should be public (%1$s)", cls.getName()); - context.report(ISSUE, node, context.getLocation(node.astName()), message); + if (declaration.getContainingClass() != null && !evaluator.isStatic(declaration)) { + String message = String.format("This ControllerChangeHandler inner class should be static (%1$s)", declaration.getQualifiedName()); + context.report(ISSUE, declaration, context.getLocation(declaration), message); return; } - if (cls.getContainingClass() != null && (flags & Modifier.STATIC) == 0) { - String message = String.format("This ControllerChangeHandler inner class should be static (%1$s)", cls.getName()); - context.report(ISSUE, node, context.getLocation(node.astName()), message); - return; - } - - boolean hasConstructor = false; boolean hasDefaultConstructor = false; - NormalTypeBody body = node.astBody(); - if (body != null) { - for (TypeMember member : body.astMembers()) { - if (member instanceof ConstructorDeclaration) { - hasConstructor = true; - ConstructorDeclaration constructor = (ConstructorDeclaration)member; - - if (constructor.astModifiers().isPublic()) { - StrictListAccessor params = constructor.astParameters(); - if (params.isEmpty()) { - hasDefaultConstructor = true; - break; - } - } + PsiMethod[] constructors = declaration.getConstructors(); + for (PsiMethod constructor : constructors) { + if (evaluator.isPublic(constructor)) { + if (constructor.getParameterList().getParametersCount() == 0) { + hasDefaultConstructor = true; + break; } } } - if (hasConstructor && !hasDefaultConstructor) { + if (constructors.length > 0 && !hasDefaultConstructor) { String message = String.format( - "This ControllerChangeHandler needs to have a public default constructor (`%1$s`)", - cls.getName()); - context.report(ISSUE, node, context.getLocation(node.astName()), message); + "This ControllerChangeHandler needs to have a public default constructor (`%1$s`)", declaration.getQualifiedName()); + context.report(ISSUE, declaration, context.getLocation(declaration), message); } } } \ No newline at end of file diff --git a/conductor-lint/src/main/java/com/bluelinelabs/conductor/lint/ControllerIssueDetector.java b/conductor-lint/src/main/java/com/bluelinelabs/conductor/lint/ControllerIssueDetector.java index 3aba3bbb..afababb6 100644 --- a/conductor-lint/src/main/java/com/bluelinelabs/conductor/lint/ControllerIssueDetector.java +++ b/conductor-lint/src/main/java/com/bluelinelabs/conductor/lint/ControllerIssueDetector.java @@ -1,8 +1,7 @@ package com.bluelinelabs.conductor.lint; import com.android.SdkConstants; -import com.android.annotations.NonNull; -import com.android.tools.lint.client.api.JavaParser.ResolvedClass; +import com.android.tools.lint.client.api.JavaEvaluator; import com.android.tools.lint.detector.api.Category; import com.android.tools.lint.detector.api.Detector; import com.android.tools.lint.detector.api.Implementation; @@ -10,21 +9,14 @@ import com.android.tools.lint.detector.api.JavaContext; import com.android.tools.lint.detector.api.Scope; import com.android.tools.lint.detector.api.Severity; -import com.android.tools.lint.detector.api.Speed; +import com.intellij.psi.PsiClass; +import com.intellij.psi.PsiMethod; +import com.intellij.psi.PsiParameter; -import java.lang.reflect.Modifier; import java.util.Collections; import java.util.List; -import lombok.ast.ClassDeclaration; -import lombok.ast.ConstructorDeclaration; -import lombok.ast.Node; -import lombok.ast.NormalTypeBody; -import lombok.ast.StrictListAccessor; -import lombok.ast.TypeMember; -import lombok.ast.VariableDefinition; - -public final class ControllerIssueDetector extends Detector implements Detector.JavaScanner, Detector.ClassScanner { +public final class ControllerIssueDetector extends Detector implements Detector.JavaPsiScanner { public static final Issue ISSUE = Issue.create("ValidController", "Controller not instantiatable", @@ -35,74 +27,56 @@ public final class ControllerIssueDetector extends Detector implements Detector. public ControllerIssueDetector() { } - @NonNull - @Override - public Speed getSpeed() { - return Speed.FAST; - } - @Override public List applicableSuperClasses() { return Collections.singletonList("com.bluelinelabs.conductor.Controller"); } @Override - public void checkClass(@NonNull JavaContext context, ClassDeclaration node, - @NonNull Node declarationOrAnonymous, @NonNull ResolvedClass cls) { - - if (node == null) { + public void checkClass(JavaContext context, PsiClass declaration) { + final JavaEvaluator evaluator = context.getEvaluator(); + if (evaluator.isAbstract(declaration)) { return; } - final int flags = node.astModifiers().getEffectiveModifierFlags(); - if ((flags & Modifier.ABSTRACT) != 0) { + if (!evaluator.isPublic(declaration)) { + String message = String.format("This Controller class should be public (%1$s)", declaration.getQualifiedName()); + context.report(ISSUE, declaration, context.getLocation(declaration), message); return; } - if ((flags & Modifier.PUBLIC) == 0) { - String message = String.format("This Controller class should be public (%1$s)", cls.getName()); - context.report(ISSUE, node, context.getLocation(node.astName()), message); + if (declaration.getContainingClass() != null && !evaluator.isStatic(declaration)) { + String message = String.format("This Controller inner class should be static (%1$s)", declaration.getQualifiedName()); + context.report(ISSUE, declaration, context.getLocation(declaration), message); return; } - if (cls.getContainingClass() != null && (flags & Modifier.STATIC) == 0) { - String message = String.format("This Controller inner class should be static (%1$s)", cls.getName()); - context.report(ISSUE, node, context.getLocation(node.astName()), message); - return; - } - boolean hasConstructor = false; boolean hasDefaultConstructor = false; boolean hasBundleConstructor = false; - NormalTypeBody body = node.astBody(); - if (body != null) { - for (TypeMember member : body.astMembers()) { - if (member instanceof ConstructorDeclaration) { - hasConstructor = true; - ConstructorDeclaration constructor = (ConstructorDeclaration)member; - - if (constructor.astModifiers().isPublic()) { - StrictListAccessor params = constructor.astParameters(); - if (params.isEmpty()) { - hasDefaultConstructor = true; - break; - } else if (params.size() == 1 && - (params.first().astTypeReference().getTypeName().equals(SdkConstants.CLASS_BUNDLE)) || - params.first().astTypeReference().getTypeName().equals("Bundle")) { - hasBundleConstructor = true; - break; - } - } + PsiMethod[] constructors = declaration.getConstructors(); + for (PsiMethod constructor : constructors) { + if (evaluator.isPublic(constructor)) { + PsiParameter[] parameters = constructor.getParameterList().getParameters(); + + if (parameters.length == 0) { + hasDefaultConstructor = true; + break; + } else if (parameters.length == 1 && + parameters[0].getType().equalsToText(SdkConstants.CLASS_BUNDLE) || + parameters[0].getType().equalsToText("Bundle")) { + hasBundleConstructor = true; + break; } } } - if (hasConstructor && !hasDefaultConstructor && !hasBundleConstructor) { + if (constructors.length > 0 && !hasDefaultConstructor && !hasBundleConstructor) { String message = String.format( "This Controller needs to have either a public default constructor or a" + " public single-argument constructor that takes a Bundle. (`%1$s`)", - cls.getName()); - context.report(ISSUE, node, context.getLocation(node.astName()), message); + declaration.getQualifiedName()); + context.report(ISSUE, declaration, context.getLocation(declaration), message); } } } \ No newline at end of file diff --git a/conductor-lint/src/test/java/com/bluelinelabs/conductor/lint/ControllerChangeHandlerDetectorTest.java b/conductor-lint/src/test/java/com/bluelinelabs/conductor/lint/ControllerChangeHandlerDetectorTest.java new file mode 100644 index 00000000..24969c00 --- /dev/null +++ b/conductor-lint/src/test/java/com/bluelinelabs/conductor/lint/ControllerChangeHandlerDetectorTest.java @@ -0,0 +1,96 @@ +package com.bluelinelabs.conductor.lint; + +import com.android.tools.lint.checks.infrastructure.LintDetectorTest; +import com.android.tools.lint.detector.api.Detector; +import com.android.tools.lint.detector.api.Issue; + +import org.intellij.lang.annotations.Language; + +import java.util.Collections; +import java.util.List; + +import static com.google.common.truth.Truth.assertThat; + +public class ControllerChangeHandlerDetectorTest extends LintDetectorTest { + + private static final String NO_WARNINGS = "No warnings."; + private static final String CONSTRUCTOR = + "src/test/SampleHandler.java:2: Error: This ControllerChangeHandler needs to have a public default constructor (test.SampleHandler) [ValidControllerChangeHandler]\n" + + "public class SampleHandler extends com.bluelinelabs.conductor.ControllerChangeHandler {\n" + + "^\n" + + "1 errors, 0 warnings\n"; + private static final String PRIVATE_CLASS_ERROR = + "src/test/SampleHandler.java:2: Error: This ControllerChangeHandler class should be public (test.SampleHandler) [ValidControllerChangeHandler]\n" + + "private class SampleHandler extends com.bluelinelabs.conductor.ControllerChangeHandler {\n" + + "^\n" + + "1 errors, 0 warnings\n"; + + public void testWithNoConstructor() throws Exception { + @Language("JAVA") String source = "" + + "package test;\n" + + "public class SampleHandler extends com.bluelinelabs.conductor.ControllerChangeHandler {\n" + + "}"; + assertThat(lintProject(java(source))).isEqualTo(NO_WARNINGS); + } + + public void testWithEmptyConstructor() throws Exception { + @Language("JAVA") String source = "" + + "package test;\n" + + "public class SampleHandler extends com.bluelinelabs.conductor.ControllerChangeHandler {\n" + + " public SampleHandler() { }\n" + + "}"; + assertThat(lintProject(java(source))).isEqualTo(NO_WARNINGS); + } + + public void testWithInvalidConstructor() throws Exception { + @Language("JAVA") String source = "" + + "package test;\n" + + "public class SampleHandler extends com.bluelinelabs.conductor.ControllerChangeHandler {\n" + + " public SampleHandler(int number) { }\n" + + "}"; + assertThat(lintProject(java(source))).isEqualTo(CONSTRUCTOR); + } + + public void testWithEmptyAndInvalidConstructor() throws Exception { + @Language("JAVA") String source = "" + + "package test;\n" + + "public class SampleHandler extends com.bluelinelabs.conductor.ControllerChangeHandler {\n" + + " public SampleHandler() { }\n" + + " public SampleHandler(int number) { }\n" + + "}"; + assertThat(lintProject(java(source))).isEqualTo(NO_WARNINGS); + } + + public void testWithPrivateConstructor() throws Exception { + @Language("JAVA") String source = "" + + "package test;\n" + + "public class SampleHandler extends com.bluelinelabs.conductor.ControllerChangeHandler {\n" + + " private SampleHandler() { }\n" + + "}"; + assertThat(lintProject(java(source))).isEqualTo(CONSTRUCTOR); + } + + public void testWithPrivateClass() throws Exception { + @Language("JAVA") String source = "" + + "package test;\n" + + "private class SampleHandler extends com.bluelinelabs.conductor.ControllerChangeHandler {\n" + + " public SampleHandler() { }\n" + + "}"; + assertThat(lintProject(java(source))).isEqualTo(PRIVATE_CLASS_ERROR); + } + + @Override + protected Detector getDetector() { + return new ControllerChangeHandlerIssueDetector(); + } + + @Override + protected List getIssues() { + return Collections.singletonList(ControllerChangeHandlerIssueDetector.ISSUE); + } + + @Override + protected boolean allowCompilationErrors() { + return true; + } +} diff --git a/conductor-lint/src/test/java/com/bluelinelabs/conductor/lint/ControllerDetectorTest.java b/conductor-lint/src/test/java/com/bluelinelabs/conductor/lint/ControllerDetectorTest.java new file mode 100644 index 00000000..b63df209 --- /dev/null +++ b/conductor-lint/src/test/java/com/bluelinelabs/conductor/lint/ControllerDetectorTest.java @@ -0,0 +1,96 @@ +package com.bluelinelabs.conductor.lint; + +import com.android.tools.lint.checks.infrastructure.LintDetectorTest; +import com.android.tools.lint.detector.api.Detector; +import com.android.tools.lint.detector.api.Issue; + +import org.intellij.lang.annotations.Language; + +import java.util.Collections; +import java.util.List; + +import static com.google.common.truth.Truth.assertThat; + +public class ControllerDetectorTest extends LintDetectorTest { + + private static final String NO_WARNINGS = "No warnings."; + private static final String CONSTRUCTOR_ERROR = + "src/test/SampleController.java:2: Error: This Controller needs to have either a public default constructor or a public single-argument constructor that takes a Bundle. (test.SampleController) [ValidController]\n" + + "public class SampleController extends com.bluelinelabs.conductor.Controller {\n" + + "^\n" + + "1 errors, 0 warnings\n"; + private static final String CLASS_ERROR = + "src/test/SampleController.java:2: Error: This Controller class should be public (test.SampleController) [ValidController]\n" + + "private class SampleController extends com.bluelinelabs.conductor.Controller {\n" + + "^\n" + + "1 errors, 0 warnings\n"; + + public void testWithNoConstructor() throws Exception { + @Language("JAVA") String source = "" + + "package test;\n" + + "public class SampleController extends com.bluelinelabs.conductor.Controller {\n" + + "}"; + assertThat(lintProject(java(source))).isEqualTo(NO_WARNINGS); + } + + public void testWithEmptyConstructor() throws Exception { + @Language("JAVA") String source = "" + + "package test;\n" + + "public class SampleController extends com.bluelinelabs.conductor.Controller {\n" + + " public SampleController() { }\n" + + "}"; + assertThat(lintProject(java(source))).isEqualTo(NO_WARNINGS); + } + + public void testWithInvalidConstructor() throws Exception { + @Language("JAVA") String source = "" + + "package test;\n" + + "public class SampleController extends com.bluelinelabs.conductor.Controller {\n" + + " public SampleController(int number) { }\n" + + "}"; + assertThat(lintProject(java(source))).isEqualTo(CONSTRUCTOR_ERROR); + } + + public void testWithEmptyAndInvalidConstructor() throws Exception { + @Language("JAVA") String source = "" + + "package test;\n" + + "public class SampleController extends com.bluelinelabs.conductor.Controller {\n" + + " public SampleController() { }\n" + + " public SampleController(int number) { }\n" + + "}"; + assertThat(lintProject(java(source))).isEqualTo(NO_WARNINGS); + } + + public void testWithPrivateConstructor() throws Exception { + @Language("JAVA") String source = "" + + "package test;\n" + + "public class SampleController extends com.bluelinelabs.conductor.Controller {\n" + + " private SampleController() { }\n" + + "}"; + assertThat(lintProject(java(source))).isEqualTo(CONSTRUCTOR_ERROR); + } + + public void testWithPrivateClass() throws Exception { + @Language("JAVA") String source = "" + + "package test;\n" + + "private class SampleController extends com.bluelinelabs.conductor.Controller {\n" + + " public SampleController() { }\n" + + "}"; + assertThat(lintProject(java(source))).isEqualTo(CLASS_ERROR); + } + + @Override + protected Detector getDetector() { + return new ControllerIssueDetector(); + } + + @Override + protected List getIssues() { + return Collections.singletonList(ControllerIssueDetector.ISSUE); + } + + @Override + protected boolean allowCompilationErrors() { + return true; + } +} diff --git a/conductor-rxlifecycle/build.gradle b/conductor-rxlifecycle/build.gradle index a7f75249..a374d0d5 100755 --- a/conductor-rxlifecycle/build.gradle +++ b/conductor-rxlifecycle/build.gradle @@ -7,10 +7,6 @@ android { compileSdkVersion rootProject.ext.compileSdkVersion buildToolsVersion rootProject.ext.buildToolsVersion - lintOptions { - abortOnError false - } - compileOptions { sourceCompatibility JavaVersion.VERSION_1_7 targetCompatibility JavaVersion.VERSION_1_7 @@ -26,7 +22,6 @@ android { dependencies { compile rootProject.ext.rxJava - compile rootProject.ext.rxAndroid compile rootProject.ext.rxLifecycle compile rootProject.ext.rxLifecycleAndroid diff --git a/conductor-rxlifecycle2/build.gradle b/conductor-rxlifecycle2/build.gradle index e2e6eb4e..8b8582ea 100644 --- a/conductor-rxlifecycle2/build.gradle +++ b/conductor-rxlifecycle2/build.gradle @@ -7,10 +7,6 @@ android { compileSdkVersion rootProject.ext.compileSdkVersion buildToolsVersion rootProject.ext.buildToolsVersion - lintOptions { - abortOnError false - } - compileOptions { sourceCompatibility JavaVersion.VERSION_1_7 targetCompatibility JavaVersion.VERSION_1_7 diff --git a/conductor-support/build.gradle b/conductor-support/build.gradle index 41e7d752..306c88cd 100755 --- a/conductor-support/build.gradle +++ b/conductor-support/build.gradle @@ -7,10 +7,6 @@ android { compileSdkVersion rootProject.ext.compileSdkVersion buildToolsVersion rootProject.ext.buildToolsVersion - lintOptions { - abortOnError false - } - compileOptions { sourceCompatibility JavaVersion.VERSION_1_7 targetCompatibility JavaVersion.VERSION_1_7 diff --git a/conductor/build.gradle b/conductor/build.gradle index f67acee5..a3ce9c01 100755 --- a/conductor/build.gradle +++ b/conductor/build.gradle @@ -14,10 +14,6 @@ android { compileSdkVersion rootProject.ext.compileSdkVersion buildToolsVersion rootProject.ext.buildToolsVersion - lintOptions { - abortOnError false - } - compileOptions { sourceCompatibility JavaVersion.VERSION_1_7 targetCompatibility JavaVersion.VERSION_1_7 diff --git a/demo/build.gradle b/demo/build.gradle index 49cabbd8..e4b44362 100755 --- a/demo/build.gradle +++ b/demo/build.gradle @@ -11,11 +11,12 @@ apply plugin: 'com.android.application' apply plugin: 'com.neenbedankt.android-apt' android { - compileSdkVersion 23 - buildToolsVersion "23.0.2" + compileSdkVersion 25 + buildToolsVersion '24.0.3' lintOptions { - abortOnError false + abortOnError true + ignore 'UnusedResources' } compileOptions { @@ -26,7 +27,7 @@ android { defaultConfig { applicationId "com.bluelinelabs.conductor.demo" minSdkVersion 16 - targetSdkVersion 23 + targetSdkVersion 25 versionCode 1 versionName "1.0.0" } diff --git a/dependencies.gradle b/dependencies.gradle index 4c8cf2b1..502c16d3 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -1,36 +1,49 @@ ext { minSdkVersion = 16 - compileSdkVersion = 23 - targetSdkVersion = 23 - buildToolsVersion = '23.0.2' + compileSdkVersion = 25 + targetSdkVersion = 25 + buildToolsVersion = '24.0.3' - supportV4 = 'com.android.support:support-v4:23.1.1' - supportDesign = 'com.android.support:design:23.1.1' - supportAnnotations = 'com.android.support:support-annotations:23.1.1' - supportAppCompat = 'com.android.support:appcompat-v7:23.1.1' + supportVersion = '23.1.1' + butterknifeVersion = '8.0.1' + picassoVersion = '2.5.2' + leakCanaryVersion = '1.4' + rxJavaVersion = '1.2.0' + rxJava2Version = '2.0.1' + rxLifecycleVersion = '0.8.0' + rxLifecycle2Version = '2.0' + junitVersion = '4.11' + roboelectricVersion = '3.0' + lintVersion = '25.2.0' - butterknife = 'com.jakewharton:butterknife:8.0.1' - butterknifeCompiler = 'com.jakewharton:butterknife-compiler:8.0.1' + supportV4 = "com.android.support:support-v4:$supportVersion" + supportDesign = "com.android.support:design:$supportVersion" + supportAnnotations = "com.android.support:support-annotations:$supportVersion" + supportAppCompat = "com.android.support:appcompat-v7:$supportVersion" - picasso = 'com.squareup.picasso:picasso:2.5.2' + butterknife = "com.jakewharton:butterknife:$butterknifeVersion" + butterknifeCompiler = "com.jakewharton:butterknife-compiler:$butterknifeVersion" - leakCanary = 'com.squareup.leakcanary:leakcanary-android:1.4' - leakCanaryNoOp = 'com.squareup.leakcanary:leakcanary-android-no-op:1.4' + picasso = "com.squareup.picasso:picasso:$picassoVersion" - rxJava = 'io.reactivex:rxjava:1.2.0' - rxJava2 = "io.reactivex.rxjava2:rxjava:2.0.1" + leakCanary = "com.squareup.leakcanary:leakcanary-android:$leakCanaryVersion" + leakCanaryNoOp = "com.squareup.leakcanary:leakcanary-android-no-op:$leakCanaryVersion" - rxAndroid = 'io.reactivex:rxandroid:1.2.1' + rxJava = "io.reactivex:rxjava:$rxJavaVersion" + rxJava2 = "io.reactivex.rxjava2:rxjava:$rxJava2Version" - rxLifecycle = 'com.trello:rxlifecycle:0.8.0' - rxLifecycleAndroid = 'com.trello:rxlifecycle-android:0.8.0' + rxLifecycle = "com.trello:rxlifecycle:$rxLifecycleVersion" + rxLifecycleAndroid = "com.trello:rxlifecycle-android:$rxLifecycleVersion" - rxLifecycle2 = "com.trello.rxlifecycle2:rxlifecycle:2.0" - rxLifecycleAndroid2 = "com.trello.rxlifecycle2:rxlifecycle-android:2.0" + rxLifecycle2 = "com.trello.rxlifecycle2:rxlifecycle:$rxLifecycle2Version" + rxLifecycleAndroid2 = "com.trello.rxlifecycle2:rxlifecycle-android:$rxLifecycle2Version" - junit = 'junit:junit:4.11' - roboelectric = 'org.robolectric:robolectric:3.0' + junit = "junit:junit:$junitVersion" + roboelectric = "org.robolectric:robolectric:$roboelectricVersion" + + lintapi = "com.android.tools.lint:lint-api:$lintVersion" + lintchecks = "com.android.tools.lint:lint-checks:$lintVersion" + lint = "com.android.tools.lint:lint:$lintVersion" + lintTests = "com.android.tools.lint:lint-tests:$lintVersion" - lintapi = 'com.android.tools.lint:lint-api:24.5.0' - lintchecks = 'com.android.tools.lint:lint-checks:24.5.0' } From 09ce640d9c01363cae0a86b67acf532ebd7de5c1 Mon Sep 17 00:00:00 2001 From: Eric Kuck Date: Wed, 14 Dec 2016 19:42:10 -0600 Subject: [PATCH 27/75] Fixes #183 --- .../demo/controllers/ParentController.java | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/demo/src/main/java/com/bluelinelabs/conductor/demo/controllers/ParentController.java b/demo/src/main/java/com/bluelinelabs/conductor/demo/controllers/ParentController.java index 62fdafd3..5a72ad4a 100644 --- a/demo/src/main/java/com/bluelinelabs/conductor/demo/controllers/ParentController.java +++ b/demo/src/main/java/com/bluelinelabs/conductor/demo/controllers/ParentController.java @@ -50,17 +50,19 @@ private void addChild(final int index) { childController.addLifecycleListener(new LifecycleListener() { @Override public void onChangeEnd(@NonNull Controller controller, @NonNull ControllerChangeHandler changeHandler, @NonNull ControllerChangeType changeType) { - if (changeType == ControllerChangeType.PUSH_ENTER && !hasShownAll) { - if (index < NUMBER_OF_CHILDREN - 1) { - addChild(index + 1); - } else { - hasShownAll = true; - } - } else if (changeType == ControllerChangeType.POP_EXIT) { - if (index > 0) { - removeChild(index - 1); - } else { - getRouter().popController(ParentController.this); + if (!isBeingDestroyed()) { + if (changeType == ControllerChangeType.PUSH_ENTER && !hasShownAll) { + if (index < NUMBER_OF_CHILDREN - 1) { + addChild(index + 1); + } else { + hasShownAll = true; + } + } else if (changeType == ControllerChangeType.POP_EXIT) { + if (index > 0) { + removeChild(index - 1); + } else { + getRouter().popController(ParentController.this); + } } } } From 54cdc515571706059c9c8de05dd2751d810cfc0b Mon Sep 17 00:00:00 2001 From: Eric Kuck Date: Wed, 14 Dec 2016 19:48:55 -0600 Subject: [PATCH 28/75] Added license to travis.yml --- .travis.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.travis.yml b/.travis.yml index 1ba3259c..7f30d781 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,6 +6,10 @@ android: - build-tools-23.0.3 - android-25 - extra-android-m2repository + licenses: + - 'android-sdk-preview-license-.+' + - 'android-sdk-license-.+' + - 'google-gdk-license-.+' script: - ./gradlew test From 0ac81767dd0482cc3ce9cf5d2210a06bb63aa536 Mon Sep 17 00:00:00 2001 From: Eric Kuck Date: Wed, 14 Dec 2016 20:05:38 -0600 Subject: [PATCH 29/75] Travis.yml licenses fix --- .travis.yml | 9 +++++---- .../conductor/demo/controllers/TextController.java | 14 ++++---------- .../demo/widget/ElasticDragDismissFrameLayout.java | 2 ++ .../layout-land/controller_master_detail_list.xml | 6 ++++-- demo/src/main/res/layout/controller_pager.xml | 2 ++ .../main/res/layout/controller_transition_demo.xml | 3 +++ .../layout/controller_transition_demo_shared.xml | 3 +++ 7 files changed, 23 insertions(+), 16 deletions(-) diff --git a/.travis.yml b/.travis.yml index 7f30d781..01aadedc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,10 +6,11 @@ android: - build-tools-23.0.3 - android-25 - extra-android-m2repository - licenses: - - 'android-sdk-preview-license-.+' - - 'android-sdk-license-.+' - - 'google-gdk-license-.+' + +licenses: + - 'android-sdk-preview-license-.+' + - 'android-sdk-license-.+' + - 'google-gdk-license-.+' script: - ./gradlew test diff --git a/demo/src/main/java/com/bluelinelabs/conductor/demo/controllers/TextController.java b/demo/src/main/java/com/bluelinelabs/conductor/demo/controllers/TextController.java index f824eea6..fbfc1896 100755 --- a/demo/src/main/java/com/bluelinelabs/conductor/demo/controllers/TextController.java +++ b/demo/src/main/java/com/bluelinelabs/conductor/demo/controllers/TextController.java @@ -1,6 +1,5 @@ package com.bluelinelabs.conductor.demo.controllers; -import android.os.Bundle; import android.support.annotation.NonNull; import android.view.LayoutInflater; import android.view.View; @@ -9,7 +8,6 @@ import com.bluelinelabs.conductor.demo.R; import com.bluelinelabs.conductor.demo.controllers.base.BaseController; -import com.bluelinelabs.conductor.demo.util.BundleBuilder; import butterknife.BindView; @@ -20,14 +18,10 @@ public class TextController extends BaseController { @BindView(R.id.text_view) TextView textView; public TextController(String text) { - this(new BundleBuilder(new Bundle()) - .putString(KEY_TEXT, text) - .build() - ); - } - - public TextController(Bundle args) { - super(args); +// this(new BundleBuilder(new Bundle()) +// .putString(KEY_TEXT, text) +// .build() +// ); } @NonNull diff --git a/demo/src/main/java/com/bluelinelabs/conductor/demo/widget/ElasticDragDismissFrameLayout.java b/demo/src/main/java/com/bluelinelabs/conductor/demo/widget/ElasticDragDismissFrameLayout.java index 65865b8f..c1b1271d 100644 --- a/demo/src/main/java/com/bluelinelabs/conductor/demo/widget/ElasticDragDismissFrameLayout.java +++ b/demo/src/main/java/com/bluelinelabs/conductor/demo/widget/ElasticDragDismissFrameLayout.java @@ -16,6 +16,7 @@ package com.bluelinelabs.conductor.demo.widget; +import android.annotation.TargetApi; import android.content.Context; import android.support.v4.view.NestedScrollingParent; import android.support.v4.view.animation.FastOutSlowInInterpolator; @@ -32,6 +33,7 @@ * Applies an elasticity factor to reduce movement as you approach the given dismiss distance. * Optionally also scales down content during drag. */ +@TargetApi(21) public class ElasticDragDismissFrameLayout extends FrameLayout implements NestedScrollingParent { public static abstract class ElasticDragDismissCallback { diff --git a/demo/src/main/res/layout-land/controller_master_detail_list.xml b/demo/src/main/res/layout-land/controller_master_detail_list.xml index 5c602077..6225634b 100644 --- a/demo/src/main/res/layout-land/controller_master_detail_list.xml +++ b/demo/src/main/res/layout-land/controller_master_detail_list.xml @@ -1,9 +1,11 @@ + android:layout_height="match_parent" + android:orientation="horizontal" + tools:ignore="InconsistentLayout"> @@ -13,6 +14,7 @@ android:elevation="6dp" android:minHeight="?attr/actionBarSize" android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" + tools:targetApi="lollipop" /> \ No newline at end of file diff --git a/demo/src/main/res/layout/controller_transition_demo_shared.xml b/demo/src/main/res/layout/controller_transition_demo_shared.xml index 97faa47e..febeb7dc 100644 --- a/demo/src/main/res/layout/controller_transition_demo_shared.xml +++ b/demo/src/main/res/layout/controller_transition_demo_shared.xml @@ -1,5 +1,6 @@ \ No newline at end of file From 23a4dbbb608a4a7d7715c58b0838fcecce3f1598 Mon Sep 17 00:00:00 2001 From: Eric Kuck Date: Wed, 14 Dec 2016 20:20:04 -0600 Subject: [PATCH 30/75] Travis yml fix --- .travis.yml | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index 01aadedc..c33d08a5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,14 +3,11 @@ language: android android: components: - tools - - build-tools-23.0.3 + - build-tools-24.0.3 - android-25 - extra-android-m2repository - -licenses: - - 'android-sdk-preview-license-.+' - - 'android-sdk-license-.+' - - 'google-gdk-license-.+' + licenses: + - '.+' script: - ./gradlew test From e7c195d9105d457abe3a9bba0cc7f525230233a5 Mon Sep 17 00:00:00 2001 From: Eric Kuck Date: Thu, 15 Dec 2016 15:11:25 -0600 Subject: [PATCH 31/75] Now internally ensures that ControllerChangeHandlers aren't reused, unless they're specifically designed to be safe for reuse Made ControllerChangeHandler's copy() method public Fixes #179 --- .../conductor/ControllerChangeHandler.java | 27 ++++++++++++++----- .../com/bluelinelabs/conductor/Router.java | 13 ++++----- .../AutoTransitionChangeHandler.java | 7 +++++ .../changehandler/FadeChangeHandler.java | 8 ++++++ .../HorizontalChangeHandler.java | 8 ++++++ .../SimpleSwapChangeHandler.java | 6 +++++ .../TransitionChangeHandler.java | 1 + .../TransitionChangeHandlerCompat.java | 9 +++++++ .../changehandler/VerticalChangeHandler.java | 7 +++++ .../internal/NoOpControllerChangeHandler.java | 5 ++++ .../demo/controllers/TextController.java | 14 +++++++--- 11 files changed, 87 insertions(+), 18 deletions(-) diff --git a/conductor/src/main/java/com/bluelinelabs/conductor/ControllerChangeHandler.java b/conductor/src/main/java/com/bluelinelabs/conductor/ControllerChangeHandler.java index f2196ac7..c8538452 100644 --- a/conductor/src/main/java/com/bluelinelabs/conductor/ControllerChangeHandler.java +++ b/conductor/src/main/java/com/bluelinelabs/conductor/ControllerChangeHandler.java @@ -29,6 +29,8 @@ public abstract class ControllerChangeHandler { private boolean forceRemoveViewOnPush; + boolean hasBeenUsed; + /** * Responsible for swapping Views from one Controller to another. * @@ -73,6 +75,16 @@ public void onAbortPush(@NonNull ControllerChangeHandler newHandler, @Nullable C */ public void completeImmediately() { } + /** + * Returns a copy of this ControllerChangeHandler. This method is internally used by the library, so + * ensure it will return an exact copy of your handler if overriding. If not overriding, the handler + * will be saved and restored from the Bundle format. + */ + @NonNull + public ControllerChangeHandler copy() { + return fromBundle(toBundle()); + } + @NonNull final Bundle toBundle() { Bundle bundle = new Bundle(); @@ -85,11 +97,6 @@ final Bundle toBundle() { return bundle; } - @NonNull - final ControllerChangeHandler copy() { - return fromBundle(toBundle()); - } - private void ensureDefaultConstructor() { try { getClass().getConstructor(); @@ -135,7 +142,15 @@ public static void executeChange(@Nullable final Controller to, @Nullable final public static void executeChange(@Nullable final Controller to, @Nullable final Controller from, final boolean isPush, @Nullable final ViewGroup container, @Nullable final ControllerChangeHandler inHandler, @NonNull final List listeners) { if (container != null) { - final ControllerChangeHandler handler = inHandler != null ? inHandler : new SimpleSwapChangeHandler(); + final ControllerChangeHandler handler; + if (inHandler == null) { + handler = new SimpleSwapChangeHandler(); + } else if (inHandler.hasBeenUsed) { + handler = inHandler.copy(); + } else { + handler = inHandler; + } + handler.hasBeenUsed = true; if (isPush && to != null) { inProgressPushHandlers.put(to.getInstanceId(), handler); diff --git a/conductor/src/main/java/com/bluelinelabs/conductor/Router.java b/conductor/src/main/java/com/bluelinelabs/conductor/Router.java index 1375f3f2..f4774845 100755 --- a/conductor/src/main/java/com/bluelinelabs/conductor/Router.java +++ b/conductor/src/main/java/com/bluelinelabs/conductor/Router.java @@ -172,7 +172,7 @@ public void replaceTopController(@NonNull RouterTransaction transaction) { 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()); + performControllerChange(null, visibleTransaction.controller, true, handler); } } } @@ -368,22 +368,19 @@ public void setBackstack(@NonNull List newBackstack, @Nullabl } if (visibleTransactionsChanged) { - ControllerChangeHandler handler = changeHandler != null ? changeHandler : new SimpleSwapChangeHandler(); - Controller rootController = oldVisibleTransactions.size() > 0 ? oldVisibleTransactions.get(0).controller : null; - performControllerChange(newVisibleTransactions.get(0).controller, rootController, true, handler); + performControllerChange(newVisibleTransactions.get(0).controller, rootController, true, changeHandler); for (int i = oldVisibleTransactions.size() - 1; i > 0; i--) { RouterTransaction transaction = oldVisibleTransactions.get(i); - ControllerChangeHandler localHandler = handler.copy(); + ControllerChangeHandler localHandler = changeHandler != null ? changeHandler.copy() : new SimpleSwapChangeHandler(); 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, transaction.pushChangeHandler()); } } } @@ -631,7 +628,7 @@ private void performControllerChange(@Nullable RouterTransaction to, @Nullable R } else if (from != null) { changeHandler = from.popChangeHandler(); } else { - changeHandler = new SimpleSwapChangeHandler(); + changeHandler = null; } Controller toController = to != null ? to.controller : null; diff --git a/conductor/src/main/java/com/bluelinelabs/conductor/changehandler/AutoTransitionChangeHandler.java b/conductor/src/main/java/com/bluelinelabs/conductor/changehandler/AutoTransitionChangeHandler.java index 72a47913..6584b842 100755 --- a/conductor/src/main/java/com/bluelinelabs/conductor/changehandler/AutoTransitionChangeHandler.java +++ b/conductor/src/main/java/com/bluelinelabs/conductor/changehandler/AutoTransitionChangeHandler.java @@ -9,6 +9,8 @@ import android.view.View; import android.view.ViewGroup; +import com.bluelinelabs.conductor.ControllerChangeHandler; + /** * A change handler that will use an AutoTransition. */ @@ -20,4 +22,9 @@ protected Transition getTransition(@NonNull ViewGroup container, @Nullable View return new AutoTransition(); } + @Override @NonNull + public ControllerChangeHandler copy() { + return new AutoTransitionChangeHandler(); + } + } diff --git a/conductor/src/main/java/com/bluelinelabs/conductor/changehandler/FadeChangeHandler.java b/conductor/src/main/java/com/bluelinelabs/conductor/changehandler/FadeChangeHandler.java index beba8665..eabc06a0 100755 --- a/conductor/src/main/java/com/bluelinelabs/conductor/changehandler/FadeChangeHandler.java +++ b/conductor/src/main/java/com/bluelinelabs/conductor/changehandler/FadeChangeHandler.java @@ -8,6 +8,8 @@ import android.view.View; import android.view.ViewGroup; +import com.bluelinelabs.conductor.ControllerChangeHandler; + /** * An {@link AnimatorChangeHandler} that will cross fade two views */ @@ -45,4 +47,10 @@ protected Animator getAnimator(@NonNull ViewGroup container, @Nullable View from protected void resetFromView(@NonNull View from) { from.setAlpha(1); } + + @Override @NonNull + public ControllerChangeHandler copy() { + return new FadeChangeHandler(getAnimationDuration(), removesFromViewOnPush()); + } + } diff --git a/conductor/src/main/java/com/bluelinelabs/conductor/changehandler/HorizontalChangeHandler.java b/conductor/src/main/java/com/bluelinelabs/conductor/changehandler/HorizontalChangeHandler.java index 06f1d438..c5099de0 100755 --- a/conductor/src/main/java/com/bluelinelabs/conductor/changehandler/HorizontalChangeHandler.java +++ b/conductor/src/main/java/com/bluelinelabs/conductor/changehandler/HorizontalChangeHandler.java @@ -8,6 +8,8 @@ import android.view.View; import android.view.ViewGroup; +import com.bluelinelabs.conductor.ControllerChangeHandler; + /** * An {@link AnimatorChangeHandler} that will slide the views left or right, depending on if it's a push or pop. */ @@ -54,4 +56,10 @@ protected Animator getAnimator(@NonNull ViewGroup container, @Nullable View from protected void resetFromView(@NonNull View from) { from.setTranslationX(0); } + + @Override @NonNull + public ControllerChangeHandler copy() { + return new HorizontalChangeHandler(getAnimationDuration(), removesFromViewOnPush()); + } + } diff --git a/conductor/src/main/java/com/bluelinelabs/conductor/changehandler/SimpleSwapChangeHandler.java b/conductor/src/main/java/com/bluelinelabs/conductor/changehandler/SimpleSwapChangeHandler.java index b39a5b38..7a27f1d9 100755 --- a/conductor/src/main/java/com/bluelinelabs/conductor/changehandler/SimpleSwapChangeHandler.java +++ b/conductor/src/main/java/com/bluelinelabs/conductor/changehandler/SimpleSwapChangeHandler.java @@ -102,4 +102,10 @@ public void onViewAttachedToWindow(@NonNull View v) { @Override public void onViewDetachedFromWindow(@NonNull View v) { } + + @Override @NonNull + public ControllerChangeHandler copy() { + return new SimpleSwapChangeHandler(removesFromViewOnPush()); + } + } diff --git a/conductor/src/main/java/com/bluelinelabs/conductor/changehandler/TransitionChangeHandler.java b/conductor/src/main/java/com/bluelinelabs/conductor/changehandler/TransitionChangeHandler.java index ebdb416a..021179ce 100644 --- a/conductor/src/main/java/com/bluelinelabs/conductor/changehandler/TransitionChangeHandler.java +++ b/conductor/src/main/java/com/bluelinelabs/conductor/changehandler/TransitionChangeHandler.java @@ -81,4 +81,5 @@ public void onTransitionResume(Transition transition) { } public final boolean removesFromViewOnPush() { return true; } + } diff --git a/conductor/src/main/java/com/bluelinelabs/conductor/changehandler/TransitionChangeHandlerCompat.java b/conductor/src/main/java/com/bluelinelabs/conductor/changehandler/TransitionChangeHandlerCompat.java index 6a00d179..07338e97 100644 --- a/conductor/src/main/java/com/bluelinelabs/conductor/changehandler/TransitionChangeHandlerCompat.java +++ b/conductor/src/main/java/com/bluelinelabs/conductor/changehandler/TransitionChangeHandlerCompat.java @@ -69,4 +69,13 @@ public boolean removesFromViewOnPush() { return changeHandler.removesFromViewOnPush(); } + @Override @NonNull + public ControllerChangeHandler copy() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + return new TransitionChangeHandlerCompat((TransitionChangeHandler)changeHandler.copy(), null); + } else { + return new TransitionChangeHandlerCompat(null, changeHandler.copy()); + } + } + } diff --git a/conductor/src/main/java/com/bluelinelabs/conductor/changehandler/VerticalChangeHandler.java b/conductor/src/main/java/com/bluelinelabs/conductor/changehandler/VerticalChangeHandler.java index 7322858b..fd937c2e 100755 --- a/conductor/src/main/java/com/bluelinelabs/conductor/changehandler/VerticalChangeHandler.java +++ b/conductor/src/main/java/com/bluelinelabs/conductor/changehandler/VerticalChangeHandler.java @@ -8,6 +8,8 @@ import android.view.View; import android.view.ViewGroup; +import com.bluelinelabs.conductor.ControllerChangeHandler; + import java.util.ArrayList; import java.util.List; @@ -49,4 +51,9 @@ protected Animator getAnimator(@NonNull ViewGroup container, @Nullable View from @Override protected void resetFromView(@NonNull View from) { } + @Override @NonNull + public ControllerChangeHandler copy() { + return new VerticalChangeHandler(getAnimationDuration(), removesFromViewOnPush()); + } + } diff --git a/conductor/src/main/java/com/bluelinelabs/conductor/internal/NoOpControllerChangeHandler.java b/conductor/src/main/java/com/bluelinelabs/conductor/internal/NoOpControllerChangeHandler.java index 77d7fd44..b429ac6c 100644 --- a/conductor/src/main/java/com/bluelinelabs/conductor/internal/NoOpControllerChangeHandler.java +++ b/conductor/src/main/java/com/bluelinelabs/conductor/internal/NoOpControllerChangeHandler.java @@ -14,4 +14,9 @@ public void performChange(@NonNull ViewGroup container, @Nullable View from, @Nu changeListener.onChangeCompleted(); } + @NonNull + @Override + public ControllerChangeHandler copy() { + return new NoOpControllerChangeHandler(); + } } \ No newline at end of file diff --git a/demo/src/main/java/com/bluelinelabs/conductor/demo/controllers/TextController.java b/demo/src/main/java/com/bluelinelabs/conductor/demo/controllers/TextController.java index fbfc1896..f824eea6 100755 --- a/demo/src/main/java/com/bluelinelabs/conductor/demo/controllers/TextController.java +++ b/demo/src/main/java/com/bluelinelabs/conductor/demo/controllers/TextController.java @@ -1,5 +1,6 @@ package com.bluelinelabs.conductor.demo.controllers; +import android.os.Bundle; import android.support.annotation.NonNull; import android.view.LayoutInflater; import android.view.View; @@ -8,6 +9,7 @@ import com.bluelinelabs.conductor.demo.R; import com.bluelinelabs.conductor.demo.controllers.base.BaseController; +import com.bluelinelabs.conductor.demo.util.BundleBuilder; import butterknife.BindView; @@ -18,10 +20,14 @@ public class TextController extends BaseController { @BindView(R.id.text_view) TextView textView; public TextController(String text) { -// this(new BundleBuilder(new Bundle()) -// .putString(KEY_TEXT, text) -// .build() -// ); + this(new BundleBuilder(new Bundle()) + .putString(KEY_TEXT, text) + .build() + ); + } + + public TextController(Bundle args) { + super(args); } @NonNull From 44ed19858bae8424e2936f3ba68e16f8a47b71d2 Mon Sep 17 00:00:00 2001 From: Eric Kuck Date: Thu, 15 Dec 2016 15:22:26 -0600 Subject: [PATCH 32/75] Fixes #185 --- .../src/main/java/com/bluelinelabs/conductor/Controller.java | 1 + 1 file changed, 1 insertion(+) diff --git a/conductor/src/main/java/com/bluelinelabs/conductor/Controller.java b/conductor/src/main/java/com/bluelinelabs/conductor/Controller.java index 851a7ccb..e4da2655 100755 --- a/conductor/src/main/java/com/bluelinelabs/conductor/Controller.java +++ b/conductor/src/main/java/com/bluelinelabs/conductor/Controller.java @@ -751,6 +751,7 @@ final void activityResumed(@NonNull Activity activity) { attach(view); } else if (attached) { needsAttach = false; + hasSavedViewState = false; } onActivityResumed(activity); From d15f2b68ab8f05e20712252eae93561b8036642c Mon Sep 17 00:00:00 2001 From: Eric Kuck Date: Fri, 16 Dec 2016 11:35:45 -0600 Subject: [PATCH 33/75] Added missing files from e7c195d9105d457abe3a9bba0cc7f525230233a5 --- .../conductor/ControllerChangeHandler.java | 15 ++++++++++++--- .../changehandler/SimpleSwapChangeHandler.java | 4 ++++ .../internal/NoOpControllerChangeHandler.java | 5 +++++ .../bluelinelabs/conductor/MockChangeHandler.java | 12 +++++++++++- 4 files changed, 32 insertions(+), 4 deletions(-) diff --git a/conductor/src/main/java/com/bluelinelabs/conductor/ControllerChangeHandler.java b/conductor/src/main/java/com/bluelinelabs/conductor/ControllerChangeHandler.java index c8538452..a30b40cb 100644 --- a/conductor/src/main/java/com/bluelinelabs/conductor/ControllerChangeHandler.java +++ b/conductor/src/main/java/com/bluelinelabs/conductor/ControllerChangeHandler.java @@ -28,8 +28,7 @@ public abstract class ControllerChangeHandler { private static final Map inProgressPushHandlers = new HashMap<>(); private boolean forceRemoveViewOnPush; - - boolean hasBeenUsed; + private boolean hasBeenUsed; /** * Responsible for swapping Views from one Controller to another. @@ -85,6 +84,16 @@ public ControllerChangeHandler copy() { return fromBundle(toBundle()); } + /** + * Returns whether or not this is a reusable ControllerChangeHandler. Defaults to false and should + * ONLY be overridden if there are absolutely no side effects to using this handler more than once. + * In the case that a handler is not reusable, it will be copied using the {@link #copy()} method + * prior to use. + */ + public boolean isReusable() { + return false; + } + @NonNull final Bundle toBundle() { Bundle bundle = new Bundle(); @@ -145,7 +154,7 @@ public static void executeChange(@Nullable final Controller to, @Nullable final final ControllerChangeHandler handler; if (inHandler == null) { handler = new SimpleSwapChangeHandler(); - } else if (inHandler.hasBeenUsed) { + } else if (inHandler.hasBeenUsed && !inHandler.isReusable()) { handler = inHandler.copy(); } else { handler = inHandler; diff --git a/conductor/src/main/java/com/bluelinelabs/conductor/changehandler/SimpleSwapChangeHandler.java b/conductor/src/main/java/com/bluelinelabs/conductor/changehandler/SimpleSwapChangeHandler.java index 7a27f1d9..5d845511 100755 --- a/conductor/src/main/java/com/bluelinelabs/conductor/changehandler/SimpleSwapChangeHandler.java +++ b/conductor/src/main/java/com/bluelinelabs/conductor/changehandler/SimpleSwapChangeHandler.java @@ -108,4 +108,8 @@ public ControllerChangeHandler copy() { return new SimpleSwapChangeHandler(removesFromViewOnPush()); } + @Override + public boolean isReusable() { + return true; + } } diff --git a/conductor/src/main/java/com/bluelinelabs/conductor/internal/NoOpControllerChangeHandler.java b/conductor/src/main/java/com/bluelinelabs/conductor/internal/NoOpControllerChangeHandler.java index b429ac6c..838174f1 100644 --- a/conductor/src/main/java/com/bluelinelabs/conductor/internal/NoOpControllerChangeHandler.java +++ b/conductor/src/main/java/com/bluelinelabs/conductor/internal/NoOpControllerChangeHandler.java @@ -19,4 +19,9 @@ public void performChange(@NonNull ViewGroup container, @Nullable View from, @Nu public ControllerChangeHandler copy() { return new NoOpControllerChangeHandler(); } + + @Override + public boolean isReusable() { + return true; + } } \ No newline at end of file diff --git a/conductor/src/test/java/com/bluelinelabs/conductor/MockChangeHandler.java b/conductor/src/test/java/com/bluelinelabs/conductor/MockChangeHandler.java index 703bf7e5..2e652abd 100644 --- a/conductor/src/test/java/com/bluelinelabs/conductor/MockChangeHandler.java +++ b/conductor/src/test/java/com/bluelinelabs/conductor/MockChangeHandler.java @@ -1,6 +1,5 @@ package com.bluelinelabs.conductor; - import android.os.Bundle; import android.support.annotation.NonNull; import android.view.View; @@ -82,4 +81,15 @@ public void restoreFromBundle(@NonNull Bundle bundle) { super.restoreFromBundle(bundle); removesFromViewOnPush = bundle.getBoolean(KEY_REMOVES_FROM_VIEW_ON_PUSH); } + + @NonNull + @Override + public ControllerChangeHandler copy() { + return new MockChangeHandler(removesFromViewOnPush, listener); + } + + @Override + public boolean isReusable() { + return true; + } } From 27f5275172da2dd03201ea57ce988e0d80bf00a2 Mon Sep 17 00:00:00 2001 From: Eric Kuck Date: Mon, 26 Dec 2016 15:25:16 -0600 Subject: [PATCH 34/75] Now throws an exception when a destroyed controller is pushed in order to make it more obvious that controllers can not be reused. --- .../com/bluelinelabs/conductor/ControllerChangeHandler.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/conductor/src/main/java/com/bluelinelabs/conductor/ControllerChangeHandler.java b/conductor/src/main/java/com/bluelinelabs/conductor/ControllerChangeHandler.java index a30b40cb..3e99c931 100644 --- a/conductor/src/main/java/com/bluelinelabs/conductor/ControllerChangeHandler.java +++ b/conductor/src/main/java/com/bluelinelabs/conductor/ControllerChangeHandler.java @@ -150,6 +150,10 @@ public static void executeChange(@Nullable final Controller to, @Nullable final } public static void executeChange(@Nullable final Controller to, @Nullable final Controller from, final boolean isPush, @Nullable final ViewGroup container, @Nullable final ControllerChangeHandler inHandler, @NonNull final List listeners) { + if (isPush && to != null && to.isDestroyed()) { + throw new IllegalStateException("Trying to push a controller that has already been destroyed. (" + to.getClass().getSimpleName() + ")"); + } + if (container != null) { final ControllerChangeHandler handler; if (inHandler == null) { From e390261b53ae819a56cea1dfa4d6aa3b4712d156 Mon Sep 17 00:00:00 2001 From: Eric Kuck Date: Sun, 15 Jan 2017 17:33:40 -0600 Subject: [PATCH 35/75] Fixes #197 --- demo/src/main/res/layout/controller_drag_dismiss.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/demo/src/main/res/layout/controller_drag_dismiss.xml b/demo/src/main/res/layout/controller_drag_dismiss.xml index ca75128b..fd431f53 100644 --- a/demo/src/main/res/layout/controller_drag_dismiss.xml +++ b/demo/src/main/res/layout/controller_drag_dismiss.xml @@ -17,6 +17,7 @@ android:textSize="22sp" android:padding="16dp" android:text="@string/drag_to_dismiss" + android:clickable="true" /> Date: Tue, 17 Jan 2017 16:45:56 -0600 Subject: [PATCH 36/75] Fixes #199 --- .../bluelinelabs/conductor/Controller.java | 4 +- .../conductor/ControllerHostedRouter.java | 4 +- .../com/bluelinelabs/conductor/Router.java | 4 +- .../conductor/ControllerLifecycleTests.java | 18 ++++--- .../conductor/MockChangeHandler.java | 18 ++++--- .../conductor/ReattachCaseTests.java | 52 +++++++++---------- .../bluelinelabs/conductor/RouterTests.java | 10 ++-- .../conductor/TargetControllerTests.java | 24 ++++----- 8 files changed, 71 insertions(+), 63 deletions(-) diff --git a/conductor/src/main/java/com/bluelinelabs/conductor/Controller.java b/conductor/src/main/java/com/bluelinelabs/conductor/Controller.java index e4da2655..5c70d68f 100755 --- a/conductor/src/main/java/com/bluelinelabs/conductor/Controller.java +++ b/conductor/src/main/java/com/bluelinelabs/conductor/Controller.java @@ -229,7 +229,7 @@ public final Router getChildRouter(@NonNull ViewGroup container, @Nullable Strin public final void removeChildRouter(@NonNull Router childRouter) { if ((childRouter instanceof ControllerHostedRouter) && childRouters.remove(childRouter)) { - childRouter.destroy(); + childRouter.destroy(true); } } @@ -969,7 +969,7 @@ private void destroy(boolean removeViews) { } for (ControllerHostedRouter childRouter : childRouters) { - childRouter.destroy(); + childRouter.destroy(false); } if (!attached) { diff --git a/conductor/src/main/java/com/bluelinelabs/conductor/ControllerHostedRouter.java b/conductor/src/main/java/com/bluelinelabs/conductor/ControllerHostedRouter.java index 0b4e3283..7a57ddf7 100644 --- a/conductor/src/main/java/com/bluelinelabs/conductor/ControllerHostedRouter.java +++ b/conductor/src/main/java/com/bluelinelabs/conductor/ControllerHostedRouter.java @@ -72,9 +72,9 @@ final void setDetachFrozen(boolean frozen) { } @Override - void destroy() { + void destroy(boolean popViews) { setDetachFrozen(false); - super.destroy(); + super.destroy(popViews); } @Override @Nullable diff --git a/conductor/src/main/java/com/bluelinelabs/conductor/Router.java b/conductor/src/main/java/com/bluelinelabs/conductor/Router.java index f4774845..18a3946e 100755 --- a/conductor/src/main/java/com/bluelinelabs/conductor/Router.java +++ b/conductor/src/main/java/com/bluelinelabs/conductor/Router.java @@ -185,11 +185,11 @@ public void replaceTopController(@NonNull RouterTransaction transaction) { performControllerChange(transaction.pushChangeHandler(handler), topTransaction, true); } - void destroy() { + void destroy(boolean popViews) { popsLastView = true; List poppedControllers = backstack.popAll(); - if (poppedControllers.size() > 0) { + if (popViews && poppedControllers.size() > 0) { trackDestroyingControllers(poppedControllers); performControllerChange(null, poppedControllers.get(0).controller, false, poppedControllers.get(0).popChangeHandler()); diff --git a/conductor/src/test/java/com/bluelinelabs/conductor/ControllerLifecycleTests.java b/conductor/src/test/java/com/bluelinelabs/conductor/ControllerLifecycleTests.java index e8e3ceaa..fd4624b3 100644 --- a/conductor/src/test/java/com/bluelinelabs/conductor/ControllerLifecycleTests.java +++ b/conductor/src/test/java/com/bluelinelabs/conductor/ControllerLifecycleTests.java @@ -383,8 +383,8 @@ public void postDestroy(@NonNull Controller controller) { }); router.pushController(RouterTransaction.with(testController) - .pushChangeHandler(new MockChangeHandler()) - .popChangeHandler(new MockChangeHandler())); + .pushChangeHandler(MockChangeHandler.defaultHandler()) + .popChangeHandler(MockChangeHandler.defaultHandler())); router.popController(testController); @@ -399,7 +399,7 @@ public void postDestroy(@NonNull Controller controller) { public void testChildLifecycle() { Controller parent = new TestController(); router.pushController(RouterTransaction.with(parent) - .pushChangeHandler(new MockChangeHandler())); + .pushChangeHandler(MockChangeHandler.defaultHandler())); TestController child = new TestController(); attachLifecycleListener(child); @@ -425,8 +425,8 @@ public void testChildLifecycle() { public void testChildLifecycle2() { Controller parent = new TestController(); router.pushController(RouterTransaction.with(parent) - .pushChangeHandler(new MockChangeHandler()) - .popChangeHandler(new MockChangeHandler())); + .pushChangeHandler(MockChangeHandler.defaultHandler()) + .popChangeHandler(MockChangeHandler.defaultHandler())); TestController child = new TestController(); attachLifecycleListener(child); @@ -445,11 +445,15 @@ public void testChildLifecycle2() { router.popCurrentController(); + expectedCallState.detachCalls++; + expectedCallState.destroyViewCalls++; + expectedCallState.destroyCalls++; + assertCalls(expectedCallState, child); } private MockChangeHandler getPushHandler(final CallState expectedCallState, final TestController controller) { - return new MockChangeHandler(new ChangeHandlerListener() { + return MockChangeHandler.listeningChangeHandler(new ChangeHandlerListener() { @Override void willStartChange() { expectedCallState.changeStartCalls++; @@ -472,7 +476,7 @@ void didEndChange() { } private MockChangeHandler getPopHandler(final CallState expectedCallState, final TestController controller) { - return new MockChangeHandler(new ChangeHandlerListener() { + return MockChangeHandler.listeningChangeHandler(new ChangeHandlerListener() { @Override void willStartChange() { expectedCallState.changeStartCalls++; diff --git a/conductor/src/test/java/com/bluelinelabs/conductor/MockChangeHandler.java b/conductor/src/test/java/com/bluelinelabs/conductor/MockChangeHandler.java index 2e652abd..e2b90993 100644 --- a/conductor/src/test/java/com/bluelinelabs/conductor/MockChangeHandler.java +++ b/conductor/src/test/java/com/bluelinelabs/conductor/MockChangeHandler.java @@ -18,19 +18,23 @@ void didEndChange() { } final ChangeHandlerListener listener; boolean removesFromViewOnPush; - public MockChangeHandler() { - this(true, null); + public static MockChangeHandler defaultHandler() { + return new MockChangeHandler(true, null); + } + + public static MockChangeHandler noRemoveViewOnPushHandler() { + return new MockChangeHandler(false, null); } - public MockChangeHandler(boolean removesViewOnPush) { - this(removesViewOnPush, null); + public static MockChangeHandler listeningChangeHandler(@NonNull ChangeHandlerListener listener) { + return new MockChangeHandler(true , listener); } - public MockChangeHandler(@NonNull ChangeHandlerListener listener) { - this(true, listener); + public MockChangeHandler() { + listener = null; } - public MockChangeHandler(boolean removesFromViewOnPush, ChangeHandlerListener listener) { + private MockChangeHandler(boolean removesFromViewOnPush, ChangeHandlerListener listener) { this.removesFromViewOnPush = removesFromViewOnPush; if (listener == null) { diff --git a/conductor/src/test/java/com/bluelinelabs/conductor/ReattachCaseTests.java b/conductor/src/test/java/com/bluelinelabs/conductor/ReattachCaseTests.java index ef68c562..94ce6876 100644 --- a/conductor/src/test/java/com/bluelinelabs/conductor/ReattachCaseTests.java +++ b/conductor/src/test/java/com/bluelinelabs/conductor/ReattachCaseTests.java @@ -36,8 +36,8 @@ public void testNeedsAttachingOnPauseAndOrientation() { final TestController controllerB = new TestController(); router.pushController(RouterTransaction.with(controllerA) - .pushChangeHandler(new MockChangeHandler()) - .popChangeHandler(new MockChangeHandler())); + .pushChangeHandler(MockChangeHandler.defaultHandler()) + .popChangeHandler(MockChangeHandler.defaultHandler())); Assert.assertTrue(controllerA.isAttached()); Assert.assertFalse(controllerB.isAttached()); @@ -48,8 +48,8 @@ public void testNeedsAttachingOnPauseAndOrientation() { Assert.assertFalse(controllerB.isAttached()); router.pushController(RouterTransaction.with(controllerB) - .pushChangeHandler(new MockChangeHandler()) - .popChangeHandler(new MockChangeHandler())); + .pushChangeHandler(MockChangeHandler.defaultHandler()) + .popChangeHandler(MockChangeHandler.defaultHandler())); Assert.assertFalse(controllerA.isAttached()); Assert.assertTrue(controllerB.isAttached()); @@ -68,13 +68,13 @@ public void testChildNeedsAttachOnPauseAndOrientation() { final TestController controllerB = new TestController(); router.pushController(RouterTransaction.with(controllerA) - .pushChangeHandler(new MockChangeHandler()) - .popChangeHandler(new MockChangeHandler())); + .pushChangeHandler(MockChangeHandler.defaultHandler()) + .popChangeHandler(MockChangeHandler.defaultHandler())); Router childRouter = controllerA.getChildRouter((ViewGroup)controllerA.getView().findViewById(TestController.VIEW_ID)); childRouter.pushController(RouterTransaction.with(childController) - .pushChangeHandler(new MockChangeHandler()) - .popChangeHandler(new MockChangeHandler())); + .pushChangeHandler(MockChangeHandler.defaultHandler()) + .popChangeHandler(MockChangeHandler.defaultHandler())); Assert.assertTrue(controllerA.isAttached()); Assert.assertTrue(childController.isAttached()); @@ -87,8 +87,8 @@ public void testChildNeedsAttachOnPauseAndOrientation() { Assert.assertFalse(controllerB.isAttached()); router.pushController(RouterTransaction.with(controllerB) - .pushChangeHandler(new MockChangeHandler()) - .popChangeHandler(new MockChangeHandler())); + .pushChangeHandler(MockChangeHandler.defaultHandler()) + .popChangeHandler(MockChangeHandler.defaultHandler())); Assert.assertFalse(controllerA.isAttached()); Assert.assertFalse(childController.isAttached()); @@ -110,22 +110,22 @@ public void testChildHandleBackOnOrientation() { final TestController childController = new TestController(); router.pushController(RouterTransaction.with(controllerA) - .pushChangeHandler(new MockChangeHandler()) - .popChangeHandler(new MockChangeHandler())); + .pushChangeHandler(MockChangeHandler.defaultHandler()) + .popChangeHandler(MockChangeHandler.defaultHandler())); Assert.assertTrue(controllerA.isAttached()); Assert.assertFalse(controllerB.isAttached()); Assert.assertFalse(childController.isAttached()); router.pushController(RouterTransaction.with(controllerB) - .pushChangeHandler(new MockChangeHandler()) - .popChangeHandler(new MockChangeHandler())); + .pushChangeHandler(MockChangeHandler.defaultHandler()) + .popChangeHandler(MockChangeHandler.defaultHandler())); Router childRouter = controllerB.getChildRouter((ViewGroup)controllerB.getView().findViewById(TestController.VIEW_ID)); childRouter.setPopsLastView(true); childRouter.pushController(RouterTransaction.with(childController) - .pushChangeHandler(new MockChangeHandler()) - .popChangeHandler(new MockChangeHandler())); + .pushChangeHandler(MockChangeHandler.defaultHandler()) + .popChangeHandler(MockChangeHandler.defaultHandler())); Assert.assertFalse(controllerA.isAttached()); Assert.assertTrue(controllerB.isAttached()); @@ -159,22 +159,22 @@ public void testReusedChildRouterHandleBackOnOrientation() { TestController childController = new TestController(); router.pushController(RouterTransaction.with(controllerA) - .pushChangeHandler(new MockChangeHandler()) - .popChangeHandler(new MockChangeHandler())); + .pushChangeHandler(MockChangeHandler.defaultHandler()) + .popChangeHandler(MockChangeHandler.defaultHandler())); Assert.assertTrue(controllerA.isAttached()); Assert.assertFalse(controllerB.isAttached()); Assert.assertFalse(childController.isAttached()); router.pushController(RouterTransaction.with(controllerB) - .pushChangeHandler(new MockChangeHandler()) - .popChangeHandler(new MockChangeHandler())); + .pushChangeHandler(MockChangeHandler.defaultHandler()) + .popChangeHandler(MockChangeHandler.defaultHandler())); Router childRouter = controllerB.getChildRouter((ViewGroup)controllerB.getView().findViewById(TestController.VIEW_ID)); childRouter.setPopsLastView(true); childRouter.pushController(RouterTransaction.with(childController) - .pushChangeHandler(new MockChangeHandler()) - .popChangeHandler(new MockChangeHandler())); + .pushChangeHandler(MockChangeHandler.defaultHandler()) + .popChangeHandler(MockChangeHandler.defaultHandler())); Assert.assertFalse(controllerA.isAttached()); Assert.assertTrue(controllerB.isAttached()); @@ -188,8 +188,8 @@ public void testReusedChildRouterHandleBackOnOrientation() { childController = new TestController(); childRouter.pushController(RouterTransaction.with(childController) - .pushChangeHandler(new MockChangeHandler()) - .popChangeHandler(new MockChangeHandler())); + .pushChangeHandler(MockChangeHandler.defaultHandler()) + .popChangeHandler(MockChangeHandler.defaultHandler())); Assert.assertFalse(controllerA.isAttached()); Assert.assertTrue(controllerB.isAttached()); @@ -206,8 +206,8 @@ public void testReusedChildRouterHandleBackOnOrientation() { childController = new TestController(); childRouter.pushController(RouterTransaction.with(childController) - .pushChangeHandler(new MockChangeHandler()) - .popChangeHandler(new MockChangeHandler())); + .pushChangeHandler(MockChangeHandler.defaultHandler()) + .popChangeHandler(MockChangeHandler.defaultHandler())); Assert.assertFalse(controllerA.isAttached()); Assert.assertTrue(controllerB.isAttached()); diff --git a/conductor/src/test/java/com/bluelinelabs/conductor/RouterTests.java b/conductor/src/test/java/com/bluelinelabs/conductor/RouterTests.java index 4195b340..cce117e5 100644 --- a/conductor/src/test/java/com/bluelinelabs/conductor/RouterTests.java +++ b/conductor/src/test/java/com/bluelinelabs/conductor/RouterTests.java @@ -222,7 +222,7 @@ public void testNewSetBackstack() { @Test public void testNewSetBackstackWithNoRemoveViewOnPush() { RouterTransaction oldRootTransaction = RouterTransaction.with(new TestController()); - RouterTransaction oldTopTransaction = RouterTransaction.with(new TestController()).pushChangeHandler(new MockChangeHandler(false)); + RouterTransaction oldTopTransaction = RouterTransaction.with(new TestController()).pushChangeHandler(MockChangeHandler.noRemoveViewOnPushHandler()); router.setRoot(oldRootTransaction); router.pushController(oldTopTransaction); @@ -232,8 +232,8 @@ public void testNewSetBackstackWithNoRemoveViewOnPush() { 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)); + RouterTransaction middleTransaction = RouterTransaction.with(new TestController()).pushChangeHandler(MockChangeHandler.noRemoveViewOnPushHandler()); + RouterTransaction topTransaction = RouterTransaction.with(new TestController()).pushChangeHandler(MockChangeHandler.noRemoveViewOnPushHandler()); List backstack = new ArrayList<>(); backstack.add(rootTransaction); @@ -286,7 +286,7 @@ public void testReplaceTopController() { @Test public void testReplaceTopControllerWithNoRemoveViewOnPush() { RouterTransaction rootTransaction = RouterTransaction.with(new TestController()); - RouterTransaction topTransaction = RouterTransaction.with(new TestController()).pushChangeHandler(new MockChangeHandler(false)); + RouterTransaction topTransaction = RouterTransaction.with(new TestController()).pushChangeHandler(MockChangeHandler.noRemoveViewOnPushHandler()); List backstack = new ArrayList<>(); backstack.add(rootTransaction); @@ -303,7 +303,7 @@ public void testReplaceTopControllerWithNoRemoveViewOnPush() { Assert.assertEquals(rootTransaction, fetchedBackstack.get(0)); Assert.assertEquals(topTransaction, fetchedBackstack.get(1)); - RouterTransaction newTopTransaction = RouterTransaction.with(new TestController()).pushChangeHandler(new MockChangeHandler(false)); + RouterTransaction newTopTransaction = RouterTransaction.with(new TestController()).pushChangeHandler(MockChangeHandler.noRemoveViewOnPushHandler()); router.replaceTopController(newTopTransaction); newTopTransaction.pushChangeHandler().completeImmediately(); diff --git a/conductor/src/test/java/com/bluelinelabs/conductor/TargetControllerTests.java b/conductor/src/test/java/com/bluelinelabs/conductor/TargetControllerTests.java index 3e6e1881..16cc9ab2 100644 --- a/conductor/src/test/java/com/bluelinelabs/conductor/TargetControllerTests.java +++ b/conductor/src/test/java/com/bluelinelabs/conductor/TargetControllerTests.java @@ -38,14 +38,14 @@ public void testSiblingTarget() { Assert.assertNull(controllerB.getTargetController()); router.pushController(RouterTransaction.with(controllerA) - .pushChangeHandler(new MockChangeHandler()) - .popChangeHandler(new MockChangeHandler())); + .pushChangeHandler(MockChangeHandler.defaultHandler()) + .popChangeHandler(MockChangeHandler.defaultHandler())); controllerB.setTargetController(controllerA); router.pushController(RouterTransaction.with(controllerB) - .pushChangeHandler(new MockChangeHandler()) - .popChangeHandler(new MockChangeHandler())); + .pushChangeHandler(MockChangeHandler.defaultHandler()) + .popChangeHandler(MockChangeHandler.defaultHandler())); Assert.assertNull(controllerA.getTargetController()); Assert.assertEquals(controllerA, controllerB.getTargetController()); @@ -60,15 +60,15 @@ public void testParentChildTarget() { Assert.assertNull(controllerB.getTargetController()); router.pushController(RouterTransaction.with(controllerA) - .pushChangeHandler(new MockChangeHandler()) - .popChangeHandler(new MockChangeHandler())); + .pushChangeHandler(MockChangeHandler.defaultHandler()) + .popChangeHandler(MockChangeHandler.defaultHandler())); controllerB.setTargetController(controllerA); Router childRouter = controllerA.getChildRouter((ViewGroup)controllerA.getView().findViewById(TestController.VIEW_ID)); childRouter.pushController(RouterTransaction.with(controllerB) - .pushChangeHandler(new MockChangeHandler()) - .popChangeHandler(new MockChangeHandler())); + .pushChangeHandler(MockChangeHandler.defaultHandler()) + .popChangeHandler(MockChangeHandler.defaultHandler())); Assert.assertNull(controllerA.getTargetController()); Assert.assertEquals(controllerA, controllerB.getTargetController()); @@ -83,15 +83,15 @@ public void testChildParentTarget() { Assert.assertNull(controllerB.getTargetController()); router.pushController(RouterTransaction.with(controllerA) - .pushChangeHandler(new MockChangeHandler()) - .popChangeHandler(new MockChangeHandler())); + .pushChangeHandler(MockChangeHandler.defaultHandler()) + .popChangeHandler(MockChangeHandler.defaultHandler())); controllerA.setTargetController(controllerB); Router childRouter = controllerA.getChildRouter((ViewGroup)controllerA.getView().findViewById(TestController.VIEW_ID)); childRouter.pushController(RouterTransaction.with(controllerB) - .pushChangeHandler(new MockChangeHandler()) - .popChangeHandler(new MockChangeHandler())); + .pushChangeHandler(MockChangeHandler.defaultHandler()) + .popChangeHandler(MockChangeHandler.defaultHandler())); Assert.assertNull(controllerB.getTargetController()); Assert.assertEquals(controllerB, controllerA.getTargetController()); From efbdf913bde0541d55a2d7d0546e58bfcf4887ea Mon Sep 17 00:00:00 2001 From: Eric Kuck Date: Wed, 18 Jan 2017 11:54:50 -0600 Subject: [PATCH 37/75] Fixes #201 --- .../bluelinelabs/conductor/Controller.java | 18 +-- .../conductor/internal/ViewAttachHandler.java | 109 +++++++++++++ .../conductor/ViewAttachHandlerTests.java | 146 ++++++++++++++++++ .../com/bluelinelabs/conductor/ViewUtils.java | 13 ++ 4 files changed, 277 insertions(+), 9 deletions(-) create mode 100644 conductor/src/main/java/com/bluelinelabs/conductor/internal/ViewAttachHandler.java create mode 100644 conductor/src/test/java/com/bluelinelabs/conductor/ViewAttachHandlerTests.java diff --git a/conductor/src/main/java/com/bluelinelabs/conductor/Controller.java b/conductor/src/main/java/com/bluelinelabs/conductor/Controller.java index 5c70d68f..93b59a95 100755 --- a/conductor/src/main/java/com/bluelinelabs/conductor/Controller.java +++ b/conductor/src/main/java/com/bluelinelabs/conductor/Controller.java @@ -18,12 +18,13 @@ import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; -import android.view.View.OnAttachStateChangeListener; import android.view.ViewGroup; import com.bluelinelabs.conductor.Router.OnControllerPushedListener; import com.bluelinelabs.conductor.internal.ClassUtils; import com.bluelinelabs.conductor.internal.RouterRequiringFunc; +import com.bluelinelabs.conductor.internal.ViewAttachHandler; +import com.bluelinelabs.conductor.internal.ViewAttachHandler.ViewAttachListener; import java.lang.ref.WeakReference; import java.lang.reflect.Constructor; @@ -80,7 +81,7 @@ public abstract class Controller { private ControllerChangeHandler overriddenPushHandler; private ControllerChangeHandler overriddenPopHandler; private RetainViewMode retainViewMode = RetainViewMode.RELEASE_DETACH; - private OnAttachStateChangeListener onAttachStateChangeListener; + private ViewAttachHandler viewAttachHandler; private final List childRouters = new ArrayList<>(); private final List lifecycleListeners = new ArrayList<>(); private final ArrayList requestedPermissions = new ArrayList<>(); @@ -847,7 +848,7 @@ private void removeViewReference() { onDestroyView(view); - view.removeOnAttachStateChangeListener(onAttachStateChangeListener); + viewAttachHandler.unregisterAttachListener(view); viewIsAttached = false; if (isBeingDestroyed) { @@ -894,9 +895,9 @@ final View inflate(@NonNull ViewGroup parent) { restoreViewState(view); - onAttachStateChangeListener = new OnAttachStateChangeListener() { + viewAttachHandler = new ViewAttachHandler(new ViewAttachListener() { @Override - public void onViewAttachedToWindow(View v) { + public void onAttached(View v) { if (v == view) { viewIsAttached = true; viewWasDetached = false; @@ -905,7 +906,7 @@ public void onViewAttachedToWindow(View v) { } @Override - public void onViewDetachedFromWindow(View v) { + public void onDetached(View v) { viewIsAttached = false; viewWasDetached = true; @@ -913,9 +914,8 @@ public void onViewDetachedFromWindow(View v) { detach(v, false); } } - }; - - view.addOnAttachStateChangeListener(onAttachStateChangeListener); + }); + viewAttachHandler.listenForAttach(view); } else if (retainViewMode == RetainViewMode.RETAIN_DETACH) { restoreChildControllerHosts(); } diff --git a/conductor/src/main/java/com/bluelinelabs/conductor/internal/ViewAttachHandler.java b/conductor/src/main/java/com/bluelinelabs/conductor/internal/ViewAttachHandler.java new file mode 100644 index 00000000..7fb55ccd --- /dev/null +++ b/conductor/src/main/java/com/bluelinelabs/conductor/internal/ViewAttachHandler.java @@ -0,0 +1,109 @@ +package com.bluelinelabs.conductor.internal; + +import android.view.View; +import android.view.View.OnAttachStateChangeListener; +import android.view.ViewGroup; + +public class ViewAttachHandler { + + public interface ViewAttachListener { + void onAttached(View view); + void onDetached(View view); + } + + private interface ChildAttachListener { + void onAttached(); + } + + private ViewAttachListener attachListener; + private OnAttachStateChangeListener rootOnAttachStateChangeListener = new OnAttachStateChangeListener() { + boolean rootAttached = false; + boolean childrenAttached = false; + + @Override + public void onViewAttachedToWindow(final View v) { + if (rootAttached) { + return; + } + + rootAttached = true; + listenForDeepestChildAttach(v, new ChildAttachListener() { + @Override + public void onAttached() { + childrenAttached = true; + attachListener.onAttached(v); + } + }); + + } + + @Override + public void onViewDetachedFromWindow(View v) { + rootAttached = false; + if (childrenAttached) { + childrenAttached = false; + attachListener.onDetached(v); + } + } + }; + private OnAttachStateChangeListener childOnAttachStateChangeListener; + + public ViewAttachHandler(ViewAttachListener attachListener) { + this.attachListener = attachListener; + } + + public void listenForAttach(final View view) { + view.addOnAttachStateChangeListener(rootOnAttachStateChangeListener); + } + + public void unregisterAttachListener(View view) { + view.removeOnAttachStateChangeListener(rootOnAttachStateChangeListener); + + if (view instanceof ViewGroup) { + findDeepestChild((ViewGroup)view).removeOnAttachStateChangeListener(childOnAttachStateChangeListener); + } + } + + void listenForDeepestChildAttach(final View view, final ChildAttachListener attachListener) { + if (!(view instanceof ViewGroup)) { + attachListener.onAttached(); + return; + } + + ViewGroup viewGroup = (ViewGroup)view; + if (viewGroup.getChildCount() == 0) { + attachListener.onAttached(); + return; + } + + childOnAttachStateChangeListener = new OnAttachStateChangeListener() { + boolean attached = false; + + @Override + public void onViewAttachedToWindow(View v) { + if (!attached) { + attached = true; + attachListener.onAttached(); + } + } + + @Override + public void onViewDetachedFromWindow(View v) { } + }; + findDeepestChild(viewGroup).addOnAttachStateChangeListener(childOnAttachStateChangeListener); + } + + private View findDeepestChild(ViewGroup viewGroup) { + if (viewGroup.getChildCount() == 0) { + return viewGroup; + } + + View lastChild = viewGroup.getChildAt(viewGroup.getChildCount() - 1); + if (lastChild instanceof ViewGroup) { + return findDeepestChild((ViewGroup)lastChild); + } else { + return lastChild; + } + } + +} diff --git a/conductor/src/test/java/com/bluelinelabs/conductor/ViewAttachHandlerTests.java b/conductor/src/test/java/com/bluelinelabs/conductor/ViewAttachHandlerTests.java new file mode 100644 index 00000000..bb30a3a9 --- /dev/null +++ b/conductor/src/test/java/com/bluelinelabs/conductor/ViewAttachHandlerTests.java @@ -0,0 +1,146 @@ +package com.bluelinelabs.conductor; + +import android.app.Activity; +import android.view.View; +import android.view.ViewGroup; +import android.widget.LinearLayout; + +import com.bluelinelabs.conductor.internal.ViewAttachHandler; +import com.bluelinelabs.conductor.internal.ViewAttachHandler.ViewAttachListener; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; + +@RunWith(RobolectricTestRunner.class) +@Config(manifest = Config.NONE) +public class ViewAttachHandlerTests { + + private Activity activity; + private ViewAttachHandler viewAttachHandler; + private CountingViewAttachListener viewAttachListener; + + @Before + public void setup() { + activity = new ActivityProxy().create(null).getActivity(); + viewAttachListener = new CountingViewAttachListener(); + viewAttachHandler = new ViewAttachHandler(viewAttachListener); + } + + @Test + public void testSimpleViewAttachDetach() { + View view = new View(activity); + viewAttachHandler.listenForAttach(view); + + Assert.assertEquals(0, viewAttachListener.attaches); + Assert.assertEquals(0, viewAttachListener.detaches); + + ViewUtils.reportAttached(view, true); + Assert.assertEquals(1, viewAttachListener.attaches); + Assert.assertEquals(0, viewAttachListener.detaches); + + ViewUtils.reportAttached(view, true); + Assert.assertEquals(1, viewAttachListener.attaches); + Assert.assertEquals(0, viewAttachListener.detaches); + + ViewUtils.reportAttached(view, false); + Assert.assertEquals(1, viewAttachListener.attaches); + Assert.assertEquals(1, viewAttachListener.detaches); + + ViewUtils.reportAttached(view, false); + Assert.assertEquals(1, viewAttachListener.attaches); + Assert.assertEquals(1, viewAttachListener.detaches); + + ViewUtils.reportAttached(view, true); + Assert.assertEquals(2, viewAttachListener.attaches); + Assert.assertEquals(1, viewAttachListener.detaches); + } + + @Test + public void testSimpleViewGroupAttachDetach() { + View view = new LinearLayout(activity); + viewAttachHandler.listenForAttach(view); + + Assert.assertEquals(0, viewAttachListener.attaches); + Assert.assertEquals(0, viewAttachListener.detaches); + + ViewUtils.reportAttached(view, true); + Assert.assertEquals(1, viewAttachListener.attaches); + Assert.assertEquals(0, viewAttachListener.detaches); + + ViewUtils.reportAttached(view, true); + Assert.assertEquals(1, viewAttachListener.attaches); + Assert.assertEquals(0, viewAttachListener.detaches); + + ViewUtils.reportAttached(view, false); + Assert.assertEquals(1, viewAttachListener.attaches); + Assert.assertEquals(1, viewAttachListener.detaches); + + ViewUtils.reportAttached(view, false); + Assert.assertEquals(1, viewAttachListener.attaches); + Assert.assertEquals(1, viewAttachListener.detaches); + + ViewUtils.reportAttached(view, true); + Assert.assertEquals(2, viewAttachListener.attaches); + Assert.assertEquals(1, viewAttachListener.detaches); + } + + @Test + public void testNestedViewGroupAttachDetach() { + ViewGroup view = new LinearLayout(activity); + View child = new LinearLayout(activity); + view.addView(child); + viewAttachHandler.listenForAttach(view); + + Assert.assertEquals(0, viewAttachListener.attaches); + Assert.assertEquals(0, viewAttachListener.detaches); + + ViewUtils.reportAttached(view, true, false); + Assert.assertEquals(0, viewAttachListener.attaches); + Assert.assertEquals(0, viewAttachListener.detaches); + + ViewUtils.reportAttached(child, true, false); + Assert.assertEquals(1, viewAttachListener.attaches); + Assert.assertEquals(0, viewAttachListener.detaches); + + ViewUtils.reportAttached(view, true, false); + ViewUtils.reportAttached(child, true, false); + Assert.assertEquals(1, viewAttachListener.attaches); + Assert.assertEquals(0, viewAttachListener.detaches); + + ViewUtils.reportAttached(view, false, false); + Assert.assertEquals(1, viewAttachListener.attaches); + Assert.assertEquals(1, viewAttachListener.detaches); + + ViewUtils.reportAttached(view, false, false); + Assert.assertEquals(1, viewAttachListener.attaches); + Assert.assertEquals(1, viewAttachListener.detaches); + + ViewUtils.reportAttached(view, true, false); + Assert.assertEquals(1, viewAttachListener.attaches); + Assert.assertEquals(1, viewAttachListener.detaches); + + ViewUtils.reportAttached(child, true, false); + Assert.assertEquals(2, viewAttachListener.attaches); + Assert.assertEquals(1, viewAttachListener.detaches); + } + + private static class CountingViewAttachListener implements ViewAttachListener { + int attaches; + int detaches; + + @Override + public void onAttached(View view) { + attaches++; + } + + @Override + public void onDetached(View view) { + detaches++; + } + } + +} diff --git a/conductor/src/test/java/com/bluelinelabs/conductor/ViewUtils.java b/conductor/src/test/java/com/bluelinelabs/conductor/ViewUtils.java index dc0a3c49..08cdb56a 100644 --- a/conductor/src/test/java/com/bluelinelabs/conductor/ViewUtils.java +++ b/conductor/src/test/java/com/bluelinelabs/conductor/ViewUtils.java @@ -2,6 +2,7 @@ import android.view.View; import android.view.View.OnAttachStateChangeListener; +import android.view.ViewGroup; import org.robolectric.util.ReflectionHelpers; @@ -10,6 +11,10 @@ public class ViewUtils { public static void reportAttached(View view, boolean attached) { + reportAttached(view, attached, true); + } + + public static void reportAttached(View view, boolean attached, boolean propogateToChildren) { if (view instanceof AttachFakingFrameLayout) { ((AttachFakingFrameLayout)view).setAttached(attached, false); } @@ -38,6 +43,14 @@ public void onViewDetachedFromWindow(View v) { } } } + if (propogateToChildren && view instanceof ViewGroup) { + ViewGroup viewGroup = (ViewGroup)view; + int childCount = viewGroup.getChildCount(); + for (int i = 0; i < childCount; i++) { + reportAttached(viewGroup.getChildAt(i), attached, true); + } + } + } private static List getAttachStateListeners(View view) { From 77ad6b4512f7113895d66d47ba0b84b0efcdeac8 Mon Sep 17 00:00:00 2001 From: Eric Kuck Date: Wed, 18 Jan 2017 12:18:06 -0600 Subject: [PATCH 38/75] Now sets ClassLoader for view state Bundle - potential fix for #198 --- .../main/java/com/bluelinelabs/conductor/Controller.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/conductor/src/main/java/com/bluelinelabs/conductor/Controller.java b/conductor/src/main/java/com/bluelinelabs/conductor/Controller.java index 93b59a95..441acac6 100755 --- a/conductor/src/main/java/com/bluelinelabs/conductor/Controller.java +++ b/conductor/src/main/java/com/bluelinelabs/conductor/Controller.java @@ -982,7 +982,7 @@ private void destroy(boolean removeViews) { private void saveViewState(@NonNull View view) { hasSavedViewState = true; - viewState = new Bundle(); + viewState = new Bundle(getClass().getClassLoader()); SparseArray hierarchyState = new SparseArray<>(); view.saveHierarchyState(hierarchyState); @@ -1063,6 +1063,10 @@ final Bundle saveInstanceState() { private void restoreInstanceState(@NonNull Bundle savedInstanceState) { viewState = savedInstanceState.getBundle(KEY_VIEW_STATE); + if (viewState != null) { + viewState.setClassLoader(getClass().getClassLoader()); + } + instanceId = savedInstanceState.getString(KEY_INSTANCE_ID); targetInstanceId = savedInstanceState.getString(KEY_TARGET_INSTANCE_ID); requestedPermissions.addAll(savedInstanceState.getStringArrayList(KEY_REQUESTED_PERMISSIONS)); From 2ce8c0a45db255732b9020068c24a74298d764a7 Mon Sep 17 00:00:00 2001 From: Eric Kuck Date: Wed, 18 Jan 2017 13:00:24 -0600 Subject: [PATCH 39/75] Version bump --- README.md | 16 ++++++++-------- gradle.properties | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 3fce89df..77098b7a 100644 --- a/README.md +++ b/README.md @@ -20,26 +20,26 @@ Conductor is architecture-agnostic and does not try to force any design decision ## Installation ```gradle -compile 'com.bluelinelabs:conductor:2.0.5' +compile 'com.bluelinelabs:conductor:2.0.6' // If you want the components that go along with // Android's support libraries (currently just a PagerAdapter): -compile 'com.bluelinelabs:conductor-support:2.0.5' +compile 'com.bluelinelabs:conductor-support:2.0.6' // If you want RxJava lifecycle support: -compile 'com.bluelinelabs:conductor-rxlifecycle:2.0.5' +compile 'com.bluelinelabs:conductor-rxlifecycle:2.0.6' // If you want RxJava2 lifecycle support: -compile 'com.bluelinelabs:conductor-rxlifecycle2:2.0.5' +compile 'com.bluelinelabs:conductor-rxlifecycle2:2.0.6' ``` SNAPSHOT: ```gradle -compile 'com.bluelinelabs:conductor:2.0.6-SNAPSHOT' -compile 'com.bluelinelabs:conductor-support:2.0.6-SNAPSHOT' -compile 'com.bluelinelabs:conductor-rxlifecycle:2.0.6-SNAPSHOT' -compile 'com.bluelinelabs:conductor-rxlifecycle2:2.0.6-SNAPSHOT' +compile 'com.bluelinelabs:conductor:2.0.7-SNAPSHOT' +compile 'com.bluelinelabs:conductor-support:2.0.7-SNAPSHOT' +compile 'com.bluelinelabs:conductor-rxlifecycle:2.0.7-SNAPSHOT' +compile 'com.bluelinelabs:conductor-rxlifecycle2:2.0.7-SNAPSHOT' ``` You also have to add the url to the snapshot repository: diff --git a/gradle.properties b/gradle.properties index 1ca257eb..9358e941 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ -VERSION_NAME=2.0.6-SNAPSHOT +VERSION_NAME=2.0.7-SNAPSHOT VERSION_CODE=2 GROUP=com.bluelinelabs From f74f8391b63ab4ab85d8d77ac76992d621c2bcde Mon Sep 17 00:00:00 2001 From: Eric Kuck Date: Thu, 19 Jan 2017 11:50:07 -0600 Subject: [PATCH 40/75] Fixes #204 (also mentioned in #199) --- .../com/bluelinelabs/conductor/Router.java | 30 ++- .../conductor/BackstackTests.java | 27 +- .../ControllerChangeHandlerTests.java | 16 +- .../conductor/ControllerLifecycleTests.java | 238 ++++++++--------- .../conductor/ControllerTests.java | 129 +++++----- .../conductor/ControllerTransactionTests.java | 13 +- .../conductor/ReattachCaseTests.java | 134 +++++----- .../conductor/RouterChangeHandlerTests.java | 239 ++++++++++++++++++ .../bluelinelabs/conductor/RouterTests.java | 170 ++++++------- .../conductor/TargetControllerTests.java | 32 ++- .../conductor/ViewAttachHandlerTests.java | 85 ++++--- .../conductor/{ => util}/ActivityProxy.java | 2 +- .../{ => util}/AttachFakingFrameLayout.java | 2 +- .../conductor/{ => util}/CallState.java | 2 +- .../conductor/util/ChangeHandlerHistory.java | 67 +++++ .../conductor/util/ListUtils.java | 16 ++ .../{ => util}/MockChangeHandler.java | 49 ++-- .../conductor/{ => util}/TestActivity.java | 2 +- .../conductor/{ => util}/TestController.java | 20 +- .../conductor/{ => util}/ViewUtils.java | 2 +- 20 files changed, 826 insertions(+), 449 deletions(-) create mode 100644 conductor/src/test/java/com/bluelinelabs/conductor/RouterChangeHandlerTests.java rename conductor/src/test/java/com/bluelinelabs/conductor/{ => util}/ActivityProxy.java (97%) rename conductor/src/test/java/com/bluelinelabs/conductor/{ => util}/AttachFakingFrameLayout.java (98%) rename conductor/src/test/java/com/bluelinelabs/conductor/{ => util}/CallState.java (99%) create mode 100644 conductor/src/test/java/com/bluelinelabs/conductor/util/ChangeHandlerHistory.java create mode 100644 conductor/src/test/java/com/bluelinelabs/conductor/util/ListUtils.java rename conductor/src/test/java/com/bluelinelabs/conductor/{ => util}/MockChangeHandler.java (56%) rename conductor/src/test/java/com/bluelinelabs/conductor/{ => util}/TestActivity.java (90%) rename conductor/src/test/java/com/bluelinelabs/conductor/{ => util}/TestController.java (85%) rename conductor/src/test/java/com/bluelinelabs/conductor/{ => util}/ViewUtils.java (98%) diff --git a/conductor/src/main/java/com/bluelinelabs/conductor/Router.java b/conductor/src/main/java/com/bluelinelabs/conductor/Router.java index 18a3946e..3c229b51 100755 --- a/conductor/src/main/java/com/bluelinelabs/conductor/Router.java +++ b/conductor/src/main/java/com/bluelinelabs/conductor/Router.java @@ -344,29 +344,19 @@ public List getBackstack() { public void setBackstack(@NonNull List newBackstack, @Nullable ControllerChangeHandler changeHandler) { List oldVisibleTransactions = getVisibleTransactions(backstack.iterator()); - backstack.setBackstack(newBackstack); + removeAllExceptVisibleAndUnowned(); + backstack.setBackstack(newBackstack); for (RouterTransaction transaction : backstack) { transaction.onAttachedToRouter(); } - removeAllExceptVisibleAndUnowned(); - if (newBackstack.size() > 0) { List reverseNewBackstack = new ArrayList<>(newBackstack); Collections.reverse(reverseNewBackstack); List 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; - } - } - } - + boolean visibleTransactionsChanged = !backstacksAreEqual(newVisibleTransactions, oldVisibleTransactions); if (visibleTransactionsChanged) { Controller rootController = oldVisibleTransactions.size() > 0 ? oldVisibleTransactions.get(0).controller : null; performControllerChange(newVisibleTransactions.get(0).controller, rootController, true, changeHandler); @@ -727,6 +717,20 @@ private List getVisibleTransactions(@NonNull Iterator lhs, List rhs) { + if (lhs.size() != rhs.size()) { + return false; + } + + for (int i = 0; i < rhs.size(); i++) { + if (rhs.get(i).controller() != lhs.get(i).controller()) { + return false; + } + } + + return true; + } + void setControllerRouter(@NonNull Controller controller) { controller.setRouter(this); } diff --git a/conductor/src/test/java/com/bluelinelabs/conductor/BackstackTests.java b/conductor/src/test/java/com/bluelinelabs/conductor/BackstackTests.java index 8d514227..9783f99b 100644 --- a/conductor/src/test/java/com/bluelinelabs/conductor/BackstackTests.java +++ b/conductor/src/test/java/com/bluelinelabs/conductor/BackstackTests.java @@ -1,9 +1,12 @@ package com.bluelinelabs.conductor; -import org.junit.Assert; +import com.bluelinelabs.conductor.util.TestController; + import org.junit.Before; import org.junit.Test; +import static org.junit.Assert.assertEquals; + public class BackstackTests { private Backstack backstack; @@ -15,20 +18,20 @@ public void setup() { @Test public void testPush() { - Assert.assertEquals(0, backstack.size()); + assertEquals(0, backstack.size()); backstack.push(RouterTransaction.with(new TestController())); - Assert.assertEquals(1, backstack.size()); + assertEquals(1, backstack.size()); } @Test public void testPop() { backstack.push(RouterTransaction.with(new TestController())); backstack.push(RouterTransaction.with(new TestController())); - Assert.assertEquals(2, backstack.size()); + assertEquals(2, backstack.size()); backstack.pop(); - Assert.assertEquals(1, backstack.size()); + assertEquals(1, backstack.size()); backstack.pop(); - Assert.assertEquals(0, backstack.size()); + assertEquals(0, backstack.size()); } @Test @@ -37,13 +40,13 @@ public void testPeek() { RouterTransaction transaction2 = RouterTransaction.with(new TestController()); backstack.push(transaction1); - Assert.assertEquals(transaction1, backstack.peek()); + assertEquals(transaction1, backstack.peek()); backstack.push(transaction2); - Assert.assertEquals(transaction2, backstack.peek()); + assertEquals(transaction2, backstack.peek()); backstack.pop(); - Assert.assertEquals(transaction1, backstack.peek()); + assertEquals(transaction1, backstack.peek()); } @Test @@ -56,11 +59,11 @@ public void testPopTo() { backstack.push(transaction2); backstack.push(transaction3); - Assert.assertEquals(3, backstack.size()); + assertEquals(3, backstack.size()); backstack.popTo(transaction1); - Assert.assertEquals(1, backstack.size()); - Assert.assertEquals(transaction1, backstack.peek()); + assertEquals(1, backstack.size()); + assertEquals(transaction1, backstack.peek()); } } diff --git a/conductor/src/test/java/com/bluelinelabs/conductor/ControllerChangeHandlerTests.java b/conductor/src/test/java/com/bluelinelabs/conductor/ControllerChangeHandlerTests.java index 084cb91d..2009cf8a 100644 --- a/conductor/src/test/java/com/bluelinelabs/conductor/ControllerChangeHandlerTests.java +++ b/conductor/src/test/java/com/bluelinelabs/conductor/ControllerChangeHandlerTests.java @@ -2,10 +2,12 @@ import com.bluelinelabs.conductor.changehandler.FadeChangeHandler; import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler; +import com.bluelinelabs.conductor.util.TestController; -import org.junit.Assert; import org.junit.Test; +import static org.junit.Assert.assertEquals; + public class ControllerChangeHandlerTests { @Test @@ -21,17 +23,17 @@ public void testSaveRestore() { ControllerChangeHandler restoredHorizontal = restoredTransaction.pushChangeHandler(); ControllerChangeHandler restoredFade = restoredTransaction.popChangeHandler(); - Assert.assertEquals(horizontalChangeHandler.getClass(), restoredHorizontal.getClass()); - Assert.assertEquals(fadeChangeHandler.getClass(), restoredFade.getClass()); + assertEquals(horizontalChangeHandler.getClass(), restoredHorizontal.getClass()); + assertEquals(fadeChangeHandler.getClass(), restoredFade.getClass()); HorizontalChangeHandler restoredHorizontalCast = (HorizontalChangeHandler)restoredHorizontal; FadeChangeHandler restoredFadeCast = (FadeChangeHandler)restoredFade; - Assert.assertEquals(horizontalChangeHandler.getAnimationDuration(), restoredHorizontalCast.getAnimationDuration()); - Assert.assertEquals(horizontalChangeHandler.removesFromViewOnPush(), restoredHorizontalCast.removesFromViewOnPush()); + assertEquals(horizontalChangeHandler.getAnimationDuration(), restoredHorizontalCast.getAnimationDuration()); + assertEquals(horizontalChangeHandler.removesFromViewOnPush(), restoredHorizontalCast.removesFromViewOnPush()); - Assert.assertEquals(fadeChangeHandler.getAnimationDuration(), restoredFadeCast.getAnimationDuration()); - Assert.assertEquals(fadeChangeHandler.removesFromViewOnPush(), restoredFadeCast.removesFromViewOnPush()); + assertEquals(fadeChangeHandler.getAnimationDuration(), restoredFadeCast.getAnimationDuration()); + assertEquals(fadeChangeHandler.removesFromViewOnPush(), restoredFadeCast.removesFromViewOnPush()); } } diff --git a/conductor/src/test/java/com/bluelinelabs/conductor/ControllerLifecycleTests.java b/conductor/src/test/java/com/bluelinelabs/conductor/ControllerLifecycleTests.java index fd4624b3..38664388 100644 --- a/conductor/src/test/java/com/bluelinelabs/conductor/ControllerLifecycleTests.java +++ b/conductor/src/test/java/com/bluelinelabs/conductor/ControllerLifecycleTests.java @@ -6,15 +6,21 @@ import android.view.ViewGroup; import com.bluelinelabs.conductor.Controller.LifecycleListener; -import com.bluelinelabs.conductor.MockChangeHandler.ChangeHandlerListener; +import com.bluelinelabs.conductor.util.ActivityProxy; +import com.bluelinelabs.conductor.util.CallState; +import com.bluelinelabs.conductor.util.MockChangeHandler; +import com.bluelinelabs.conductor.util.MockChangeHandler.ChangeHandlerListener; +import com.bluelinelabs.conductor.util.TestController; -import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + @RunWith(RobolectricTestRunner.class) @Config(manifest = Config.NONE) public class ControllerLifecycleTests { @@ -60,7 +66,7 @@ public void testNormalLifecycle() { router.popCurrentController(); - Assert.assertNull(controller.getView()); + assertNull(controller.getView()); assertCalls(expectedCallState, controller); } @@ -194,191 +200,191 @@ public void testLifecycleCallOrder() { @Override public void preCreateView(@NonNull Controller controller) { callState.createViewCalls++; - Assert.assertEquals(1, callState.createViewCalls); - Assert.assertEquals(0, testController.currentCallState.createViewCalls); + assertEquals(1, callState.createViewCalls); + assertEquals(0, testController.currentCallState.createViewCalls); - Assert.assertEquals(0, callState.attachCalls); - Assert.assertEquals(0, testController.currentCallState.attachCalls); + assertEquals(0, callState.attachCalls); + assertEquals(0, testController.currentCallState.attachCalls); - Assert.assertEquals(0, callState.detachCalls); - Assert.assertEquals(0, testController.currentCallState.detachCalls); + assertEquals(0, callState.detachCalls); + assertEquals(0, testController.currentCallState.detachCalls); - Assert.assertEquals(0, callState.destroyViewCalls); - Assert.assertEquals(0, testController.currentCallState.destroyViewCalls); + assertEquals(0, callState.destroyViewCalls); + assertEquals(0, testController.currentCallState.destroyViewCalls); - Assert.assertEquals(0, callState.destroyCalls); - Assert.assertEquals(0, testController.currentCallState.destroyCalls); + assertEquals(0, callState.destroyCalls); + assertEquals(0, testController.currentCallState.destroyCalls); } @Override public void postCreateView(@NonNull Controller controller, @NonNull View view) { callState.createViewCalls++; - Assert.assertEquals(2, callState.createViewCalls); - Assert.assertEquals(1, testController.currentCallState.createViewCalls); + assertEquals(2, callState.createViewCalls); + assertEquals(1, testController.currentCallState.createViewCalls); - Assert.assertEquals(0, callState.attachCalls); - Assert.assertEquals(0, testController.currentCallState.attachCalls); + assertEquals(0, callState.attachCalls); + assertEquals(0, testController.currentCallState.attachCalls); - Assert.assertEquals(0, callState.detachCalls); - Assert.assertEquals(0, testController.currentCallState.detachCalls); + assertEquals(0, callState.detachCalls); + assertEquals(0, testController.currentCallState.detachCalls); - Assert.assertEquals(0, callState.destroyViewCalls); - Assert.assertEquals(0, testController.currentCallState.destroyViewCalls); + assertEquals(0, callState.destroyViewCalls); + assertEquals(0, testController.currentCallState.destroyViewCalls); - Assert.assertEquals(0, callState.destroyCalls); - Assert.assertEquals(0, testController.currentCallState.destroyCalls); + assertEquals(0, callState.destroyCalls); + assertEquals(0, testController.currentCallState.destroyCalls); } @Override public void preAttach(@NonNull Controller controller, @NonNull View view) { callState.attachCalls++; - Assert.assertEquals(2, callState.createViewCalls); - Assert.assertEquals(1, testController.currentCallState.createViewCalls); + assertEquals(2, callState.createViewCalls); + assertEquals(1, testController.currentCallState.createViewCalls); - Assert.assertEquals(1, callState.attachCalls); - Assert.assertEquals(0, testController.currentCallState.attachCalls); + assertEquals(1, callState.attachCalls); + assertEquals(0, testController.currentCallState.attachCalls); - Assert.assertEquals(0, callState.detachCalls); - Assert.assertEquals(0, testController.currentCallState.detachCalls); + assertEquals(0, callState.detachCalls); + assertEquals(0, testController.currentCallState.detachCalls); - Assert.assertEquals(0, callState.destroyViewCalls); - Assert.assertEquals(0, testController.currentCallState.destroyViewCalls); + assertEquals(0, callState.destroyViewCalls); + assertEquals(0, testController.currentCallState.destroyViewCalls); - Assert.assertEquals(0, callState.destroyCalls); - Assert.assertEquals(0, testController.currentCallState.destroyCalls); + assertEquals(0, callState.destroyCalls); + assertEquals(0, testController.currentCallState.destroyCalls); } @Override public void postAttach(@NonNull Controller controller, @NonNull View view) { callState.attachCalls++; - Assert.assertEquals(2, callState.createViewCalls); - Assert.assertEquals(1, testController.currentCallState.createViewCalls); + assertEquals(2, callState.createViewCalls); + assertEquals(1, testController.currentCallState.createViewCalls); - Assert.assertEquals(2, callState.attachCalls); - Assert.assertEquals(1, testController.currentCallState.attachCalls); + assertEquals(2, callState.attachCalls); + assertEquals(1, testController.currentCallState.attachCalls); - Assert.assertEquals(0, callState.detachCalls); - Assert.assertEquals(0, testController.currentCallState.detachCalls); + assertEquals(0, callState.detachCalls); + assertEquals(0, testController.currentCallState.detachCalls); - Assert.assertEquals(0, callState.destroyViewCalls); - Assert.assertEquals(0, testController.currentCallState.destroyViewCalls); + assertEquals(0, callState.destroyViewCalls); + assertEquals(0, testController.currentCallState.destroyViewCalls); - Assert.assertEquals(0, callState.destroyCalls); - Assert.assertEquals(0, testController.currentCallState.destroyCalls); + assertEquals(0, callState.destroyCalls); + assertEquals(0, testController.currentCallState.destroyCalls); } @Override public void preDetach(@NonNull Controller controller, @NonNull View view) { callState.detachCalls++; - Assert.assertEquals(2, callState.createViewCalls); - Assert.assertEquals(1, testController.currentCallState.createViewCalls); + assertEquals(2, callState.createViewCalls); + assertEquals(1, testController.currentCallState.createViewCalls); - Assert.assertEquals(2, callState.attachCalls); - Assert.assertEquals(1, testController.currentCallState.attachCalls); + assertEquals(2, callState.attachCalls); + assertEquals(1, testController.currentCallState.attachCalls); - Assert.assertEquals(1, callState.detachCalls); - Assert.assertEquals(0, testController.currentCallState.detachCalls); + assertEquals(1, callState.detachCalls); + assertEquals(0, testController.currentCallState.detachCalls); - Assert.assertEquals(0, callState.destroyViewCalls); - Assert.assertEquals(0, testController.currentCallState.destroyViewCalls); + assertEquals(0, callState.destroyViewCalls); + assertEquals(0, testController.currentCallState.destroyViewCalls); - Assert.assertEquals(0, callState.destroyCalls); - Assert.assertEquals(0, testController.currentCallState.destroyCalls); + assertEquals(0, callState.destroyCalls); + assertEquals(0, testController.currentCallState.destroyCalls); } @Override public void postDetach(@NonNull Controller controller, @NonNull View view) { callState.detachCalls++; - Assert.assertEquals(2, callState.createViewCalls); - Assert.assertEquals(1, testController.currentCallState.createViewCalls); + assertEquals(2, callState.createViewCalls); + assertEquals(1, testController.currentCallState.createViewCalls); - Assert.assertEquals(2, callState.attachCalls); - Assert.assertEquals(1, testController.currentCallState.attachCalls); + assertEquals(2, callState.attachCalls); + assertEquals(1, testController.currentCallState.attachCalls); - Assert.assertEquals(2, callState.detachCalls); - Assert.assertEquals(1, testController.currentCallState.detachCalls); + assertEquals(2, callState.detachCalls); + assertEquals(1, testController.currentCallState.detachCalls); - Assert.assertEquals(0, callState.destroyViewCalls); - Assert.assertEquals(0, testController.currentCallState.destroyViewCalls); + assertEquals(0, callState.destroyViewCalls); + assertEquals(0, testController.currentCallState.destroyViewCalls); - Assert.assertEquals(0, callState.destroyCalls); - Assert.assertEquals(0, testController.currentCallState.destroyCalls); + assertEquals(0, callState.destroyCalls); + assertEquals(0, testController.currentCallState.destroyCalls); } @Override public void preDestroyView(@NonNull Controller controller, @NonNull View view) { callState.destroyViewCalls++; - Assert.assertEquals(2, callState.createViewCalls); - Assert.assertEquals(1, testController.currentCallState.createViewCalls); + assertEquals(2, callState.createViewCalls); + assertEquals(1, testController.currentCallState.createViewCalls); - Assert.assertEquals(2, callState.attachCalls); - Assert.assertEquals(1, testController.currentCallState.attachCalls); + assertEquals(2, callState.attachCalls); + assertEquals(1, testController.currentCallState.attachCalls); - Assert.assertEquals(2, callState.detachCalls); - Assert.assertEquals(1, testController.currentCallState.detachCalls); + assertEquals(2, callState.detachCalls); + assertEquals(1, testController.currentCallState.detachCalls); - Assert.assertEquals(1, callState.destroyViewCalls); - Assert.assertEquals(0, testController.currentCallState.destroyViewCalls); + assertEquals(1, callState.destroyViewCalls); + assertEquals(0, testController.currentCallState.destroyViewCalls); - Assert.assertEquals(0, callState.destroyCalls); - Assert.assertEquals(0, testController.currentCallState.destroyCalls); + assertEquals(0, callState.destroyCalls); + assertEquals(0, testController.currentCallState.destroyCalls); } @Override public void postDestroyView(@NonNull Controller controller) { callState.destroyViewCalls++; - Assert.assertEquals(2, callState.createViewCalls); - Assert.assertEquals(1, testController.currentCallState.createViewCalls); + assertEquals(2, callState.createViewCalls); + assertEquals(1, testController.currentCallState.createViewCalls); - Assert.assertEquals(2, callState.attachCalls); - Assert.assertEquals(1, testController.currentCallState.attachCalls); + assertEquals(2, callState.attachCalls); + assertEquals(1, testController.currentCallState.attachCalls); - Assert.assertEquals(2, callState.detachCalls); - Assert.assertEquals(1, testController.currentCallState.detachCalls); + assertEquals(2, callState.detachCalls); + assertEquals(1, testController.currentCallState.detachCalls); - Assert.assertEquals(2, callState.destroyViewCalls); - Assert.assertEquals(1, testController.currentCallState.destroyViewCalls); + assertEquals(2, callState.destroyViewCalls); + assertEquals(1, testController.currentCallState.destroyViewCalls); - Assert.assertEquals(0, callState.destroyCalls); - Assert.assertEquals(0, testController.currentCallState.destroyCalls); + assertEquals(0, callState.destroyCalls); + assertEquals(0, testController.currentCallState.destroyCalls); } @Override public void preDestroy(@NonNull Controller controller) { callState.destroyCalls++; - Assert.assertEquals(2, callState.createViewCalls); - Assert.assertEquals(1, testController.currentCallState.createViewCalls); + assertEquals(2, callState.createViewCalls); + assertEquals(1, testController.currentCallState.createViewCalls); - Assert.assertEquals(2, callState.attachCalls); - Assert.assertEquals(1, testController.currentCallState.attachCalls); + assertEquals(2, callState.attachCalls); + assertEquals(1, testController.currentCallState.attachCalls); - Assert.assertEquals(2, callState.detachCalls); - Assert.assertEquals(1, testController.currentCallState.detachCalls); + assertEquals(2, callState.detachCalls); + assertEquals(1, testController.currentCallState.detachCalls); - Assert.assertEquals(2, callState.destroyViewCalls); - Assert.assertEquals(1, testController.currentCallState.destroyViewCalls); + assertEquals(2, callState.destroyViewCalls); + assertEquals(1, testController.currentCallState.destroyViewCalls); - Assert.assertEquals(1, callState.destroyCalls); - Assert.assertEquals(0, testController.currentCallState.destroyCalls); + assertEquals(1, callState.destroyCalls); + assertEquals(0, testController.currentCallState.destroyCalls); } @Override public void postDestroy(@NonNull Controller controller) { callState.destroyCalls++; - Assert.assertEquals(2, callState.createViewCalls); - Assert.assertEquals(1, testController.currentCallState.createViewCalls); + assertEquals(2, callState.createViewCalls); + assertEquals(1, testController.currentCallState.createViewCalls); - Assert.assertEquals(2, callState.attachCalls); - Assert.assertEquals(1, testController.currentCallState.attachCalls); + assertEquals(2, callState.attachCalls); + assertEquals(1, testController.currentCallState.attachCalls); - Assert.assertEquals(2, callState.detachCalls); - Assert.assertEquals(1, testController.currentCallState.detachCalls); + assertEquals(2, callState.detachCalls); + assertEquals(1, testController.currentCallState.detachCalls); - Assert.assertEquals(2, callState.destroyViewCalls); - Assert.assertEquals(1, testController.currentCallState.destroyViewCalls); + assertEquals(2, callState.destroyViewCalls); + assertEquals(1, testController.currentCallState.destroyViewCalls); - Assert.assertEquals(2, callState.destroyCalls); - Assert.assertEquals(1, testController.currentCallState.destroyCalls); + assertEquals(2, callState.destroyCalls); + assertEquals(1, testController.currentCallState.destroyCalls); } }); @@ -388,11 +394,11 @@ public void postDestroy(@NonNull Controller controller) { router.popController(testController); - Assert.assertEquals(2, callState.createViewCalls); - Assert.assertEquals(2, callState.attachCalls); - Assert.assertEquals(2, callState.detachCalls); - Assert.assertEquals(2, callState.destroyViewCalls); - Assert.assertEquals(2, callState.destroyCalls); + assertEquals(2, callState.createViewCalls); + assertEquals(2, callState.attachCalls); + assertEquals(2, callState.detachCalls); + assertEquals(2, callState.destroyViewCalls); + assertEquals(2, callState.destroyCalls); } @Test @@ -455,20 +461,20 @@ public void testChildLifecycle2() { private MockChangeHandler getPushHandler(final CallState expectedCallState, final TestController controller) { return MockChangeHandler.listeningChangeHandler(new ChangeHandlerListener() { @Override - void willStartChange() { + public void willStartChange() { expectedCallState.changeStartCalls++; expectedCallState.createViewCalls++; assertCalls(expectedCallState, controller); } @Override - void didAttachOrDetach() { + public void didAttachOrDetach() { expectedCallState.attachCalls++; assertCalls(expectedCallState, controller); } @Override - void didEndChange() { + public void didEndChange() { expectedCallState.changeEndCalls++; assertCalls(expectedCallState, controller); } @@ -478,13 +484,13 @@ void didEndChange() { private MockChangeHandler getPopHandler(final CallState expectedCallState, final TestController controller) { return MockChangeHandler.listeningChangeHandler(new ChangeHandlerListener() { @Override - void willStartChange() { + public void willStartChange() { expectedCallState.changeStartCalls++; assertCalls(expectedCallState, controller); } @Override - void didAttachOrDetach() { + public void didAttachOrDetach() { expectedCallState.destroyViewCalls++; expectedCallState.detachCalls++; expectedCallState.destroyCalls++; @@ -492,7 +498,7 @@ void didAttachOrDetach() { } @Override - void didEndChange() { + public void didEndChange() { expectedCallState.changeEndCalls++; assertCalls(expectedCallState, controller); } @@ -500,8 +506,8 @@ void didEndChange() { } private void assertCalls(CallState callState, TestController controller) { - Assert.assertEquals("Expected call counts and controller call counts do not match.", callState, controller.currentCallState); - Assert.assertEquals("Expected call counts and lifecycle call counts do not match.", callState, currentCallState); + assertEquals("Expected call counts and controller call counts do not match.", callState, controller.currentCallState); + assertEquals("Expected call counts and lifecycle call counts do not match.", callState, currentCallState); } private void attachLifecycleListener(Controller controller) { diff --git a/conductor/src/test/java/com/bluelinelabs/conductor/ControllerTests.java b/conductor/src/test/java/com/bluelinelabs/conductor/ControllerTests.java index 256f19d5..00b7ff30 100644 --- a/conductor/src/test/java/com/bluelinelabs/conductor/ControllerTests.java +++ b/conductor/src/test/java/com/bluelinelabs/conductor/ControllerTests.java @@ -7,14 +7,21 @@ import android.view.ViewGroup; import com.bluelinelabs.conductor.Controller.RetainViewMode; +import com.bluelinelabs.conductor.util.ActivityProxy; +import com.bluelinelabs.conductor.util.CallState; +import com.bluelinelabs.conductor.util.TestController; +import com.bluelinelabs.conductor.util.ViewUtils; -import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + @RunWith(RobolectricTestRunner.class) @Config(manifest = Config.NONE) public class ControllerTests { @@ -42,26 +49,26 @@ public void testViewRetention() { // Test View getting released w/ RELEASE_DETACH controller.setRetainViewMode(RetainViewMode.RELEASE_DETACH); - Assert.assertNull(controller.getView()); + assertNull(controller.getView()); View view = controller.inflate(router.container); - Assert.assertNotNull(controller.getView()); + assertNotNull(controller.getView()); ViewUtils.reportAttached(view, true); - Assert.assertNotNull(controller.getView()); + assertNotNull(controller.getView()); ViewUtils.reportAttached(view, false); - Assert.assertNull(controller.getView()); + assertNull(controller.getView()); // Test View getting retained w/ RETAIN_DETACH controller.setRetainViewMode(RetainViewMode.RETAIN_DETACH); view = controller.inflate(router.container); - Assert.assertNotNull(controller.getView()); + assertNotNull(controller.getView()); ViewUtils.reportAttached(view, true); - Assert.assertNotNull(controller.getView()); + assertNotNull(controller.getView()); ViewUtils.reportAttached(view, false); - Assert.assertNotNull(controller.getView()); + assertNotNull(controller.getView()); // Ensure re-setting RELEASE_DETACH releases controller.setRetainViewMode(RetainViewMode.RELEASE_DETACH); - Assert.assertNull(controller.getView()); + assertNull(controller.getView()); } @Test @@ -263,48 +270,48 @@ public void testAddRemoveChildControllers() { router.pushController(RouterTransaction.with(parent)); - Assert.assertEquals(0, parent.getChildRouters().size()); - Assert.assertNull(child1.getParentController()); - Assert.assertNull(child2.getParentController()); + assertEquals(0, parent.getChildRouters().size()); + assertNull(child1.getParentController()); + assertNull(child2.getParentController()); Router childRouter = parent.getChildRouter((ViewGroup)parent.getView().findViewById(TestController.VIEW_ID)); childRouter.setPopsLastView(true); childRouter.setRoot(RouterTransaction.with(child1)); - Assert.assertEquals(1, parent.getChildRouters().size()); - Assert.assertEquals(childRouter, parent.getChildRouters().get(0)); - Assert.assertEquals(1, childRouter.getBackstackSize()); - Assert.assertEquals(child1, childRouter.getControllers().get(0)); - Assert.assertEquals(parent, child1.getParentController()); - Assert.assertNull(child2.getParentController()); + assertEquals(1, parent.getChildRouters().size()); + assertEquals(childRouter, parent.getChildRouters().get(0)); + assertEquals(1, childRouter.getBackstackSize()); + assertEquals(child1, childRouter.getControllers().get(0)); + assertEquals(parent, child1.getParentController()); + assertNull(child2.getParentController()); childRouter = parent.getChildRouter((ViewGroup)parent.getView().findViewById(TestController.VIEW_ID)); childRouter.pushController(RouterTransaction.with(child2)); - Assert.assertEquals(1, parent.getChildRouters().size()); - Assert.assertEquals(childRouter, parent.getChildRouters().get(0)); - Assert.assertEquals(2, childRouter.getBackstackSize()); - Assert.assertEquals(child1, childRouter.getControllers().get(0)); - Assert.assertEquals(child2, childRouter.getControllers().get(1)); - Assert.assertEquals(parent, child1.getParentController()); - Assert.assertEquals(parent, child2.getParentController()); + assertEquals(1, parent.getChildRouters().size()); + assertEquals(childRouter, parent.getChildRouters().get(0)); + assertEquals(2, childRouter.getBackstackSize()); + assertEquals(child1, childRouter.getControllers().get(0)); + assertEquals(child2, childRouter.getControllers().get(1)); + assertEquals(parent, child1.getParentController()); + assertEquals(parent, child2.getParentController()); childRouter.popController(child2); - Assert.assertEquals(1, parent.getChildRouters().size()); - Assert.assertEquals(childRouter, parent.getChildRouters().get(0)); - Assert.assertEquals(1, childRouter.getBackstackSize()); - Assert.assertEquals(child1, childRouter.getControllers().get(0)); - Assert.assertEquals(parent, child1.getParentController()); - Assert.assertNull(child2.getParentController()); + assertEquals(1, parent.getChildRouters().size()); + assertEquals(childRouter, parent.getChildRouters().get(0)); + assertEquals(1, childRouter.getBackstackSize()); + assertEquals(child1, childRouter.getControllers().get(0)); + assertEquals(parent, child1.getParentController()); + assertNull(child2.getParentController()); childRouter.popController(child1); - Assert.assertEquals(1, parent.getChildRouters().size()); - Assert.assertEquals(childRouter, parent.getChildRouters().get(0)); - Assert.assertEquals(0, childRouter.getBackstackSize()); - Assert.assertNull(child1.getParentController()); - Assert.assertNull(child2.getParentController()); + assertEquals(1, parent.getChildRouters().size()); + assertEquals(childRouter, parent.getChildRouters().get(0)); + assertEquals(0, childRouter.getBackstackSize()); + assertNull(child1.getParentController()); + assertNull(child2.getParentController()); } @Test @@ -316,9 +323,9 @@ public void testAddRemoveChildRouters() { router.pushController(RouterTransaction.with(parent)); - Assert.assertEquals(0, parent.getChildRouters().size()); - Assert.assertNull(child1.getParentController()); - Assert.assertNull(child2.getParentController()); + assertEquals(0, parent.getChildRouters().size()); + assertNull(child1.getParentController()); + assertNull(child2.getParentController()); Router childRouter1 = parent.getChildRouter((ViewGroup)parent.getView().findViewById(TestController.CHILD_VIEW_ID_1)); Router childRouter2 = parent.getChildRouter((ViewGroup)parent.getView().findViewById(TestController.CHILD_VIEW_ID_2)); @@ -326,37 +333,37 @@ public void testAddRemoveChildRouters() { childRouter1.setRoot(RouterTransaction.with(child1)); childRouter2.setRoot(RouterTransaction.with(child2)); - Assert.assertEquals(2, parent.getChildRouters().size()); - Assert.assertEquals(childRouter1, parent.getChildRouters().get(0)); - Assert.assertEquals(childRouter2, parent.getChildRouters().get(1)); - Assert.assertEquals(1, childRouter1.getBackstackSize()); - Assert.assertEquals(1, childRouter2.getBackstackSize()); - Assert.assertEquals(child1, childRouter1.getControllers().get(0)); - Assert.assertEquals(child2, childRouter2.getControllers().get(0)); - Assert.assertEquals(parent, child1.getParentController()); - Assert.assertEquals(parent, child2.getParentController()); + assertEquals(2, parent.getChildRouters().size()); + assertEquals(childRouter1, parent.getChildRouters().get(0)); + assertEquals(childRouter2, parent.getChildRouters().get(1)); + assertEquals(1, childRouter1.getBackstackSize()); + assertEquals(1, childRouter2.getBackstackSize()); + assertEquals(child1, childRouter1.getControllers().get(0)); + assertEquals(child2, childRouter2.getControllers().get(0)); + assertEquals(parent, child1.getParentController()); + assertEquals(parent, child2.getParentController()); parent.removeChildRouter(childRouter2); - Assert.assertEquals(1, parent.getChildRouters().size()); - Assert.assertEquals(childRouter1, parent.getChildRouters().get(0)); - Assert.assertEquals(1, childRouter1.getBackstackSize()); - Assert.assertEquals(0, childRouter2.getBackstackSize()); - Assert.assertEquals(child1, childRouter1.getControllers().get(0)); - Assert.assertEquals(parent, child1.getParentController()); - Assert.assertNull(child2.getParentController()); + assertEquals(1, parent.getChildRouters().size()); + assertEquals(childRouter1, parent.getChildRouters().get(0)); + assertEquals(1, childRouter1.getBackstackSize()); + assertEquals(0, childRouter2.getBackstackSize()); + assertEquals(child1, childRouter1.getControllers().get(0)); + assertEquals(parent, child1.getParentController()); + assertNull(child2.getParentController()); parent.removeChildRouter(childRouter1); - Assert.assertEquals(0, parent.getChildRouters().size()); - Assert.assertEquals(0, childRouter1.getBackstackSize()); - Assert.assertEquals(0, childRouter2.getBackstackSize()); - Assert.assertNull(child1.getParentController()); - Assert.assertNull(child2.getParentController()); + assertEquals(0, parent.getChildRouters().size()); + assertEquals(0, childRouter1.getBackstackSize()); + assertEquals(0, childRouter2.getBackstackSize()); + assertNull(child1.getParentController()); + assertNull(child2.getParentController()); } private void assertCalls(CallState callState, TestController controller) { - Assert.assertEquals("Expected call counts and controller call counts do not match.", callState, controller.currentCallState); + assertEquals("Expected call counts and controller call counts do not match.", callState, controller.currentCallState); } } diff --git a/conductor/src/test/java/com/bluelinelabs/conductor/ControllerTransactionTests.java b/conductor/src/test/java/com/bluelinelabs/conductor/ControllerTransactionTests.java index 37b39ad9..8cced734 100644 --- a/conductor/src/test/java/com/bluelinelabs/conductor/ControllerTransactionTests.java +++ b/conductor/src/test/java/com/bluelinelabs/conductor/ControllerTransactionTests.java @@ -4,11 +4,12 @@ import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler; import com.bluelinelabs.conductor.changehandler.VerticalChangeHandler; - -import junit.framework.Assert; +import com.bluelinelabs.conductor.util.TestController; import org.junit.Test; +import static org.junit.Assert.assertEquals; + public class ControllerTransactionTests { @Test @@ -22,10 +23,10 @@ public void testRouterSaveRestore() { RouterTransaction restoredTransaction = new RouterTransaction(bundle); - Assert.assertEquals(transaction.controller.getClass(), restoredTransaction.controller.getClass()); - Assert.assertEquals(transaction.pushChangeHandler().getClass(), restoredTransaction.pushChangeHandler().getClass()); - Assert.assertEquals(transaction.popChangeHandler().getClass(), restoredTransaction.popChangeHandler().getClass()); - Assert.assertEquals(transaction.tag(), restoredTransaction.tag()); + assertEquals(transaction.controller.getClass(), restoredTransaction.controller.getClass()); + assertEquals(transaction.pushChangeHandler().getClass(), restoredTransaction.pushChangeHandler().getClass()); + assertEquals(transaction.popChangeHandler().getClass(), restoredTransaction.popChangeHandler().getClass()); + assertEquals(transaction.tag(), restoredTransaction.tag()); } } diff --git a/conductor/src/test/java/com/bluelinelabs/conductor/ReattachCaseTests.java b/conductor/src/test/java/com/bluelinelabs/conductor/ReattachCaseTests.java index 94ce6876..1d00d21e 100644 --- a/conductor/src/test/java/com/bluelinelabs/conductor/ReattachCaseTests.java +++ b/conductor/src/test/java/com/bluelinelabs/conductor/ReattachCaseTests.java @@ -3,13 +3,19 @@ import android.os.Bundle; import android.view.ViewGroup; -import org.junit.Assert; +import com.bluelinelabs.conductor.util.ActivityProxy; +import com.bluelinelabs.conductor.util.MockChangeHandler; +import com.bluelinelabs.conductor.util.TestController; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + @RunWith(RobolectricTestRunner.class) @Config(manifest = Config.NONE) public class ReattachCaseTests { @@ -39,33 +45,33 @@ public void testNeedsAttachingOnPauseAndOrientation() { .pushChangeHandler(MockChangeHandler.defaultHandler()) .popChangeHandler(MockChangeHandler.defaultHandler())); - Assert.assertTrue(controllerA.isAttached()); - Assert.assertFalse(controllerB.isAttached()); + assertTrue(controllerA.isAttached()); + assertFalse(controllerB.isAttached()); sleepWakeDevice(); - Assert.assertTrue(controllerA.isAttached()); - Assert.assertFalse(controllerB.isAttached()); + assertTrue(controllerA.isAttached()); + assertFalse(controllerB.isAttached()); router.pushController(RouterTransaction.with(controllerB) .pushChangeHandler(MockChangeHandler.defaultHandler()) .popChangeHandler(MockChangeHandler.defaultHandler())); - Assert.assertFalse(controllerA.isAttached()); - Assert.assertTrue(controllerB.isAttached()); + assertFalse(controllerA.isAttached()); + assertTrue(controllerB.isAttached()); activityProxy.rotate(); router.rebindIfNeeded(); - Assert.assertFalse(controllerA.isAttached()); - Assert.assertTrue(controllerB.isAttached()); + assertFalse(controllerA.isAttached()); + assertTrue(controllerB.isAttached()); } @Test public void testChildNeedsAttachOnPauseAndOrientation() { - final TestController controllerA = new TestController(); - final TestController childController = new TestController(); - final TestController controllerB = new TestController(); + final Controller controllerA = new TestController(); + final Controller childController = new TestController(); + final Controller controllerB = new TestController(); router.pushController(RouterTransaction.with(controllerA) .pushChangeHandler(MockChangeHandler.defaultHandler()) @@ -76,31 +82,31 @@ public void testChildNeedsAttachOnPauseAndOrientation() { .pushChangeHandler(MockChangeHandler.defaultHandler()) .popChangeHandler(MockChangeHandler.defaultHandler())); - Assert.assertTrue(controllerA.isAttached()); - Assert.assertTrue(childController.isAttached()); - Assert.assertFalse(controllerB.isAttached()); + assertTrue(controllerA.isAttached()); + assertTrue(childController.isAttached()); + assertFalse(controllerB.isAttached()); sleepWakeDevice(); - Assert.assertTrue(controllerA.isAttached()); - Assert.assertTrue(childController.isAttached()); - Assert.assertFalse(controllerB.isAttached()); + assertTrue(controllerA.isAttached()); + assertTrue(childController.isAttached()); + assertFalse(controllerB.isAttached()); router.pushController(RouterTransaction.with(controllerB) .pushChangeHandler(MockChangeHandler.defaultHandler()) .popChangeHandler(MockChangeHandler.defaultHandler())); - Assert.assertFalse(controllerA.isAttached()); - Assert.assertFalse(childController.isAttached()); - Assert.assertTrue(controllerB.isAttached()); + assertFalse(controllerA.isAttached()); + assertFalse(childController.isAttached()); + assertTrue(controllerB.isAttached()); activityProxy.rotate(); router.rebindIfNeeded(); - Assert.assertFalse(controllerA.isAttached()); - Assert.assertFalse(childController.isAttached()); - Assert.assertTrue(childController.getNeedsAttach()); - Assert.assertTrue(controllerB.isAttached()); + assertFalse(controllerA.isAttached()); + assertFalse(childController.isAttached()); + assertTrue(childController.getNeedsAttach()); + assertTrue(controllerB.isAttached()); } @Test @@ -113,9 +119,9 @@ public void testChildHandleBackOnOrientation() { .pushChangeHandler(MockChangeHandler.defaultHandler()) .popChangeHandler(MockChangeHandler.defaultHandler())); - Assert.assertTrue(controllerA.isAttached()); - Assert.assertFalse(controllerB.isAttached()); - Assert.assertFalse(childController.isAttached()); + assertTrue(controllerA.isAttached()); + assertFalse(controllerB.isAttached()); + assertFalse(childController.isAttached()); router.pushController(RouterTransaction.with(controllerB) .pushChangeHandler(MockChangeHandler.defaultHandler()) @@ -127,28 +133,28 @@ public void testChildHandleBackOnOrientation() { .pushChangeHandler(MockChangeHandler.defaultHandler()) .popChangeHandler(MockChangeHandler.defaultHandler())); - Assert.assertFalse(controllerA.isAttached()); - Assert.assertTrue(controllerB.isAttached()); - Assert.assertTrue(childController.isAttached()); + assertFalse(controllerA.isAttached()); + assertTrue(controllerB.isAttached()); + assertTrue(childController.isAttached()); activityProxy.rotate(); router.rebindIfNeeded(); - Assert.assertFalse(controllerA.isAttached()); - Assert.assertTrue(controllerB.isAttached()); - Assert.assertTrue(childController.isAttached()); + assertFalse(controllerA.isAttached()); + assertTrue(controllerB.isAttached()); + assertTrue(childController.isAttached()); router.handleBack(); - Assert.assertFalse(controllerA.isAttached()); - Assert.assertTrue(controllerB.isAttached()); - Assert.assertFalse(childController.isAttached()); + assertFalse(controllerA.isAttached()); + assertTrue(controllerB.isAttached()); + assertFalse(childController.isAttached()); router.handleBack(); - Assert.assertTrue(controllerA.isAttached()); - Assert.assertFalse(controllerB.isAttached()); - Assert.assertFalse(childController.isAttached()); + assertTrue(controllerA.isAttached()); + assertFalse(controllerB.isAttached()); + assertFalse(childController.isAttached()); } // Attempt to test https://github.com/bluelinelabs/Conductor/issues/86#issuecomment-231381271 @@ -162,9 +168,9 @@ public void testReusedChildRouterHandleBackOnOrientation() { .pushChangeHandler(MockChangeHandler.defaultHandler()) .popChangeHandler(MockChangeHandler.defaultHandler())); - Assert.assertTrue(controllerA.isAttached()); - Assert.assertFalse(controllerB.isAttached()); - Assert.assertFalse(childController.isAttached()); + assertTrue(controllerA.isAttached()); + assertFalse(controllerB.isAttached()); + assertFalse(childController.isAttached()); router.pushController(RouterTransaction.with(controllerB) .pushChangeHandler(MockChangeHandler.defaultHandler()) @@ -176,31 +182,31 @@ public void testReusedChildRouterHandleBackOnOrientation() { .pushChangeHandler(MockChangeHandler.defaultHandler()) .popChangeHandler(MockChangeHandler.defaultHandler())); - Assert.assertFalse(controllerA.isAttached()); - Assert.assertTrue(controllerB.isAttached()); - Assert.assertTrue(childController.isAttached()); + assertFalse(controllerA.isAttached()); + assertTrue(controllerB.isAttached()); + assertTrue(childController.isAttached()); router.handleBack(); - Assert.assertFalse(controllerA.isAttached()); - Assert.assertTrue(controllerB.isAttached()); - Assert.assertFalse(childController.isAttached()); + assertFalse(controllerA.isAttached()); + assertTrue(controllerB.isAttached()); + assertFalse(childController.isAttached()); childController = new TestController(); childRouter.pushController(RouterTransaction.with(childController) .pushChangeHandler(MockChangeHandler.defaultHandler()) .popChangeHandler(MockChangeHandler.defaultHandler())); - Assert.assertFalse(controllerA.isAttached()); - Assert.assertTrue(controllerB.isAttached()); - Assert.assertTrue(childController.isAttached()); + assertFalse(controllerA.isAttached()); + assertTrue(controllerB.isAttached()); + assertTrue(childController.isAttached()); activityProxy.rotate(); router.rebindIfNeeded(); - Assert.assertFalse(controllerA.isAttached()); - Assert.assertTrue(controllerB.isAttached()); - Assert.assertTrue(childController.isAttached()); + assertFalse(controllerA.isAttached()); + assertTrue(controllerB.isAttached()); + assertTrue(childController.isAttached()); router.handleBack(); @@ -209,21 +215,21 @@ public void testReusedChildRouterHandleBackOnOrientation() { .pushChangeHandler(MockChangeHandler.defaultHandler()) .popChangeHandler(MockChangeHandler.defaultHandler())); - Assert.assertFalse(controllerA.isAttached()); - Assert.assertTrue(controllerB.isAttached()); - Assert.assertTrue(childController.isAttached()); + assertFalse(controllerA.isAttached()); + assertTrue(controllerB.isAttached()); + assertTrue(childController.isAttached()); router.handleBack(); - Assert.assertFalse(controllerA.isAttached()); - Assert.assertTrue(controllerB.isAttached()); - Assert.assertFalse(childController.isAttached()); + assertFalse(controllerA.isAttached()); + assertTrue(controllerB.isAttached()); + assertFalse(childController.isAttached()); router.handleBack(); - Assert.assertTrue(controllerA.isAttached()); - Assert.assertFalse(controllerB.isAttached()); - Assert.assertFalse(childController.isAttached()); + assertTrue(controllerA.isAttached()); + assertFalse(controllerB.isAttached()); + assertFalse(childController.isAttached()); } private void sleepWakeDevice() { diff --git a/conductor/src/test/java/com/bluelinelabs/conductor/RouterChangeHandlerTests.java b/conductor/src/test/java/com/bluelinelabs/conductor/RouterChangeHandlerTests.java new file mode 100644 index 00000000..4f92fafd --- /dev/null +++ b/conductor/src/test/java/com/bluelinelabs/conductor/RouterChangeHandlerTests.java @@ -0,0 +1,239 @@ +package com.bluelinelabs.conductor; + +import android.view.View; + +import com.bluelinelabs.conductor.util.ActivityProxy; +import com.bluelinelabs.conductor.util.ListUtils; +import com.bluelinelabs.conductor.util.MockChangeHandler; +import com.bluelinelabs.conductor.util.TestController; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; + +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +@RunWith(RobolectricTestRunner.class) +@Config(manifest = Config.NONE) +public class RouterChangeHandlerTests { + + private Router router; + + @Before + public void setup() { + ActivityProxy activityProxy = new ActivityProxy().create(null).start().resume(); + router = Conductor.attachRouter(activityProxy.getActivity(), activityProxy.getView(), null); + } + + @Test + public void testSetRootHandler() { + MockChangeHandler handler = MockChangeHandler.taggedHandler("root", true); + TestController rootController = new TestController(); + router.setRoot(RouterTransaction.with(rootController).pushChangeHandler(handler)); + + assertTrue(rootController.changeHandlerHistory.isValidHistory); + assertNull(rootController.changeHandlerHistory.latestFromView()); + assertNotNull(rootController.changeHandlerHistory.latestToView()); + assertEquals(rootController.getView(), rootController.changeHandlerHistory.latestToView()); + assertTrue(rootController.changeHandlerHistory.latestIsPush()); + assertEquals(handler.tag, rootController.changeHandlerHistory.latestChangeHandler().tag); + } + + @Test + public void testPushPopHandlers() { + TestController rootController = new TestController(); + router.setRoot(RouterTransaction.with(rootController).pushChangeHandler(MockChangeHandler.defaultHandler())); + View rootView = rootController.getView(); + + MockChangeHandler pushHandler = MockChangeHandler.taggedHandler("push", true); + MockChangeHandler popHandler = MockChangeHandler.taggedHandler("pop", true); + TestController pushController = new TestController(); + router.pushController(RouterTransaction.with(pushController).pushChangeHandler(pushHandler).popChangeHandler(popHandler)); + + assertTrue(rootController.changeHandlerHistory.isValidHistory); + assertTrue(pushController.changeHandlerHistory.isValidHistory); + + assertNotNull(pushController.changeHandlerHistory.latestFromView()); + assertNotNull(pushController.changeHandlerHistory.latestToView()); + assertEquals(rootView, pushController.changeHandlerHistory.latestFromView()); + assertEquals(pushController.getView(), pushController.changeHandlerHistory.latestToView()); + assertTrue(pushController.changeHandlerHistory.latestIsPush()); + assertEquals(pushHandler.tag, pushController.changeHandlerHistory.latestChangeHandler().tag); + + View pushView = pushController.getView(); + router.popController(pushController); + + assertNotNull(pushController.changeHandlerHistory.latestFromView()); + assertNotNull(pushController.changeHandlerHistory.latestToView()); + assertEquals(pushView, pushController.changeHandlerHistory.fromViewAt(1)); + assertEquals(rootController.getView(), pushController.changeHandlerHistory.latestToView()); + assertFalse(pushController.changeHandlerHistory.latestIsPush()); + assertEquals(popHandler.tag, pushController.changeHandlerHistory.latestChangeHandler().tag); + } + + @Test + public void testResetRootHandlers() { + TestController initialController1 = new TestController(); + MockChangeHandler initialPushHandler1 = MockChangeHandler.taggedHandler("initialPush1", true); + MockChangeHandler initialPopHandler1 = MockChangeHandler.taggedHandler("initialPop1", true); + router.setRoot(RouterTransaction.with(initialController1).pushChangeHandler(initialPushHandler1).popChangeHandler(initialPopHandler1)); + TestController initialController2 = new TestController(); + MockChangeHandler initialPushHandler2 = MockChangeHandler.taggedHandler("initialPush2", false); + MockChangeHandler initialPopHandler2 = MockChangeHandler.taggedHandler("initialPop2", false); + router.pushController(RouterTransaction.with(initialController2).pushChangeHandler(initialPushHandler2).popChangeHandler(initialPopHandler2)); + + View initialView1 = initialController1.getView(); + View initialView2 = initialController2.getView(); + + TestController newRootController = new TestController(); + MockChangeHandler newRootHandler = MockChangeHandler.taggedHandler("newRootHandler", true); + + router.setRoot(RouterTransaction.with(newRootController).pushChangeHandler(newRootHandler)); + + assertTrue(initialController1.changeHandlerHistory.isValidHistory); + assertTrue(initialController2.changeHandlerHistory.isValidHistory); + assertTrue(newRootController.changeHandlerHistory.isValidHistory); + + assertEquals(3, initialController1.changeHandlerHistory.size()); + assertEquals(2, initialController2.changeHandlerHistory.size()); + assertEquals(1, newRootController.changeHandlerHistory.size()); + + assertNotNull(initialController1.changeHandlerHistory.latestToView()); + assertEquals(newRootController.getView(), initialController1.changeHandlerHistory.latestToView()); + assertEquals(initialView1, initialController1.changeHandlerHistory.latestFromView()); + assertEquals(newRootHandler.tag, initialController1.changeHandlerHistory.latestChangeHandler().tag); + assertTrue(initialController1.changeHandlerHistory.latestIsPush()); + + assertNull(initialController2.changeHandlerHistory.latestToView()); + assertEquals(initialView2, initialController2.changeHandlerHistory.latestFromView()); + assertEquals(newRootHandler.tag, initialController2.changeHandlerHistory.latestChangeHandler().tag); + assertTrue(initialController2.changeHandlerHistory.latestIsPush()); + + assertNotNull(newRootController.changeHandlerHistory.latestToView()); + assertEquals(newRootController.getView(), newRootController.changeHandlerHistory.latestToView()); + assertEquals(initialView1, newRootController.changeHandlerHistory.latestFromView()); + assertEquals(newRootHandler.tag, newRootController.changeHandlerHistory.latestChangeHandler().tag); + assertTrue(newRootController.changeHandlerHistory.latestIsPush()); + } + + @Test + public void testSetBackstackHandlers() { + TestController initialController1 = new TestController(); + MockChangeHandler initialPushHandler1 = MockChangeHandler.taggedHandler("initialPush1", true); + MockChangeHandler initialPopHandler1 = MockChangeHandler.taggedHandler("initialPop1", true); + router.setRoot(RouterTransaction.with(initialController1).pushChangeHandler(initialPushHandler1).popChangeHandler(initialPopHandler1)); + TestController initialController2 = new TestController(); + MockChangeHandler initialPushHandler2 = MockChangeHandler.taggedHandler("initialPush2", false); + MockChangeHandler initialPopHandler2 = MockChangeHandler.taggedHandler("initialPop2", false); + router.pushController(RouterTransaction.with(initialController2).pushChangeHandler(initialPushHandler2).popChangeHandler(initialPopHandler2)); + + View initialView1 = initialController1.getView(); + View initialView2 = initialController2.getView(); + + TestController newController1 = new TestController(); + TestController newController2 = new TestController(); + MockChangeHandler setBackstackHandler = MockChangeHandler.taggedHandler("setBackstackHandler", true); + + List newBackstack = ListUtils.listOf( + RouterTransaction.with(newController1), + RouterTransaction.with(newController2) + ); + + router.setBackstack(newBackstack, setBackstackHandler); + + assertTrue(initialController1.changeHandlerHistory.isValidHistory); + assertTrue(initialController2.changeHandlerHistory.isValidHistory); + assertTrue(newController1.changeHandlerHistory.isValidHistory); + + assertEquals(3, initialController1.changeHandlerHistory.size()); + assertEquals(2, initialController2.changeHandlerHistory.size()); + assertEquals(0, newController1.changeHandlerHistory.size()); + assertEquals(1, newController2.changeHandlerHistory.size()); + + assertNotNull(initialController1.changeHandlerHistory.latestToView()); + assertEquals(newController2.getView(), initialController1.changeHandlerHistory.latestToView()); + assertEquals(initialView1, initialController1.changeHandlerHistory.latestFromView()); + assertEquals(setBackstackHandler.tag, initialController1.changeHandlerHistory.latestChangeHandler().tag); + assertTrue(initialController1.changeHandlerHistory.latestIsPush()); + + assertNull(initialController2.changeHandlerHistory.latestToView()); + assertEquals(initialView2, initialController2.changeHandlerHistory.latestFromView()); + assertEquals(setBackstackHandler.tag, initialController2.changeHandlerHistory.latestChangeHandler().tag); + assertTrue(initialController2.changeHandlerHistory.latestIsPush()); + + assertNotNull(newController2.changeHandlerHistory.latestToView()); + assertEquals(newController2.getView(), newController2.changeHandlerHistory.latestToView()); + assertEquals(initialView1, newController2.changeHandlerHistory.latestFromView()); + assertEquals(setBackstackHandler.tag, newController2.changeHandlerHistory.latestChangeHandler().tag); + assertTrue(newController2.changeHandlerHistory.latestIsPush()); + } + + @Test + public void testSetBackstackWithTwoVisibleHandlers() { + TestController initialController1 = new TestController(); + MockChangeHandler initialPushHandler1 = MockChangeHandler.taggedHandler("initialPush1", true); + MockChangeHandler initialPopHandler1 = MockChangeHandler.taggedHandler("initialPop1", true); + router.setRoot(RouterTransaction.with(initialController1).pushChangeHandler(initialPushHandler1).popChangeHandler(initialPopHandler1)); + TestController initialController2 = new TestController(); + MockChangeHandler initialPushHandler2 = MockChangeHandler.taggedHandler("initialPush2", false); + MockChangeHandler initialPopHandler2 = MockChangeHandler.taggedHandler("initialPop2", false); + router.pushController(RouterTransaction.with(initialController2).pushChangeHandler(initialPushHandler2).popChangeHandler(initialPopHandler2)); + + View initialView1 = initialController1.getView(); + View initialView2 = initialController2.getView(); + + TestController newController1 = new TestController(); + TestController newController2 = new TestController(); + MockChangeHandler setBackstackHandler = MockChangeHandler.taggedHandler("setBackstackHandler", true); + + List newBackstack = ListUtils.listOf( + RouterTransaction.with(newController1), + RouterTransaction.with(newController2).pushChangeHandler(MockChangeHandler.noRemoveViewOnPushHandler()) + ); + + router.setBackstack(newBackstack, setBackstackHandler); + + assertTrue(initialController1.changeHandlerHistory.isValidHistory); + assertTrue(initialController2.changeHandlerHistory.isValidHistory); + assertTrue(newController1.changeHandlerHistory.isValidHistory); + + assertEquals(3, initialController1.changeHandlerHistory.size()); + assertEquals(2, initialController2.changeHandlerHistory.size()); + assertEquals(2, newController1.changeHandlerHistory.size()); + assertEquals(1, newController2.changeHandlerHistory.size()); + + assertNotNull(initialController1.changeHandlerHistory.latestToView()); + assertEquals(newController1.getView(), initialController1.changeHandlerHistory.latestToView()); + assertEquals(initialView1, initialController1.changeHandlerHistory.latestFromView()); + assertEquals(setBackstackHandler.tag, initialController1.changeHandlerHistory.latestChangeHandler().tag); + assertTrue(initialController1.changeHandlerHistory.latestIsPush()); + + assertNull(initialController2.changeHandlerHistory.latestToView()); + assertEquals(initialView2, initialController2.changeHandlerHistory.latestFromView()); + assertEquals(setBackstackHandler.tag, initialController2.changeHandlerHistory.latestChangeHandler().tag); + assertTrue(initialController2.changeHandlerHistory.latestIsPush()); + + assertNotNull(newController1.changeHandlerHistory.latestToView()); + assertEquals(newController1.getView(), newController1.changeHandlerHistory.toViewAt(0)); + assertEquals(newController2.getView(), newController1.changeHandlerHistory.latestToView()); + assertEquals(initialView1, newController1.changeHandlerHistory.fromViewAt(0)); + assertEquals(newController1.getView(), newController1.changeHandlerHistory.latestFromView()); + assertEquals(setBackstackHandler.tag, newController1.changeHandlerHistory.latestChangeHandler().tag); + assertTrue(newController1.changeHandlerHistory.latestIsPush()); + + assertNotNull(newController2.changeHandlerHistory.latestToView()); + assertEquals(newController2.getView(), newController2.changeHandlerHistory.latestToView()); + assertEquals(newController1.getView(), newController2.changeHandlerHistory.latestFromView()); + assertEquals(setBackstackHandler.tag, newController2.changeHandlerHistory.latestChangeHandler().tag); + assertTrue(newController2.changeHandlerHistory.latestIsPush()); + } + +} diff --git a/conductor/src/test/java/com/bluelinelabs/conductor/RouterTests.java b/conductor/src/test/java/com/bluelinelabs/conductor/RouterTests.java index cce117e5..5ddf521e 100644 --- a/conductor/src/test/java/com/bluelinelabs/conductor/RouterTests.java +++ b/conductor/src/test/java/com/bluelinelabs/conductor/RouterTests.java @@ -1,15 +1,23 @@ package com.bluelinelabs.conductor; -import org.junit.Assert; +import com.bluelinelabs.conductor.util.ActivityProxy; +import com.bluelinelabs.conductor.util.ListUtils; +import com.bluelinelabs.conductor.util.MockChangeHandler; +import com.bluelinelabs.conductor.util.TestController; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; -import java.util.ArrayList; import java.util.List; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + @RunWith(RobolectricTestRunner.class) @Config(manifest = Config.NONE) public class RouterTests { @@ -28,13 +36,13 @@ public void testSetRoot() { Controller rootController = new TestController(); - Assert.assertFalse(router.hasRootController()); + assertFalse(router.hasRootController()); router.setRoot(RouterTransaction.with(rootController).tag(rootTag)); - Assert.assertTrue(router.hasRootController()); + assertTrue(router.hasRootController()); - Assert.assertEquals(rootController, router.getControllerWithTag(rootTag)); + assertEquals(rootController, router.getControllerWithTag(rootTag)); } @Test @@ -48,8 +56,8 @@ public void testSetNewRoot() { router.setRoot(RouterTransaction.with(oldRootController).tag(oldRootTag)); router.setRoot(RouterTransaction.with(newRootController).tag(newRootTag)); - Assert.assertNull(router.getControllerWithTag(oldRootTag)); - Assert.assertEquals(newRootController, router.getControllerWithTag(newRootTag)); + assertNull(router.getControllerWithTag(oldRootTag)); + assertEquals(newRootController, router.getControllerWithTag(newRootTag)); } @Test @@ -58,8 +66,8 @@ public void testGetByInstanceId() { router.pushController(RouterTransaction.with(controller)); - Assert.assertEquals(controller, router.getControllerWithInstanceId(controller.getInstanceId())); - Assert.assertNull(router.getControllerWithInstanceId("fake id")); + assertEquals(controller, router.getControllerWithInstanceId(controller.getInstanceId())); + assertNull(router.getControllerWithInstanceId("fake id")); } @Test @@ -76,8 +84,8 @@ public void testGetByTag() { router.pushController(RouterTransaction.with(controller2) .tag(controller2Tag)); - Assert.assertEquals(controller1, router.getControllerWithTag(controller1Tag)); - Assert.assertEquals(controller2, router.getControllerWithTag(controller2Tag)); + assertEquals(controller1, router.getControllerWithTag(controller1Tag)); + assertEquals(controller2, router.getControllerWithTag(controller2Tag)); } @Test @@ -91,26 +99,26 @@ public void testPushPopControllers() { router.pushController(RouterTransaction.with(controller1) .tag(controller1Tag)); - Assert.assertEquals(1, router.getBackstackSize()); + assertEquals(1, router.getBackstackSize()); router.pushController(RouterTransaction.with(controller2) .tag(controller2Tag)); - Assert.assertEquals(2, router.getBackstackSize()); + assertEquals(2, router.getBackstackSize()); router.popCurrentController(); - Assert.assertEquals(1, router.getBackstackSize()); + assertEquals(1, router.getBackstackSize()); - Assert.assertEquals(controller1, router.getControllerWithTag(controller1Tag)); - Assert.assertNull(router.getControllerWithTag(controller2Tag)); + assertEquals(controller1, router.getControllerWithTag(controller1Tag)); + assertNull(router.getControllerWithTag(controller2Tag)); router.popCurrentController(); - Assert.assertEquals(0, router.getBackstackSize()); + assertEquals(0, router.getBackstackSize()); - Assert.assertNull(router.getControllerWithTag(controller1Tag)); - Assert.assertNull(router.getControllerWithTag(controller2Tag)); + assertNull(router.getControllerWithTag(controller1Tag)); + assertNull(router.getControllerWithTag(controller2Tag)); } @Test @@ -139,11 +147,11 @@ public void testPopToTag() { router.popToTag(controller2Tag); - Assert.assertEquals(2, router.getBackstackSize()); - Assert.assertEquals(controller1, router.getControllerWithTag(controller1Tag)); - Assert.assertEquals(controller2, router.getControllerWithTag(controller2Tag)); - Assert.assertNull(router.getControllerWithTag(controller3Tag)); - Assert.assertNull(router.getControllerWithTag(controller4Tag)); + assertEquals(2, router.getBackstackSize()); + assertEquals(controller1, router.getControllerWithTag(controller1Tag)); + assertEquals(controller2, router.getControllerWithTag(controller2Tag)); + assertNull(router.getControllerWithTag(controller3Tag)); + assertNull(router.getControllerWithTag(controller4Tag)); } @Test @@ -167,10 +175,10 @@ public void testPopNonCurrent() { router.popController(controller2); - Assert.assertEquals(2, router.getBackstackSize()); - Assert.assertEquals(controller1, router.getControllerWithTag(controller1Tag)); - Assert.assertNull(router.getControllerWithTag(controller2Tag)); - Assert.assertEquals(controller3, router.getControllerWithTag(controller3Tag)); + assertEquals(2, router.getBackstackSize()); + assertEquals(controller1, router.getControllerWithTag(controller1Tag)); + assertNull(router.getControllerWithTag(controller2Tag)); + assertEquals(controller3, router.getControllerWithTag(controller3Tag)); } @Test @@ -179,44 +187,36 @@ public void testSetBackstack() { 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); - + List backstack = ListUtils.listOf(rootTransaction, middleTransaction, topTransaction); router.setBackstack(backstack, null); - Assert.assertEquals(3, router.getBackstackSize()); + 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)); + assertEquals(rootTransaction, fetchedBackstack.get(0)); + assertEquals(middleTransaction, fetchedBackstack.get(1)); + assertEquals(topTransaction, fetchedBackstack.get(2)); } @Test public void testNewSetBackstack() { router.setRoot(RouterTransaction.with(new TestController())); - Assert.assertEquals(1, router.getBackstackSize()); + 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); - + List backstack = ListUtils.listOf(rootTransaction, middleTransaction, topTransaction); router.setBackstack(backstack, null); - Assert.assertEquals(3, router.getBackstackSize()); + 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)); + assertEquals(rootTransaction, fetchedBackstack.get(0)); + assertEquals(middleTransaction, fetchedBackstack.get(1)); + assertEquals(topTransaction, fetchedBackstack.get(2)); } @Test @@ -226,34 +226,30 @@ public void testNewSetBackstackWithNoRemoveViewOnPush() { router.setRoot(oldRootTransaction); router.pushController(oldTopTransaction); - Assert.assertEquals(2, router.getBackstackSize()); + assertEquals(2, router.getBackstackSize()); - Assert.assertTrue(oldRootTransaction.controller.isAttached()); - Assert.assertTrue(oldTopTransaction.controller.isAttached()); + assertTrue(oldRootTransaction.controller.isAttached()); + assertTrue(oldTopTransaction.controller.isAttached()); RouterTransaction rootTransaction = RouterTransaction.with(new TestController()); RouterTransaction middleTransaction = RouterTransaction.with(new TestController()).pushChangeHandler(MockChangeHandler.noRemoveViewOnPushHandler()); RouterTransaction topTransaction = RouterTransaction.with(new TestController()).pushChangeHandler(MockChangeHandler.noRemoveViewOnPushHandler()); - List backstack = new ArrayList<>(); - backstack.add(rootTransaction); - backstack.add(middleTransaction); - backstack.add(topTransaction); - + List backstack = ListUtils.listOf(rootTransaction, middleTransaction, topTransaction); router.setBackstack(backstack, null); - Assert.assertEquals(3, router.getBackstackSize()); + 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()); + assertEquals(rootTransaction, fetchedBackstack.get(0)); + assertEquals(middleTransaction, fetchedBackstack.get(1)); + assertEquals(topTransaction, fetchedBackstack.get(2)); + + assertFalse(oldRootTransaction.controller.isAttached()); + assertFalse(oldTopTransaction.controller.isAttached()); + assertTrue(rootTransaction.controller.isAttached()); + assertTrue(middleTransaction.controller.isAttached()); + assertTrue(topTransaction.controller.isAttached()); } @Test @@ -261,26 +257,23 @@ 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); - + List backstack = ListUtils.listOf(rootTransaction, topTransaction); router.setBackstack(backstack, null); - Assert.assertEquals(2, router.getBackstackSize()); + assertEquals(2, router.getBackstackSize()); List fetchedBackstack = router.getBackstack(); - Assert.assertEquals(rootTransaction, fetchedBackstack.get(0)); - Assert.assertEquals(topTransaction, fetchedBackstack.get(1)); + assertEquals(rootTransaction, fetchedBackstack.get(0)); + assertEquals(topTransaction, fetchedBackstack.get(1)); RouterTransaction newTopTransaction = RouterTransaction.with(new TestController()); router.replaceTopController(newTopTransaction); - Assert.assertEquals(2, router.getBackstackSize()); + assertEquals(2, router.getBackstackSize()); fetchedBackstack = router.getBackstack(); - Assert.assertEquals(rootTransaction, fetchedBackstack.get(0)); - Assert.assertEquals(newTopTransaction, fetchedBackstack.get(1)); + assertEquals(rootTransaction, fetchedBackstack.get(0)); + assertEquals(newTopTransaction, fetchedBackstack.get(1)); } @Test @@ -288,34 +281,31 @@ public void testReplaceTopControllerWithNoRemoveViewOnPush() { RouterTransaction rootTransaction = RouterTransaction.with(new TestController()); RouterTransaction topTransaction = RouterTransaction.with(new TestController()).pushChangeHandler(MockChangeHandler.noRemoveViewOnPushHandler()); - List backstack = new ArrayList<>(); - backstack.add(rootTransaction); - backstack.add(topTransaction); - + List backstack = ListUtils.listOf(rootTransaction, topTransaction); router.setBackstack(backstack, null); - Assert.assertEquals(2, router.getBackstackSize()); + assertEquals(2, router.getBackstackSize()); - Assert.assertTrue(rootTransaction.controller.isAttached()); - Assert.assertTrue(topTransaction.controller.isAttached()); + assertTrue(rootTransaction.controller.isAttached()); + assertTrue(topTransaction.controller.isAttached()); List fetchedBackstack = router.getBackstack(); - Assert.assertEquals(rootTransaction, fetchedBackstack.get(0)); - Assert.assertEquals(topTransaction, fetchedBackstack.get(1)); + assertEquals(rootTransaction, fetchedBackstack.get(0)); + assertEquals(topTransaction, fetchedBackstack.get(1)); RouterTransaction newTopTransaction = RouterTransaction.with(new TestController()).pushChangeHandler(MockChangeHandler.noRemoveViewOnPushHandler()); router.replaceTopController(newTopTransaction); newTopTransaction.pushChangeHandler().completeImmediately(); - Assert.assertEquals(2, router.getBackstackSize()); + assertEquals(2, router.getBackstackSize()); fetchedBackstack = router.getBackstack(); - Assert.assertEquals(rootTransaction, fetchedBackstack.get(0)); - Assert.assertEquals(newTopTransaction, fetchedBackstack.get(1)); + assertEquals(rootTransaction, fetchedBackstack.get(0)); + assertEquals(newTopTransaction, fetchedBackstack.get(1)); - Assert.assertTrue(rootTransaction.controller.isAttached()); - Assert.assertFalse(topTransaction.controller.isAttached()); - Assert.assertTrue(newTopTransaction.controller.isAttached()); + assertTrue(rootTransaction.controller.isAttached()); + assertFalse(topTransaction.controller.isAttached()); + 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 16cc9ab2..9a91ca0c 100644 --- a/conductor/src/test/java/com/bluelinelabs/conductor/TargetControllerTests.java +++ b/conductor/src/test/java/com/bluelinelabs/conductor/TargetControllerTests.java @@ -3,13 +3,19 @@ import android.os.Bundle; import android.view.ViewGroup; -import org.junit.Assert; +import com.bluelinelabs.conductor.util.ActivityProxy; +import com.bluelinelabs.conductor.util.MockChangeHandler; +import com.bluelinelabs.conductor.util.TestController; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + @RunWith(RobolectricTestRunner.class) @Config(manifest = Config.NONE) public class TargetControllerTests { @@ -34,8 +40,8 @@ public void testSiblingTarget() { final TestController controllerA = new TestController(); final TestController controllerB = new TestController(); - Assert.assertNull(controllerA.getTargetController()); - Assert.assertNull(controllerB.getTargetController()); + assertNull(controllerA.getTargetController()); + assertNull(controllerB.getTargetController()); router.pushController(RouterTransaction.with(controllerA) .pushChangeHandler(MockChangeHandler.defaultHandler()) @@ -47,8 +53,8 @@ public void testSiblingTarget() { .pushChangeHandler(MockChangeHandler.defaultHandler()) .popChangeHandler(MockChangeHandler.defaultHandler())); - Assert.assertNull(controllerA.getTargetController()); - Assert.assertEquals(controllerA, controllerB.getTargetController()); + assertNull(controllerA.getTargetController()); + assertEquals(controllerA, controllerB.getTargetController()); } @Test @@ -56,8 +62,8 @@ public void testParentChildTarget() { final TestController controllerA = new TestController(); final TestController controllerB = new TestController(); - Assert.assertNull(controllerA.getTargetController()); - Assert.assertNull(controllerB.getTargetController()); + assertNull(controllerA.getTargetController()); + assertNull(controllerB.getTargetController()); router.pushController(RouterTransaction.with(controllerA) .pushChangeHandler(MockChangeHandler.defaultHandler()) @@ -70,8 +76,8 @@ public void testParentChildTarget() { .pushChangeHandler(MockChangeHandler.defaultHandler()) .popChangeHandler(MockChangeHandler.defaultHandler())); - Assert.assertNull(controllerA.getTargetController()); - Assert.assertEquals(controllerA, controllerB.getTargetController()); + assertNull(controllerA.getTargetController()); + assertEquals(controllerA, controllerB.getTargetController()); } @Test @@ -79,8 +85,8 @@ public void testChildParentTarget() { final TestController controllerA = new TestController(); final TestController controllerB = new TestController(); - Assert.assertNull(controllerA.getTargetController()); - Assert.assertNull(controllerB.getTargetController()); + assertNull(controllerA.getTargetController()); + assertNull(controllerB.getTargetController()); router.pushController(RouterTransaction.with(controllerA) .pushChangeHandler(MockChangeHandler.defaultHandler()) @@ -93,8 +99,8 @@ public void testChildParentTarget() { .pushChangeHandler(MockChangeHandler.defaultHandler()) .popChangeHandler(MockChangeHandler.defaultHandler())); - Assert.assertNull(controllerB.getTargetController()); - Assert.assertEquals(controllerB, controllerA.getTargetController()); + assertNull(controllerB.getTargetController()); + assertEquals(controllerB, controllerA.getTargetController()); } } diff --git a/conductor/src/test/java/com/bluelinelabs/conductor/ViewAttachHandlerTests.java b/conductor/src/test/java/com/bluelinelabs/conductor/ViewAttachHandlerTests.java index bb30a3a9..6da8f333 100644 --- a/conductor/src/test/java/com/bluelinelabs/conductor/ViewAttachHandlerTests.java +++ b/conductor/src/test/java/com/bluelinelabs/conductor/ViewAttachHandlerTests.java @@ -7,14 +7,17 @@ import com.bluelinelabs.conductor.internal.ViewAttachHandler; import com.bluelinelabs.conductor.internal.ViewAttachHandler.ViewAttachListener; +import com.bluelinelabs.conductor.util.ActivityProxy; +import com.bluelinelabs.conductor.util.ViewUtils; -import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; +import static org.junit.Assert.assertEquals; + @RunWith(RobolectricTestRunner.class) @Config(manifest = Config.NONE) public class ViewAttachHandlerTests { @@ -35,28 +38,28 @@ public void testSimpleViewAttachDetach() { View view = new View(activity); viewAttachHandler.listenForAttach(view); - Assert.assertEquals(0, viewAttachListener.attaches); - Assert.assertEquals(0, viewAttachListener.detaches); + assertEquals(0, viewAttachListener.attaches); + assertEquals(0, viewAttachListener.detaches); ViewUtils.reportAttached(view, true); - Assert.assertEquals(1, viewAttachListener.attaches); - Assert.assertEquals(0, viewAttachListener.detaches); + assertEquals(1, viewAttachListener.attaches); + assertEquals(0, viewAttachListener.detaches); ViewUtils.reportAttached(view, true); - Assert.assertEquals(1, viewAttachListener.attaches); - Assert.assertEquals(0, viewAttachListener.detaches); + assertEquals(1, viewAttachListener.attaches); + assertEquals(0, viewAttachListener.detaches); ViewUtils.reportAttached(view, false); - Assert.assertEquals(1, viewAttachListener.attaches); - Assert.assertEquals(1, viewAttachListener.detaches); + assertEquals(1, viewAttachListener.attaches); + assertEquals(1, viewAttachListener.detaches); ViewUtils.reportAttached(view, false); - Assert.assertEquals(1, viewAttachListener.attaches); - Assert.assertEquals(1, viewAttachListener.detaches); + assertEquals(1, viewAttachListener.attaches); + assertEquals(1, viewAttachListener.detaches); ViewUtils.reportAttached(view, true); - Assert.assertEquals(2, viewAttachListener.attaches); - Assert.assertEquals(1, viewAttachListener.detaches); + assertEquals(2, viewAttachListener.attaches); + assertEquals(1, viewAttachListener.detaches); } @Test @@ -64,28 +67,28 @@ public void testSimpleViewGroupAttachDetach() { View view = new LinearLayout(activity); viewAttachHandler.listenForAttach(view); - Assert.assertEquals(0, viewAttachListener.attaches); - Assert.assertEquals(0, viewAttachListener.detaches); + assertEquals(0, viewAttachListener.attaches); + assertEquals(0, viewAttachListener.detaches); ViewUtils.reportAttached(view, true); - Assert.assertEquals(1, viewAttachListener.attaches); - Assert.assertEquals(0, viewAttachListener.detaches); + assertEquals(1, viewAttachListener.attaches); + assertEquals(0, viewAttachListener.detaches); ViewUtils.reportAttached(view, true); - Assert.assertEquals(1, viewAttachListener.attaches); - Assert.assertEquals(0, viewAttachListener.detaches); + assertEquals(1, viewAttachListener.attaches); + assertEquals(0, viewAttachListener.detaches); ViewUtils.reportAttached(view, false); - Assert.assertEquals(1, viewAttachListener.attaches); - Assert.assertEquals(1, viewAttachListener.detaches); + assertEquals(1, viewAttachListener.attaches); + assertEquals(1, viewAttachListener.detaches); ViewUtils.reportAttached(view, false); - Assert.assertEquals(1, viewAttachListener.attaches); - Assert.assertEquals(1, viewAttachListener.detaches); + assertEquals(1, viewAttachListener.attaches); + assertEquals(1, viewAttachListener.detaches); ViewUtils.reportAttached(view, true); - Assert.assertEquals(2, viewAttachListener.attaches); - Assert.assertEquals(1, viewAttachListener.detaches); + assertEquals(2, viewAttachListener.attaches); + assertEquals(1, viewAttachListener.detaches); } @Test @@ -95,37 +98,37 @@ public void testNestedViewGroupAttachDetach() { view.addView(child); viewAttachHandler.listenForAttach(view); - Assert.assertEquals(0, viewAttachListener.attaches); - Assert.assertEquals(0, viewAttachListener.detaches); + assertEquals(0, viewAttachListener.attaches); + assertEquals(0, viewAttachListener.detaches); ViewUtils.reportAttached(view, true, false); - Assert.assertEquals(0, viewAttachListener.attaches); - Assert.assertEquals(0, viewAttachListener.detaches); + assertEquals(0, viewAttachListener.attaches); + assertEquals(0, viewAttachListener.detaches); ViewUtils.reportAttached(child, true, false); - Assert.assertEquals(1, viewAttachListener.attaches); - Assert.assertEquals(0, viewAttachListener.detaches); + assertEquals(1, viewAttachListener.attaches); + assertEquals(0, viewAttachListener.detaches); ViewUtils.reportAttached(view, true, false); ViewUtils.reportAttached(child, true, false); - Assert.assertEquals(1, viewAttachListener.attaches); - Assert.assertEquals(0, viewAttachListener.detaches); + assertEquals(1, viewAttachListener.attaches); + assertEquals(0, viewAttachListener.detaches); ViewUtils.reportAttached(view, false, false); - Assert.assertEquals(1, viewAttachListener.attaches); - Assert.assertEquals(1, viewAttachListener.detaches); + assertEquals(1, viewAttachListener.attaches); + assertEquals(1, viewAttachListener.detaches); ViewUtils.reportAttached(view, false, false); - Assert.assertEquals(1, viewAttachListener.attaches); - Assert.assertEquals(1, viewAttachListener.detaches); + assertEquals(1, viewAttachListener.attaches); + assertEquals(1, viewAttachListener.detaches); ViewUtils.reportAttached(view, true, false); - Assert.assertEquals(1, viewAttachListener.attaches); - Assert.assertEquals(1, viewAttachListener.detaches); + assertEquals(1, viewAttachListener.attaches); + assertEquals(1, viewAttachListener.detaches); ViewUtils.reportAttached(child, true, false); - Assert.assertEquals(2, viewAttachListener.attaches); - Assert.assertEquals(1, viewAttachListener.detaches); + assertEquals(2, viewAttachListener.attaches); + assertEquals(1, viewAttachListener.detaches); } private static class CountingViewAttachListener implements ViewAttachListener { diff --git a/conductor/src/test/java/com/bluelinelabs/conductor/ActivityProxy.java b/conductor/src/test/java/com/bluelinelabs/conductor/util/ActivityProxy.java similarity index 97% rename from conductor/src/test/java/com/bluelinelabs/conductor/ActivityProxy.java rename to conductor/src/test/java/com/bluelinelabs/conductor/util/ActivityProxy.java index 81c4865c..b9f2272e 100644 --- a/conductor/src/test/java/com/bluelinelabs/conductor/ActivityProxy.java +++ b/conductor/src/test/java/com/bluelinelabs/conductor/util/ActivityProxy.java @@ -1,4 +1,4 @@ -package com.bluelinelabs.conductor; +package com.bluelinelabs.conductor.util; import android.os.Bundle; import android.support.annotation.IdRes; diff --git a/conductor/src/test/java/com/bluelinelabs/conductor/AttachFakingFrameLayout.java b/conductor/src/test/java/com/bluelinelabs/conductor/util/AttachFakingFrameLayout.java similarity index 98% rename from conductor/src/test/java/com/bluelinelabs/conductor/AttachFakingFrameLayout.java rename to conductor/src/test/java/com/bluelinelabs/conductor/util/AttachFakingFrameLayout.java index 1d1afbc2..a5eeb99b 100644 --- a/conductor/src/test/java/com/bluelinelabs/conductor/AttachFakingFrameLayout.java +++ b/conductor/src/test/java/com/bluelinelabs/conductor/util/AttachFakingFrameLayout.java @@ -1,4 +1,4 @@ -package com.bluelinelabs.conductor; +package com.bluelinelabs.conductor.util; import android.content.Context; import android.os.IBinder; diff --git a/conductor/src/test/java/com/bluelinelabs/conductor/CallState.java b/conductor/src/test/java/com/bluelinelabs/conductor/util/CallState.java similarity index 99% rename from conductor/src/test/java/com/bluelinelabs/conductor/CallState.java rename to conductor/src/test/java/com/bluelinelabs/conductor/util/CallState.java index 72549d73..b77c96be 100644 --- a/conductor/src/test/java/com/bluelinelabs/conductor/CallState.java +++ b/conductor/src/test/java/com/bluelinelabs/conductor/util/CallState.java @@ -1,4 +1,4 @@ -package com.bluelinelabs.conductor; +package com.bluelinelabs.conductor.util; import android.os.Parcel; import android.os.Parcelable; diff --git a/conductor/src/test/java/com/bluelinelabs/conductor/util/ChangeHandlerHistory.java b/conductor/src/test/java/com/bluelinelabs/conductor/util/ChangeHandlerHistory.java new file mode 100644 index 00000000..96007e97 --- /dev/null +++ b/conductor/src/test/java/com/bluelinelabs/conductor/util/ChangeHandlerHistory.java @@ -0,0 +1,67 @@ +package com.bluelinelabs.conductor.util; + +import android.view.View; + +import java.util.ArrayList; +import java.util.List; + +public class ChangeHandlerHistory { + + private List entries = new ArrayList<>(); + public boolean isValidHistory = true; + + public void addEntry(View from, View to, boolean isPush, MockChangeHandler handler) { + entries.add(new Entry(from, to, isPush, handler)); + } + + public int size() { + return entries.size(); + } + + public View fromViewAt(int index) { + return entries.get(index).from; + } + + public View toViewAt(int index) { + return entries.get(index).to; + } + + public boolean isPushAt(int index) { + return entries.get(index).isPush; + } + + public MockChangeHandler changeHandlerAt(int index) { + return entries.get(index).changeHandler; + } + + public View latestFromView() { + return fromViewAt(size() - 1); + } + + public View latestToView() { + return toViewAt(size() - 1); + } + + public boolean latestIsPush() { + return isPushAt(size() - 1); + } + + public MockChangeHandler latestChangeHandler() { + return changeHandlerAt(size() - 1); + } + + private static class Entry { + final View from; + final View to; + final boolean isPush; + final MockChangeHandler changeHandler; + + Entry(View from, View to, boolean isPush, MockChangeHandler changeHandler) { + this.from = from; + this.to = to; + this.isPush = isPush; + this.changeHandler = changeHandler; + } + } + +} diff --git a/conductor/src/test/java/com/bluelinelabs/conductor/util/ListUtils.java b/conductor/src/test/java/com/bluelinelabs/conductor/util/ListUtils.java new file mode 100644 index 00000000..67bf84e9 --- /dev/null +++ b/conductor/src/test/java/com/bluelinelabs/conductor/util/ListUtils.java @@ -0,0 +1,16 @@ +package com.bluelinelabs.conductor.util; + +import java.util.ArrayList; +import java.util.List; + +public class ListUtils { + + public static List listOf(T... elements) { + List list = new ArrayList<>(); + for (T element : elements) { + list.add(element); + } + return list; + } + +} diff --git a/conductor/src/test/java/com/bluelinelabs/conductor/MockChangeHandler.java b/conductor/src/test/java/com/bluelinelabs/conductor/util/MockChangeHandler.java similarity index 56% rename from conductor/src/test/java/com/bluelinelabs/conductor/MockChangeHandler.java rename to conductor/src/test/java/com/bluelinelabs/conductor/util/MockChangeHandler.java index e2b90993..168c3cb6 100644 --- a/conductor/src/test/java/com/bluelinelabs/conductor/MockChangeHandler.java +++ b/conductor/src/test/java/com/bluelinelabs/conductor/util/MockChangeHandler.java @@ -1,40 +1,52 @@ -package com.bluelinelabs.conductor; +package com.bluelinelabs.conductor.util; import android.os.Bundle; import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.view.View; import android.view.ViewGroup; +import com.bluelinelabs.conductor.ControllerChangeHandler; + public class MockChangeHandler extends ControllerChangeHandler { private static final String KEY_REMOVES_FROM_VIEW_ON_PUSH = "MockChangeHandler.removesFromViewOnPush"; + private static final String KEY_TAG = "MockChangeHandler.tag"; - static class ChangeHandlerListener { - void willStartChange() { } - void didAttachOrDetach() { } - void didEndChange() { } + public static class ChangeHandlerListener { + public void willStartChange() { } + public void didAttachOrDetach() { } + public void didEndChange() { } } - final ChangeHandlerListener listener; - boolean removesFromViewOnPush; + private final ChangeHandlerListener listener; + private boolean removesFromViewOnPush; + + public View from; + public View to; + public String tag; public static MockChangeHandler defaultHandler() { - return new MockChangeHandler(true, null); + return new MockChangeHandler(true, null, null); } public static MockChangeHandler noRemoveViewOnPushHandler() { - return new MockChangeHandler(false, null); + return new MockChangeHandler(false, null, null); } public static MockChangeHandler listeningChangeHandler(@NonNull ChangeHandlerListener listener) { - return new MockChangeHandler(true , listener); + return new MockChangeHandler(true, null, listener); + } + + public static MockChangeHandler taggedHandler(String tag, boolean removeViewOnPush) { + return new MockChangeHandler(removeViewOnPush, tag, null); } public MockChangeHandler() { listener = null; } - private MockChangeHandler(boolean removesFromViewOnPush, ChangeHandlerListener listener) { + private MockChangeHandler(boolean removesFromViewOnPush, String tag, ChangeHandlerListener listener) { this.removesFromViewOnPush = removesFromViewOnPush; if (listener == null) { @@ -45,12 +57,17 @@ private MockChangeHandler(boolean removesFromViewOnPush, ChangeHandlerListener l } @Override - public void performChange(@NonNull ViewGroup container, View from, View to, boolean isPush, @NonNull ControllerChangeCompletedListener changeListener) { + public void performChange(@NonNull ViewGroup container, @Nullable View from, @Nullable View to, boolean isPush, @NonNull ControllerChangeCompletedListener changeListener) { + this.from = from; + this.to = to; + listener.willStartChange(); if (isPush) { - container.addView(to); - listener.didAttachOrDetach(); + if (to != null) { + container.addView(to); + listener.didAttachOrDetach(); + } if (removesFromViewOnPush && from != null) { container.removeView(from); @@ -78,18 +95,20 @@ public boolean removesFromViewOnPush() { public void saveToBundle(@NonNull Bundle bundle) { super.saveToBundle(bundle); bundle.putBoolean(KEY_REMOVES_FROM_VIEW_ON_PUSH, removesFromViewOnPush); + bundle.putString(KEY_TAG, tag); } @Override public void restoreFromBundle(@NonNull Bundle bundle) { super.restoreFromBundle(bundle); removesFromViewOnPush = bundle.getBoolean(KEY_REMOVES_FROM_VIEW_ON_PUSH); + tag = bundle.getString(KEY_TAG); } @NonNull @Override public ControllerChangeHandler copy() { - return new MockChangeHandler(removesFromViewOnPush, listener); + return new MockChangeHandler(removesFromViewOnPush, tag, listener); } @Override diff --git a/conductor/src/test/java/com/bluelinelabs/conductor/TestActivity.java b/conductor/src/test/java/com/bluelinelabs/conductor/util/TestActivity.java similarity index 90% rename from conductor/src/test/java/com/bluelinelabs/conductor/TestActivity.java rename to conductor/src/test/java/com/bluelinelabs/conductor/util/TestActivity.java index 1a05326f..6278168a 100644 --- a/conductor/src/test/java/com/bluelinelabs/conductor/TestActivity.java +++ b/conductor/src/test/java/com/bluelinelabs/conductor/util/TestActivity.java @@ -1,4 +1,4 @@ -package com.bluelinelabs.conductor; +package com.bluelinelabs.conductor.util; import android.app.Activity; diff --git a/conductor/src/test/java/com/bluelinelabs/conductor/TestController.java b/conductor/src/test/java/com/bluelinelabs/conductor/util/TestController.java similarity index 85% rename from conductor/src/test/java/com/bluelinelabs/conductor/TestController.java rename to conductor/src/test/java/com/bluelinelabs/conductor/util/TestController.java index a06e3d50..f9eb9d71 100644 --- a/conductor/src/test/java/com/bluelinelabs/conductor/TestController.java +++ b/conductor/src/test/java/com/bluelinelabs/conductor/util/TestController.java @@ -1,4 +1,4 @@ -package com.bluelinelabs.conductor; +package com.bluelinelabs.conductor.util; import android.content.Intent; import android.os.Bundle; @@ -11,6 +11,10 @@ import android.view.ViewGroup; import android.widget.FrameLayout; +import com.bluelinelabs.conductor.Controller; +import com.bluelinelabs.conductor.ControllerChangeHandler; +import com.bluelinelabs.conductor.ControllerChangeType; + public class TestController extends Controller { @IdRes public static final int VIEW_ID = 2342; @@ -19,11 +23,8 @@ public class TestController extends Controller { private static final String KEY_CALL_STATE = "TestController.currentCallState"; - public CallState currentCallState; - - public TestController() { - currentCallState = new CallState(); - } + public CallState currentCallState = new CallState(); + public ChangeHandlerHistory changeHandlerHistory = new ChangeHandlerHistory(); @NonNull @Override @@ -53,6 +54,13 @@ protected void onChangeStarted(@NonNull ControllerChangeHandler changeHandler, @ protected void onChangeEnded(@NonNull ControllerChangeHandler changeHandler, @NonNull ControllerChangeType changeType) { super.onChangeEnded(changeHandler, changeType); currentCallState.changeEndCalls++; + + if (changeHandler instanceof MockChangeHandler) { + MockChangeHandler mockHandler = (MockChangeHandler)changeHandler; + changeHandlerHistory.addEntry(mockHandler.from, mockHandler.to, changeType.isPush, mockHandler); + } else { + changeHandlerHistory.isValidHistory = false; + } } @Override diff --git a/conductor/src/test/java/com/bluelinelabs/conductor/ViewUtils.java b/conductor/src/test/java/com/bluelinelabs/conductor/util/ViewUtils.java similarity index 98% rename from conductor/src/test/java/com/bluelinelabs/conductor/ViewUtils.java rename to conductor/src/test/java/com/bluelinelabs/conductor/util/ViewUtils.java index 08cdb56a..abc8e669 100644 --- a/conductor/src/test/java/com/bluelinelabs/conductor/ViewUtils.java +++ b/conductor/src/test/java/com/bluelinelabs/conductor/util/ViewUtils.java @@ -1,4 +1,4 @@ -package com.bluelinelabs.conductor; +package com.bluelinelabs.conductor.util; import android.view.View; import android.view.View.OnAttachStateChangeListener; From f16f7b6d2cec3a91cd86f4efc4d90743ae5d4069 Mon Sep 17 00:00:00 2001 From: Eric Kuck Date: Thu, 19 Jan 2017 12:02:31 -0600 Subject: [PATCH 41/75] Fixes #205 --- .../conductor/changehandler/AnimatorChangeHandler.java | 1 + 1 file changed, 1 insertion(+) diff --git a/conductor/src/main/java/com/bluelinelabs/conductor/changehandler/AnimatorChangeHandler.java b/conductor/src/main/java/com/bluelinelabs/conductor/changehandler/AnimatorChangeHandler.java index e046fe7c..10009b0d 100755 --- a/conductor/src/main/java/com/bluelinelabs/conductor/changehandler/AnimatorChangeHandler.java +++ b/conductor/src/main/java/com/bluelinelabs/conductor/changehandler/AnimatorChangeHandler.java @@ -150,6 +150,7 @@ private void complete(@NonNull ControllerChangeCompletedListener changeListener, if (animatorListener != null) { animator.removeListener(animatorListener); } + animator.end(); animator = null; } } From be40900e1ecbd7f1677ec520ee39b4a2eb05269e Mon Sep 17 00:00:00 2001 From: Allan Hasegawa Date: Thu, 19 Jan 2017 17:20:20 -0200 Subject: [PATCH 42/75] #Fixes 206 (#207) --- .../src/main/java/com/bluelinelabs/conductor/Controller.java | 1 + 1 file changed, 1 insertion(+) diff --git a/conductor/src/main/java/com/bluelinelabs/conductor/Controller.java b/conductor/src/main/java/com/bluelinelabs/conductor/Controller.java index 441acac6..f475e952 100755 --- a/conductor/src/main/java/com/bluelinelabs/conductor/Controller.java +++ b/conductor/src/main/java/com/bluelinelabs/conductor/Controller.java @@ -849,6 +849,7 @@ private void removeViewReference() { onDestroyView(view); viewAttachHandler.unregisterAttachListener(view); + viewAttachHandler = null; viewIsAttached = false; if (isBeingDestroyed) { From 04d40a5b90d7e427516d984684f3a3300d10dcfc Mon Sep 17 00:00:00 2001 From: Eric Kuck Date: Thu, 19 Jan 2017 13:21:02 -0600 Subject: [PATCH 43/75] Minor ViewAttachHandler optimizations --- .../bluelinelabs/conductor/internal/ViewAttachHandler.java | 4 +++- demo/src/main/res/layout/controller_overlay.xml | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/conductor/src/main/java/com/bluelinelabs/conductor/internal/ViewAttachHandler.java b/conductor/src/main/java/com/bluelinelabs/conductor/internal/ViewAttachHandler.java index 7fb55ccd..426e8d22 100644 --- a/conductor/src/main/java/com/bluelinelabs/conductor/internal/ViewAttachHandler.java +++ b/conductor/src/main/java/com/bluelinelabs/conductor/internal/ViewAttachHandler.java @@ -59,7 +59,7 @@ public void listenForAttach(final View view) { public void unregisterAttachListener(View view) { view.removeOnAttachStateChangeListener(rootOnAttachStateChangeListener); - if (view instanceof ViewGroup) { + if (childOnAttachStateChangeListener != null && view instanceof ViewGroup) { findDeepestChild((ViewGroup)view).removeOnAttachStateChangeListener(childOnAttachStateChangeListener); } } @@ -84,6 +84,8 @@ public void onViewAttachedToWindow(View v) { if (!attached) { attached = true; attachListener.onAttached(); + v.removeOnAttachStateChangeListener(this); + childOnAttachStateChangeListener = null; } } diff --git a/demo/src/main/res/layout/controller_overlay.xml b/demo/src/main/res/layout/controller_overlay.xml index 88e765c5..1a4f7331 100755 --- a/demo/src/main/res/layout/controller_overlay.xml +++ b/demo/src/main/res/layout/controller_overlay.xml @@ -3,7 +3,8 @@ xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" - android:background="#88000000" > + android:background="#88000000" + android:clickable="true" > Date: Thu, 19 Jan 2017 14:13:26 -0600 Subject: [PATCH 44/75] Fixes #203 --- .../src/main/java/com/bluelinelabs/conductor/Router.java | 5 +++++ .../test/java/com/bluelinelabs/conductor/RouterTests.java | 4 ++++ 2 files changed, 9 insertions(+) diff --git a/conductor/src/main/java/com/bluelinelabs/conductor/Router.java b/conductor/src/main/java/com/bluelinelabs/conductor/Router.java index 3c229b51..5c5b305f 100755 --- a/conductor/src/main/java/com/bluelinelabs/conductor/Router.java +++ b/conductor/src/main/java/com/bluelinelabs/conductor/Router.java @@ -373,6 +373,11 @@ public void setBackstack(@NonNull List newBackstack, @Nullabl performControllerChange(transaction.controller, newVisibleTransactions.get(i - 1).controller, true, transaction.pushChangeHandler()); } } + + // Ensure all new controllers have a valid router set + for (RouterTransaction transaction : newBackstack) { + transaction.controller.setRouter(this); + } } if (onControllerPushedListener != null) { diff --git a/conductor/src/test/java/com/bluelinelabs/conductor/RouterTests.java b/conductor/src/test/java/com/bluelinelabs/conductor/RouterTests.java index 5ddf521e..eae0661a 100644 --- a/conductor/src/test/java/com/bluelinelabs/conductor/RouterTests.java +++ b/conductor/src/test/java/com/bluelinelabs/conductor/RouterTests.java @@ -217,6 +217,10 @@ public void testNewSetBackstack() { assertEquals(rootTransaction, fetchedBackstack.get(0)); assertEquals(middleTransaction, fetchedBackstack.get(1)); assertEquals(topTransaction, fetchedBackstack.get(2)); + + assertEquals(router, rootTransaction.controller.getRouter()); + assertEquals(router, middleTransaction.controller.getRouter()); + assertEquals(router, topTransaction.controller.getRouter()); } @Test From 10a1c8af3ed5ea8e442aa1800ecadc410de8bf35 Mon Sep 17 00:00:00 2001 From: Eric Kuck Date: Thu, 19 Jan 2017 14:21:48 -0600 Subject: [PATCH 45/75] Version bump --- README.md | 16 ++++++++-------- gradle.properties | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 77098b7a..9675040c 100644 --- a/README.md +++ b/README.md @@ -20,26 +20,26 @@ Conductor is architecture-agnostic and does not try to force any design decision ## Installation ```gradle -compile 'com.bluelinelabs:conductor:2.0.6' +compile 'com.bluelinelabs:conductor:2.0.7' // If you want the components that go along with // Android's support libraries (currently just a PagerAdapter): -compile 'com.bluelinelabs:conductor-support:2.0.6' +compile 'com.bluelinelabs:conductor-support:2.0.7' // If you want RxJava lifecycle support: -compile 'com.bluelinelabs:conductor-rxlifecycle:2.0.6' +compile 'com.bluelinelabs:conductor-rxlifecycle:2.0.7' // If you want RxJava2 lifecycle support: -compile 'com.bluelinelabs:conductor-rxlifecycle2:2.0.6' +compile 'com.bluelinelabs:conductor-rxlifecycle2:2.0.7' ``` SNAPSHOT: ```gradle -compile 'com.bluelinelabs:conductor:2.0.7-SNAPSHOT' -compile 'com.bluelinelabs:conductor-support:2.0.7-SNAPSHOT' -compile 'com.bluelinelabs:conductor-rxlifecycle:2.0.7-SNAPSHOT' -compile 'com.bluelinelabs:conductor-rxlifecycle2:2.0.7-SNAPSHOT' +compile 'com.bluelinelabs:conductor:2.0.6-SNAPSHOT' +compile 'com.bluelinelabs:conductor-support:2.0.6-SNAPSHOT' +compile 'com.bluelinelabs:conductor-rxlifecycle:2.0.6-SNAPSHOT' +compile 'com.bluelinelabs:conductor-rxlifecycle2:2.0.6-SNAPSHOT' ``` You also have to add the url to the snapshot repository: diff --git a/gradle.properties b/gradle.properties index 9358e941..50b9bc24 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ -VERSION_NAME=2.0.7-SNAPSHOT +VERSION_NAME=2.0.8-SNAPSHOT VERSION_CODE=2 GROUP=com.bluelinelabs From 60d0fabcf487d92f515419388a7f66054e8380aa Mon Sep 17 00:00:00 2001 From: Eric Kuck Date: Fri, 20 Jan 2017 17:15:33 -0600 Subject: [PATCH 46/75] Fixes #208 --- .../bluelinelabs/conductor/Controller.java | 2 +- .../com/bluelinelabs/conductor/Router.java | 3 +- ...rollerLifecycleActivityReferenceTests.java | 268 ++++++++++++++++++ ...=> ControllerLifecycleCallbacksTests.java} | 6 +- .../conductor/util/ActivityProxy.java | 9 +- 5 files changed, 280 insertions(+), 8 deletions(-) create mode 100644 conductor/src/test/java/com/bluelinelabs/conductor/ControllerLifecycleActivityReferenceTests.java rename conductor/src/test/java/com/bluelinelabs/conductor/{ControllerLifecycleTests.java => ControllerLifecycleCallbacksTests.java} (99%) diff --git a/conductor/src/main/java/com/bluelinelabs/conductor/Controller.java b/conductor/src/main/java/com/bluelinelabs/conductor/Controller.java index f475e952..e8b34d18 100755 --- a/conductor/src/main/java/com/bluelinelabs/conductor/Controller.java +++ b/conductor/src/main/java/com/bluelinelabs/conductor/Controller.java @@ -268,7 +268,7 @@ public final View getView() { */ @Nullable public final Activity getActivity() { - return router.getActivity(); + return router != null ? router.getActivity() : null; } /** diff --git a/conductor/src/main/java/com/bluelinelabs/conductor/Router.java b/conductor/src/main/java/com/bluelinelabs/conductor/Router.java index 5c5b305f..cc8c9b1c 100755 --- a/conductor/src/main/java/com/bluelinelabs/conductor/Router.java +++ b/conductor/src/main/java/com/bluelinelabs/conductor/Router.java @@ -188,10 +188,9 @@ public void replaceTopController(@NonNull RouterTransaction transaction) { void destroy(boolean popViews) { popsLastView = true; List poppedControllers = backstack.popAll(); + trackDestroyingControllers(poppedControllers); if (popViews && poppedControllers.size() > 0) { - trackDestroyingControllers(poppedControllers); - performControllerChange(null, poppedControllers.get(0).controller, false, poppedControllers.get(0).popChangeHandler()); } } diff --git a/conductor/src/test/java/com/bluelinelabs/conductor/ControllerLifecycleActivityReferenceTests.java b/conductor/src/test/java/com/bluelinelabs/conductor/ControllerLifecycleActivityReferenceTests.java new file mode 100644 index 00000000..15a9d7db --- /dev/null +++ b/conductor/src/test/java/com/bluelinelabs/conductor/ControllerLifecycleActivityReferenceTests.java @@ -0,0 +1,268 @@ +package com.bluelinelabs.conductor; + +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.view.View; +import android.view.ViewGroup; + +import com.bluelinelabs.conductor.util.ActivityProxy; +import com.bluelinelabs.conductor.util.ListUtils; +import com.bluelinelabs.conductor.util.MockChangeHandler; +import com.bluelinelabs.conductor.util.TestController; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +@RunWith(RobolectricTestRunner.class) +@Config(manifest = Config.NONE) +public class ControllerLifecycleActivityReferenceTests { + + private Router router; + + private ActivityProxy activityProxy; + + public void createActivityController(Bundle savedInstanceState, boolean includeStartAndResume) { + activityProxy = new ActivityProxy().create(savedInstanceState); + + if (includeStartAndResume) { + activityProxy.start().resume(); + } + + router = Conductor.attachRouter(activityProxy.getActivity(), activityProxy.getView(), savedInstanceState); + router.setPopsLastView(true); + if (!router.hasRootController()) { + router.setRoot(RouterTransaction.with(new TestController())); + } + } + + @Before + public void setup() { + createActivityController(null, true); + } + + @Test + public void testSingleControllerActivityOnPush() { + Controller controller = new TestController(); + + assertNull(controller.getActivity()); + + ActivityReferencingLifecycleListener listener = new ActivityReferencingLifecycleListener(); + controller.addLifecycleListener(listener); + + router.pushController(RouterTransaction.with(controller) + .pushChangeHandler(MockChangeHandler.defaultHandler()) + .popChangeHandler(MockChangeHandler.defaultHandler())); + + assertEquals(ListUtils.listOf(true), listener.changeEndReferences); + assertEquals(ListUtils.listOf(true), listener.postCreateViewReferences); + assertEquals(ListUtils.listOf(true), listener.postAttachReferences); + assertEquals(ListUtils.listOf(), listener.postDetachReferences); + assertEquals(ListUtils.listOf(), listener.postDestroyViewReferences); + assertEquals(ListUtils.listOf(), listener.postDestroyReferences); + } + + @Test + public void testChildControllerActivityOnPush() { + Controller parent = new TestController(); + router.pushController(RouterTransaction.with(parent) + .pushChangeHandler(MockChangeHandler.defaultHandler()) + .popChangeHandler(MockChangeHandler.defaultHandler())); + + TestController child = new TestController(); + + assertNull(child.getActivity()); + + ActivityReferencingLifecycleListener listener = new ActivityReferencingLifecycleListener(); + child.addLifecycleListener(listener); + + Router childRouter = parent.getChildRouter((ViewGroup)parent.getView().findViewById(TestController.VIEW_ID)); + childRouter.pushController(RouterTransaction.with(child) + .pushChangeHandler(MockChangeHandler.defaultHandler()) + .popChangeHandler(MockChangeHandler.defaultHandler())); + + assertEquals(ListUtils.listOf(true), listener.changeEndReferences); + assertEquals(ListUtils.listOf(true), listener.postCreateViewReferences); + assertEquals(ListUtils.listOf(true), listener.postAttachReferences); + assertEquals(ListUtils.listOf(), listener.postDetachReferences); + assertEquals(ListUtils.listOf(), listener.postDestroyViewReferences); + assertEquals(ListUtils.listOf(), listener.postDestroyReferences); + } + + @Test + public void testSingleControllerActivityOnPop() { + Controller controller = new TestController(); + + ActivityReferencingLifecycleListener listener = new ActivityReferencingLifecycleListener(); + controller.addLifecycleListener(listener); + + router.pushController(RouterTransaction.with(controller) + .pushChangeHandler(MockChangeHandler.defaultHandler()) + .popChangeHandler(MockChangeHandler.defaultHandler())); + + router.popCurrentController(); + + assertEquals(ListUtils.listOf(true, true), listener.changeEndReferences); + assertEquals(ListUtils.listOf(true), listener.postCreateViewReferences); + assertEquals(ListUtils.listOf(true), listener.postAttachReferences); + assertEquals(ListUtils.listOf(true), listener.postDetachReferences); + assertEquals(ListUtils.listOf(true), listener.postDestroyViewReferences); + assertEquals(ListUtils.listOf(true), listener.postDestroyReferences); + } + + @Test + public void testChildControllerActivityOnPop() { + Controller parent = new TestController(); + + router.pushController(RouterTransaction.with(parent) + .pushChangeHandler(MockChangeHandler.defaultHandler()) + .popChangeHandler(MockChangeHandler.defaultHandler())); + + TestController child = new TestController(); + + ActivityReferencingLifecycleListener listener = new ActivityReferencingLifecycleListener(); + child.addLifecycleListener(listener); + + Router childRouter = parent.getChildRouter((ViewGroup)parent.getView().findViewById(TestController.VIEW_ID)); + childRouter.setPopsLastView(true); + childRouter.pushController(RouterTransaction.with(child) + .pushChangeHandler(MockChangeHandler.defaultHandler()) + .popChangeHandler(MockChangeHandler.defaultHandler())); + + childRouter.popCurrentController(); + + assertEquals(ListUtils.listOf(true, true), listener.changeEndReferences); + assertEquals(ListUtils.listOf(true), listener.postCreateViewReferences); + assertEquals(ListUtils.listOf(true), listener.postAttachReferences); + assertEquals(ListUtils.listOf(true), listener.postDetachReferences); + assertEquals(ListUtils.listOf(true), listener.postDestroyViewReferences); + assertEquals(ListUtils.listOf(true), listener.postDestroyReferences); + } + + @Test + public void testChildControllerActivityOnParentPop() { + Controller parent = new TestController(); + + router.pushController(RouterTransaction.with(parent) + .pushChangeHandler(MockChangeHandler.defaultHandler()) + .popChangeHandler(MockChangeHandler.defaultHandler())); + + TestController child = new TestController(); + + ActivityReferencingLifecycleListener listener = new ActivityReferencingLifecycleListener(); + child.addLifecycleListener(listener); + + Router childRouter = parent.getChildRouter((ViewGroup)parent.getView().findViewById(TestController.VIEW_ID)); + childRouter.setPopsLastView(true); + childRouter.pushController(RouterTransaction.with(child) + .pushChangeHandler(MockChangeHandler.defaultHandler()) + .popChangeHandler(MockChangeHandler.defaultHandler())); + + router.popCurrentController(); + + assertEquals(ListUtils.listOf(true), listener.changeEndReferences); + assertEquals(ListUtils.listOf(true), listener.postCreateViewReferences); + assertEquals(ListUtils.listOf(true), listener.postAttachReferences); + assertEquals(ListUtils.listOf(true), listener.postDetachReferences); + assertEquals(ListUtils.listOf(true), listener.postDestroyViewReferences); + assertEquals(ListUtils.listOf(true), listener.postDestroyReferences); + } + + @Test + public void testSingleControllerActivityOnDestroy() { + Controller controller = new TestController(); + + ActivityReferencingLifecycleListener listener = new ActivityReferencingLifecycleListener(); + controller.addLifecycleListener(listener); + + router.pushController(RouterTransaction.with(controller) + .pushChangeHandler(MockChangeHandler.defaultHandler()) + .popChangeHandler(MockChangeHandler.defaultHandler())); + + activityProxy.pause().stop(false).destroy(); + + assertEquals(ListUtils.listOf(true), listener.changeEndReferences); + assertEquals(ListUtils.listOf(true), listener.postCreateViewReferences); + assertEquals(ListUtils.listOf(true), listener.postAttachReferences); + assertEquals(ListUtils.listOf(true), listener.postDetachReferences); + assertEquals(ListUtils.listOf(true), listener.postDestroyViewReferences); + assertEquals(ListUtils.listOf(true), listener.postDestroyReferences); + } + + @Test + public void testChildControllerActivityOnDestroy() { + Controller parent = new TestController(); + + router.pushController(RouterTransaction.with(parent) + .pushChangeHandler(MockChangeHandler.defaultHandler()) + .popChangeHandler(MockChangeHandler.defaultHandler())); + + TestController child = new TestController(); + + ActivityReferencingLifecycleListener listener = new ActivityReferencingLifecycleListener(); + child.addLifecycleListener(listener); + + Router childRouter = parent.getChildRouter((ViewGroup)parent.getView().findViewById(TestController.VIEW_ID)); + childRouter.setPopsLastView(true); + childRouter.pushController(RouterTransaction.with(child) + .pushChangeHandler(MockChangeHandler.defaultHandler()) + .popChangeHandler(MockChangeHandler.defaultHandler())); + + activityProxy.pause().stop(false).destroy(); + + assertEquals(ListUtils.listOf(true), listener.changeEndReferences); + assertEquals(ListUtils.listOf(true), listener.postCreateViewReferences); + assertEquals(ListUtils.listOf(true), listener.postAttachReferences); + assertEquals(ListUtils.listOf(true), listener.postDetachReferences); + assertEquals(ListUtils.listOf(true), listener.postDestroyViewReferences); + assertEquals(ListUtils.listOf(true), listener.postDestroyReferences); + } + + static class ActivityReferencingLifecycleListener extends Controller.LifecycleListener { + List changeEndReferences = new ArrayList<>(); + List postCreateViewReferences = new ArrayList<>(); + List postAttachReferences = new ArrayList<>(); + List postDetachReferences = new ArrayList<>(); + List postDestroyViewReferences = new ArrayList<>(); + List postDestroyReferences = new ArrayList<>(); + + @Override + public void onChangeEnd(@NonNull Controller controller, @NonNull ControllerChangeHandler changeHandler, @NonNull ControllerChangeType changeType) { + changeEndReferences.add(controller.getActivity() != null); + } + + @Override + public void postCreateView(@NonNull Controller controller, @NonNull View view) { + postCreateViewReferences.add(controller.getActivity() != null); + } + + @Override + public void postAttach(@NonNull Controller controller, @NonNull View view) { + postAttachReferences.add(controller.getActivity() != null); + } + + @Override + public void postDetach(@NonNull Controller controller, @NonNull View view) { + postDetachReferences.add(controller.getActivity() != null); + } + + @Override + public void postDestroyView(@NonNull Controller controller) { + postDestroyViewReferences.add(controller.getActivity() != null); + } + + @Override + public void postDestroy(@NonNull Controller controller) { + postDestroyReferences.add(controller.getActivity() != null); + } + } + +} diff --git a/conductor/src/test/java/com/bluelinelabs/conductor/ControllerLifecycleTests.java b/conductor/src/test/java/com/bluelinelabs/conductor/ControllerLifecycleCallbacksTests.java similarity index 99% rename from conductor/src/test/java/com/bluelinelabs/conductor/ControllerLifecycleTests.java rename to conductor/src/test/java/com/bluelinelabs/conductor/ControllerLifecycleCallbacksTests.java index 38664388..ab974061 100644 --- a/conductor/src/test/java/com/bluelinelabs/conductor/ControllerLifecycleTests.java +++ b/conductor/src/test/java/com/bluelinelabs/conductor/ControllerLifecycleCallbacksTests.java @@ -23,7 +23,7 @@ @RunWith(RobolectricTestRunner.class) @Config(manifest = Config.NONE) -public class ControllerLifecycleTests { +public class ControllerLifecycleCallbacksTests { private Router router; @@ -89,7 +89,7 @@ public void testLifecycleWithActivityDestroy() { assertCalls(expectedCallState, controller); - activityProxy.stop(); + activityProxy.stop(true); expectedCallState.saveViewStateCalls++; expectedCallState.detachCalls++; @@ -128,7 +128,7 @@ public void testLifecycleWithActivityConfigurationChange() { activityProxy.pause(); assertCalls(expectedCallState, controller); - activityProxy.stop(); + activityProxy.stop(true); expectedCallState.detachCalls++; expectedCallState.destroyViewCalls++; assertCalls(expectedCallState, controller); diff --git a/conductor/src/test/java/com/bluelinelabs/conductor/util/ActivityProxy.java b/conductor/src/test/java/com/bluelinelabs/conductor/util/ActivityProxy.java index b9f2272e..be124911 100644 --- a/conductor/src/test/java/com/bluelinelabs/conductor/util/ActivityProxy.java +++ b/conductor/src/test/java/com/bluelinelabs/conductor/util/ActivityProxy.java @@ -45,14 +45,19 @@ public ActivityProxy saveInstanceState(Bundle outState) { return this; } - public ActivityProxy stop() { + public ActivityProxy stop(boolean detachView) { activityController.stop(); - view.setAttached(false); + + if (detachView) { + view.setAttached(false); + } + return this; } public ActivityProxy destroy() { activityController.destroy(); + view.setAttached(false); return this; } From 44bcd0f977bd9f6f6785af7de717915d40192546 Mon Sep 17 00:00:00 2001 From: Eric Kuck Date: Fri, 20 Jan 2017 17:35:15 -0600 Subject: [PATCH 47/75] Fixes #205 --- .../conductor/changehandler/AnimatorChangeHandler.java | 2 +- .../conductor/changehandler/FadeChangeHandler.java | 5 +++-- .../conductor/changehandler/HorizontalChangeHandler.java | 4 +++- .../changehandler/CircularRevealChangeHandlerCompat.java | 5 +++-- .../conductor/demo/changehandler/ScaleFadeChangeHandler.java | 5 +++-- 5 files changed, 13 insertions(+), 8 deletions(-) diff --git a/conductor/src/main/java/com/bluelinelabs/conductor/changehandler/AnimatorChangeHandler.java b/conductor/src/main/java/com/bluelinelabs/conductor/changehandler/AnimatorChangeHandler.java index 10009b0d..cda62004 100755 --- a/conductor/src/main/java/com/bluelinelabs/conductor/changehandler/AnimatorChangeHandler.java +++ b/conductor/src/main/java/com/bluelinelabs/conductor/changehandler/AnimatorChangeHandler.java @@ -150,7 +150,7 @@ private void complete(@NonNull ControllerChangeCompletedListener changeListener, if (animatorListener != null) { animator.removeListener(animatorListener); } - animator.end(); + animator.cancel(); animator = null; } } diff --git a/conductor/src/main/java/com/bluelinelabs/conductor/changehandler/FadeChangeHandler.java b/conductor/src/main/java/com/bluelinelabs/conductor/changehandler/FadeChangeHandler.java index eabc06a0..6f498403 100755 --- a/conductor/src/main/java/com/bluelinelabs/conductor/changehandler/FadeChangeHandler.java +++ b/conductor/src/main/java/com/bluelinelabs/conductor/changehandler/FadeChangeHandler.java @@ -32,8 +32,9 @@ public FadeChangeHandler(long duration, boolean removesFromViewOnPush) { @Override @NonNull protected Animator getAnimator(@NonNull ViewGroup container, @Nullable View from, @Nullable View to, boolean isPush, boolean toAddedToContainer) { AnimatorSet animator = new AnimatorSet(); - if (to != null && toAddedToContainer) { - animator.play(ObjectAnimator.ofFloat(to, View.ALPHA, 0, 1)); + if (to != null) { + float start = toAddedToContainer ? 0 : to.getAlpha(); + animator.play(ObjectAnimator.ofFloat(to, View.ALPHA, start, 1)); } if (from != null) { diff --git a/conductor/src/main/java/com/bluelinelabs/conductor/changehandler/HorizontalChangeHandler.java b/conductor/src/main/java/com/bluelinelabs/conductor/changehandler/HorizontalChangeHandler.java index c5099de0..86f8e225 100755 --- a/conductor/src/main/java/com/bluelinelabs/conductor/changehandler/HorizontalChangeHandler.java +++ b/conductor/src/main/java/com/bluelinelabs/conductor/changehandler/HorizontalChangeHandler.java @@ -45,7 +45,9 @@ protected Animator getAnimator(@NonNull ViewGroup container, @Nullable View from animatorSet.play(ObjectAnimator.ofFloat(from, View.TRANSLATION_X, from.getWidth())); } if (to != null) { - animatorSet.play(ObjectAnimator.ofFloat(to, View.TRANSLATION_X, -to.getWidth(), 0)); + // Allow this to have a nice transition when coming off an aborted push animation + float fromLeft = from != null ? from.getX() : 0; + animatorSet.play(ObjectAnimator.ofFloat(to, View.TRANSLATION_X, fromLeft - to.getWidth(), 0)); } } diff --git a/demo/src/main/java/com/bluelinelabs/conductor/demo/changehandler/CircularRevealChangeHandlerCompat.java b/demo/src/main/java/com/bluelinelabs/conductor/demo/changehandler/CircularRevealChangeHandlerCompat.java index ea9cd0f8..347bdd92 100644 --- a/demo/src/main/java/com/bluelinelabs/conductor/demo/changehandler/CircularRevealChangeHandlerCompat.java +++ b/demo/src/main/java/com/bluelinelabs/conductor/demo/changehandler/CircularRevealChangeHandlerCompat.java @@ -22,8 +22,9 @@ protected Animator getAnimator(@NonNull ViewGroup container, View from, View to, return super.getAnimator(container, from, to, isPush, toAddedToContainer); } else { AnimatorSet animator = new AnimatorSet(); - if (to != null && toAddedToContainer) { - animator.play(ObjectAnimator.ofFloat(to, View.ALPHA, 0, 1)); + if (to != null) { + float start = toAddedToContainer ? 0 : to.getAlpha(); + animator.play(ObjectAnimator.ofFloat(to, View.ALPHA, start, 1)); } if (from != null) { diff --git a/demo/src/main/java/com/bluelinelabs/conductor/demo/changehandler/ScaleFadeChangeHandler.java b/demo/src/main/java/com/bluelinelabs/conductor/demo/changehandler/ScaleFadeChangeHandler.java index 193010da..3dd8e4f0 100644 --- a/demo/src/main/java/com/bluelinelabs/conductor/demo/changehandler/ScaleFadeChangeHandler.java +++ b/demo/src/main/java/com/bluelinelabs/conductor/demo/changehandler/ScaleFadeChangeHandler.java @@ -18,8 +18,9 @@ public ScaleFadeChangeHandler() { @Override @NonNull protected Animator getAnimator(@NonNull ViewGroup container, View from, View to, boolean isPush, boolean toAddedToContainer) { AnimatorSet animator = new AnimatorSet(); - if (to != null && toAddedToContainer) { - animator.play(ObjectAnimator.ofFloat(to, View.ALPHA, 0, 1)); + if (to != null) { + float start = toAddedToContainer ? 0 : to.getAlpha(); + animator.play(ObjectAnimator.ofFloat(to, View.ALPHA, start, 1)); } if (from != null) { From 90e015b6b3546d9b2ac0783ad2327625237326f5 Mon Sep 17 00:00:00 2001 From: Eric Kuck Date: Tue, 31 Jan 2017 18:06:23 -0600 Subject: [PATCH 48/75] Now correctly calls onDetach when the host activity is stopped, even if the view is not detached from the window. Fixes #213 --- .../bluelinelabs/conductor/Controller.java | 40 ++++--- .../conductor/ControllerHostedRouter.java | 4 +- .../conductor/internal/ViewAttachHandler.java | 107 ++++++++++++------ .../ControllerLifecycleCallbacksTests.java | 33 ++++++ .../conductor/ViewAttachHandlerTests.java | 93 ++++++++++++++- docs/Controller Lifecycle.jpg | Bin 78891 -> 66705 bytes 6 files changed, 225 insertions(+), 52 deletions(-) diff --git a/conductor/src/main/java/com/bluelinelabs/conductor/Controller.java b/conductor/src/main/java/com/bluelinelabs/conductor/Controller.java index e8b34d18..3085b1f5 100755 --- a/conductor/src/main/java/com/bluelinelabs/conductor/Controller.java +++ b/conductor/src/main/java/com/bluelinelabs/conductor/Controller.java @@ -744,6 +744,10 @@ final void executeWithRouter(@NonNull RouterRequiringFunc listener) { } final void activityStarted(@NonNull Activity activity) { + if (viewAttachHandler != null) { + viewAttachHandler.onActivityStarted(); + } + onActivityStarted(activity); } @@ -763,12 +767,15 @@ final void activityPaused(@NonNull Activity activity) { } final void activityStopped(@NonNull Activity activity) { + if (viewAttachHandler != null) { + viewAttachHandler.onActivityStopped(); + } onActivityStopped(activity); } final void activityDestroyed(boolean isChangingConfigurations) { if (isChangingConfigurations) { - detach(view, true); + detach(view, true, false); } else { destroy(true); } @@ -802,14 +809,14 @@ private void attach(@NonNull View view) { } } - void detach(@NonNull View view, boolean forceViewRefRemoval) { + void detach(@NonNull View view, boolean forceViewRefRemoval, boolean blockViewRefRemoval) { if (!attachedToUnownedParent) { for (ControllerHostedRouter router : childRouters) { router.prepareForHostDetach(); } } - final boolean removeViewRef = forceViewRefRemoval || retainViewMode == RetainViewMode.RELEASE_DETACH || isBeingDestroyed; + final boolean removeViewRef = !blockViewRefRemoval && (forceViewRefRemoval || retainViewMode == RetainViewMode.RELEASE_DETACH || isBeingDestroyed); if (attached) { List listeners = new ArrayList<>(lifecycleListeners); @@ -874,7 +881,7 @@ private void removeViewReference() { final View inflate(@NonNull ViewGroup parent) { if (view != null && view.getParent() != null && view.getParent() != parent) { - detach(view, true); + detach(view, true, false); removeViewReference(); } @@ -898,21 +905,26 @@ final View inflate(@NonNull ViewGroup parent) { viewAttachHandler = new ViewAttachHandler(new ViewAttachListener() { @Override - public void onAttached(View v) { - if (v == view) { - viewIsAttached = true; - viewWasDetached = false; - } - attach(v); + public void onAttached() { + viewIsAttached = true; + viewWasDetached = false; + attach(view); } @Override - public void onDetached(View v) { + public void onDetached(boolean fromActivityStop) { viewIsAttached = false; viewWasDetached = true; if (!isDetachFrozen) { - detach(v, false); + detach(view, false, fromActivityStop); + } + } + + @Override + public void onViewDetachAfterStop() { + if (!isDetachFrozen) { + detach(view, false, false); } } }); @@ -976,7 +988,7 @@ private void destroy(boolean removeViews) { if (!attached) { removeViewReference(); } else if (removeViews) { - detach(view, true); + detach(view, true, false); } } @@ -1156,7 +1168,7 @@ final void setDetachFrozen(boolean frozen) { } if (!frozen && view != null && viewWasDetached) { - detach(view, false); + detach(view, false, false); } } } diff --git a/conductor/src/main/java/com/bluelinelabs/conductor/ControllerHostedRouter.java b/conductor/src/main/java/com/bluelinelabs/conductor/ControllerHostedRouter.java index 7a57ddf7..f2a6c4ee 100644 --- a/conductor/src/main/java/com/bluelinelabs/conductor/ControllerHostedRouter.java +++ b/conductor/src/main/java/com/bluelinelabs/conductor/ControllerHostedRouter.java @@ -51,12 +51,12 @@ final void removeHost() { final List controllersToDestroy = new ArrayList<>(destroyingControllers); for (Controller controller : controllersToDestroy) { if (controller.getView() != null) { - controller.detach(controller.getView(), true); + controller.detach(controller.getView(), true, false); } } for (RouterTransaction transaction : backstack) { if (transaction.controller.getView() != null) { - transaction.controller.detach(transaction.controller.getView(), true); + transaction.controller.detach(transaction.controller.getView(), true, false); } } diff --git a/conductor/src/main/java/com/bluelinelabs/conductor/internal/ViewAttachHandler.java b/conductor/src/main/java/com/bluelinelabs/conductor/internal/ViewAttachHandler.java index 426e8d22..8e27662d 100644 --- a/conductor/src/main/java/com/bluelinelabs/conductor/internal/ViewAttachHandler.java +++ b/conductor/src/main/java/com/bluelinelabs/conductor/internal/ViewAttachHandler.java @@ -4,66 +4,107 @@ import android.view.View.OnAttachStateChangeListener; import android.view.ViewGroup; -public class ViewAttachHandler { +public class ViewAttachHandler implements OnAttachStateChangeListener { + + private enum ReportedState { + VIEW_DETACHED, + ACTIVITY_STOPPED, + ATTACHED + } public interface ViewAttachListener { - void onAttached(View view); - void onDetached(View view); + void onAttached(); + void onDetached(boolean fromActivityStop); + void onViewDetachAfterStop(); } private interface ChildAttachListener { void onAttached(); } + private boolean rootAttached = false; + boolean childrenAttached = false; + private boolean activityStopped = false; + private ReportedState reportedState = ReportedState.VIEW_DETACHED; private ViewAttachListener attachListener; - private OnAttachStateChangeListener rootOnAttachStateChangeListener = new OnAttachStateChangeListener() { - boolean rootAttached = false; - boolean childrenAttached = false; - - @Override - public void onViewAttachedToWindow(final View v) { - if (rootAttached) { - return; - } + private OnAttachStateChangeListener childOnAttachStateChangeListener; - rootAttached = true; - listenForDeepestChildAttach(v, new ChildAttachListener() { - @Override - public void onAttached() { - childrenAttached = true; - attachListener.onAttached(v); - } - }); + public ViewAttachHandler(ViewAttachListener attachListener) { + this.attachListener = attachListener; + } + @Override + public void onViewAttachedToWindow(final View v) { + if (rootAttached) { + return; } - @Override - public void onViewDetachedFromWindow(View v) { - rootAttached = false; - if (childrenAttached) { - childrenAttached = false; - attachListener.onDetached(v); + rootAttached = true; + listenForDeepestChildAttach(v, new ChildAttachListener() { + @Override + public void onAttached() { + childrenAttached = true; + reportAttached(); + } - } - }; - private OnAttachStateChangeListener childOnAttachStateChangeListener; + }); + } - public ViewAttachHandler(ViewAttachListener attachListener) { - this.attachListener = attachListener; + @Override + public void onViewDetachedFromWindow(View v) { + rootAttached = false; + if (childrenAttached) { + childrenAttached = false; + reportDetached(); + } } public void listenForAttach(final View view) { - view.addOnAttachStateChangeListener(rootOnAttachStateChangeListener); + view.addOnAttachStateChangeListener(this); } public void unregisterAttachListener(View view) { - view.removeOnAttachStateChangeListener(rootOnAttachStateChangeListener); + view.removeOnAttachStateChangeListener(this); if (childOnAttachStateChangeListener != null && view instanceof ViewGroup) { findDeepestChild((ViewGroup)view).removeOnAttachStateChangeListener(childOnAttachStateChangeListener); } } + public void onActivityStarted() { + activityStopped = false; + reportAttached(); + } + + public void onActivityStopped() { + activityStopped = true; + reportDetached(); + } + + void reportAttached() { + if (rootAttached && childrenAttached && !activityStopped && reportedState != ReportedState.ATTACHED) { + reportedState = ReportedState.ATTACHED; + attachListener.onAttached(); + } + } + + void reportDetached() { + boolean wasDetachedForActivity = reportedState == ReportedState.ACTIVITY_STOPPED; + boolean isDetachedForActivity = rootAttached; + + if (isDetachedForActivity) { + reportedState = ReportedState.ACTIVITY_STOPPED; + } else { + reportedState = ReportedState.VIEW_DETACHED; + } + + if (wasDetachedForActivity && !isDetachedForActivity) { + attachListener.onViewDetachAfterStop(); + } else { + attachListener.onDetached(isDetachedForActivity); + } + } + void listenForDeepestChildAttach(final View view, final ChildAttachListener attachListener) { if (!(view instanceof ViewGroup)) { attachListener.onAttached(); diff --git a/conductor/src/test/java/com/bluelinelabs/conductor/ControllerLifecycleCallbacksTests.java b/conductor/src/test/java/com/bluelinelabs/conductor/ControllerLifecycleCallbacksTests.java index ab974061..048ecff2 100644 --- a/conductor/src/test/java/com/bluelinelabs/conductor/ControllerLifecycleCallbacksTests.java +++ b/conductor/src/test/java/com/bluelinelabs/conductor/ControllerLifecycleCallbacksTests.java @@ -11,6 +11,7 @@ import com.bluelinelabs.conductor.util.MockChangeHandler; import com.bluelinelabs.conductor.util.MockChangeHandler.ChangeHandlerListener; import com.bluelinelabs.conductor.util.TestController; +import com.bluelinelabs.conductor.util.ViewUtils; import org.junit.Before; import org.junit.Test; @@ -19,6 +20,7 @@ import org.robolectric.annotation.Config; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; @RunWith(RobolectricTestRunner.class) @@ -71,6 +73,37 @@ public void testNormalLifecycle() { assertCalls(expectedCallState, controller); } + @Test + public void testLifecycleWithActivityStop() { + TestController controller = new TestController(); + attachLifecycleListener(controller); + + CallState expectedCallState = new CallState(); + + assertCalls(expectedCallState, controller); + router.pushController(RouterTransaction.with(controller) + .pushChangeHandler(getPushHandler(expectedCallState, controller))); + + assertCalls(expectedCallState, controller); + + activityProxy.getActivity().isDestroying = true; + activityProxy.pause(); + + assertCalls(expectedCallState, controller); + + activityProxy.stop(false); + + expectedCallState.detachCalls++; + assertCalls(expectedCallState, controller); + + assertNotNull(controller.getView()); + ViewUtils.reportAttached(controller.getView(), false); + + expectedCallState.saveViewStateCalls++; + expectedCallState.destroyViewCalls++; + assertCalls(expectedCallState, controller); + } + @Test public void testLifecycleWithActivityDestroy() { TestController controller = new TestController(); diff --git a/conductor/src/test/java/com/bluelinelabs/conductor/ViewAttachHandlerTests.java b/conductor/src/test/java/com/bluelinelabs/conductor/ViewAttachHandlerTests.java index 6da8f333..e08619e9 100644 --- a/conductor/src/test/java/com/bluelinelabs/conductor/ViewAttachHandlerTests.java +++ b/conductor/src/test/java/com/bluelinelabs/conductor/ViewAttachHandlerTests.java @@ -40,55 +40,107 @@ public void testSimpleViewAttachDetach() { assertEquals(0, viewAttachListener.attaches); assertEquals(0, viewAttachListener.detaches); + assertEquals(0, viewAttachListener.detachAfterStops); ViewUtils.reportAttached(view, true); assertEquals(1, viewAttachListener.attaches); assertEquals(0, viewAttachListener.detaches); + assertEquals(0, viewAttachListener.detachAfterStops); ViewUtils.reportAttached(view, true); assertEquals(1, viewAttachListener.attaches); assertEquals(0, viewAttachListener.detaches); + assertEquals(0, viewAttachListener.detachAfterStops); ViewUtils.reportAttached(view, false); assertEquals(1, viewAttachListener.attaches); assertEquals(1, viewAttachListener.detaches); + assertEquals(0, viewAttachListener.detachAfterStops); ViewUtils.reportAttached(view, false); assertEquals(1, viewAttachListener.attaches); assertEquals(1, viewAttachListener.detaches); + assertEquals(0, viewAttachListener.detachAfterStops); ViewUtils.reportAttached(view, true); assertEquals(2, viewAttachListener.attaches); assertEquals(1, viewAttachListener.detaches); + assertEquals(0, viewAttachListener.detachAfterStops); + + viewAttachHandler.onActivityStopped(); + assertEquals(2, viewAttachListener.attaches); + assertEquals(2, viewAttachListener.detaches); + assertEquals(0, viewAttachListener.detachAfterStops); + + ViewUtils.reportAttached(view, false); + assertEquals(2, viewAttachListener.attaches); + assertEquals(2, viewAttachListener.detaches); + assertEquals(1, viewAttachListener.detachAfterStops); + + ViewUtils.reportAttached(view, true); + assertEquals(2, viewAttachListener.attaches); + assertEquals(2, viewAttachListener.detaches); + assertEquals(1, viewAttachListener.detachAfterStops); + + viewAttachHandler.onActivityStarted(); + assertEquals(3, viewAttachListener.attaches); + assertEquals(2, viewAttachListener.detaches); + assertEquals(1, viewAttachListener.detachAfterStops); } @Test public void testSimpleViewGroupAttachDetach() { - View view = new LinearLayout(activity); + View view = new View(activity); viewAttachHandler.listenForAttach(view); assertEquals(0, viewAttachListener.attaches); assertEquals(0, viewAttachListener.detaches); + assertEquals(0, viewAttachListener.detachAfterStops); ViewUtils.reportAttached(view, true); assertEquals(1, viewAttachListener.attaches); assertEquals(0, viewAttachListener.detaches); + assertEquals(0, viewAttachListener.detachAfterStops); ViewUtils.reportAttached(view, true); assertEquals(1, viewAttachListener.attaches); assertEquals(0, viewAttachListener.detaches); + assertEquals(0, viewAttachListener.detachAfterStops); ViewUtils.reportAttached(view, false); assertEquals(1, viewAttachListener.attaches); assertEquals(1, viewAttachListener.detaches); + assertEquals(0, viewAttachListener.detachAfterStops); ViewUtils.reportAttached(view, false); assertEquals(1, viewAttachListener.attaches); assertEquals(1, viewAttachListener.detaches); + assertEquals(0, viewAttachListener.detachAfterStops); ViewUtils.reportAttached(view, true); assertEquals(2, viewAttachListener.attaches); assertEquals(1, viewAttachListener.detaches); + assertEquals(0, viewAttachListener.detachAfterStops); + + viewAttachHandler.onActivityStopped(); + assertEquals(2, viewAttachListener.attaches); + assertEquals(2, viewAttachListener.detaches); + assertEquals(0, viewAttachListener.detachAfterStops); + + ViewUtils.reportAttached(view, false); + assertEquals(2, viewAttachListener.attaches); + assertEquals(2, viewAttachListener.detaches); + assertEquals(1, viewAttachListener.detachAfterStops); + + ViewUtils.reportAttached(view, true); + assertEquals(2, viewAttachListener.attaches); + assertEquals(2, viewAttachListener.detaches); + assertEquals(1, viewAttachListener.detachAfterStops); + + viewAttachHandler.onActivityStarted(); + assertEquals(3, viewAttachListener.attaches); + assertEquals(2, viewAttachListener.detaches); + assertEquals(1, viewAttachListener.detachAfterStops); } @Test @@ -100,50 +152,85 @@ public void testNestedViewGroupAttachDetach() { assertEquals(0, viewAttachListener.attaches); assertEquals(0, viewAttachListener.detaches); + assertEquals(0, viewAttachListener.detachAfterStops); ViewUtils.reportAttached(view, true, false); assertEquals(0, viewAttachListener.attaches); assertEquals(0, viewAttachListener.detaches); + assertEquals(0, viewAttachListener.detachAfterStops); ViewUtils.reportAttached(child, true, false); assertEquals(1, viewAttachListener.attaches); assertEquals(0, viewAttachListener.detaches); + assertEquals(0, viewAttachListener.detachAfterStops); ViewUtils.reportAttached(view, true, false); ViewUtils.reportAttached(child, true, false); assertEquals(1, viewAttachListener.attaches); assertEquals(0, viewAttachListener.detaches); + assertEquals(0, viewAttachListener.detachAfterStops); ViewUtils.reportAttached(view, false, false); assertEquals(1, viewAttachListener.attaches); assertEquals(1, viewAttachListener.detaches); + assertEquals(0, viewAttachListener.detachAfterStops); ViewUtils.reportAttached(view, false, false); assertEquals(1, viewAttachListener.attaches); assertEquals(1, viewAttachListener.detaches); + assertEquals(0, viewAttachListener.detachAfterStops); ViewUtils.reportAttached(view, true, false); assertEquals(1, viewAttachListener.attaches); assertEquals(1, viewAttachListener.detaches); + assertEquals(0, viewAttachListener.detachAfterStops); ViewUtils.reportAttached(child, true, false); assertEquals(2, viewAttachListener.attaches); assertEquals(1, viewAttachListener.detaches); + assertEquals(0, viewAttachListener.detachAfterStops); + + viewAttachHandler.onActivityStopped(); + assertEquals(2, viewAttachListener.attaches); + assertEquals(2, viewAttachListener.detaches); + assertEquals(0, viewAttachListener.detachAfterStops); + + ViewUtils.reportAttached(view, false, false); + assertEquals(2, viewAttachListener.attaches); + assertEquals(2, viewAttachListener.detaches); + assertEquals(1, viewAttachListener.detachAfterStops); + + ViewUtils.reportAttached(view, true, false); + ViewUtils.reportAttached(child, true, false); + assertEquals(2, viewAttachListener.attaches); + assertEquals(2, viewAttachListener.detaches); + assertEquals(1, viewAttachListener.detachAfterStops); + + viewAttachHandler.onActivityStarted(); + assertEquals(3, viewAttachListener.attaches); + assertEquals(2, viewAttachListener.detaches); + assertEquals(1, viewAttachListener.detachAfterStops); } private static class CountingViewAttachListener implements ViewAttachListener { int attaches; int detaches; + int detachAfterStops; @Override - public void onAttached(View view) { + public void onAttached() { attaches++; } @Override - public void onDetached(View view) { + public void onDetached(boolean fromActivityStop) { detaches++; } + + @Override + public void onViewDetachAfterStop() { + detachAfterStops++; + } } } diff --git a/docs/Controller Lifecycle.jpg b/docs/Controller Lifecycle.jpg index 07d40c4b1ff0693968b1c5e60a21c9d0f8acda18..7247e612b8a6b5dbe8656e887b65a9b0fe4b5d1d 100644 GIT binary patch literal 66705 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

b zw$8=zYNnr(0grS_aO&>MnW4#MKkxHa%{%gT6K6)>+a-s}e!lEexO#eP@ybs@ovLTf z#yLnGR@-*+#p@58LYIzhzYy(ge6zB(G%b1By;rv`eOUQXc41cPUY&5YVKgI`3YeEBEnw&DHS#g}iKz2zD?C;is+YWI5viJ)Zp#8Za(*lGeb4khw$a=7ec#!eKc`vlGrcpb>YR{)+os=# zX4?r~xxQ-Yjj7p_Z{8ER9qO5vy=nI3J5v`vUCR4e;V#$a^Oui!&oQ&Eo~XsTE>*e& zc*WqMr^`1Sy45%Jf?KP_(OExGVIvylD7SuG`C<&kn(t2_CJ)4>eRL*3XPnfbr_qkQ&mv85-zWZmaTg1Bs?rkDQ z9Y}3b;Rraml?VnAu|{h1isj-AyE)FCzyl0#6(!5xkXU+*?dYuYOPA?KPZztj&A7|X zQ=cbsWv09F`@$^?I<9PD^lm`72a`3cJfxXUWb-q7Kh3d=?^x&PjcEevW_C<@3=uZf*@?hCR>yKa{5iFB_nf*L6M`15 z_0IK-_V!-0=k!@kK9kpes@l27fYVon-&f7;$&=U1F7$eqCettU5wz^2mJ7V>WDjWBiM-Q#(6SSK z@UoNk3#&oPPP{=2RTh5H0xeV-)qyvx>O}hr6HV2{{BB({xp?%9@3`6l|gblJHhkA*thDvfUYxb>Wm z+O6^NwzJ2pz)4k8mTH(?z(^qcBsaY BNmc*= literal 78891 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 From 690001ed2a42d2aa89ecbf63a48b0f972c22806c Mon Sep 17 00:00:00 2001 From: blazeroni Date: Wed, 1 Feb 2017 08:12:36 -0800 Subject: [PATCH 49/75] Fixes issue when retrieving an existing controller from a ControllerPagerAdapter (#216) --- .../conductor/support/ControllerPagerAdapter.java | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/conductor-support/src/main/java/com/bluelinelabs/conductor/support/ControllerPagerAdapter.java b/conductor-support/src/main/java/com/bluelinelabs/conductor/support/ControllerPagerAdapter.java index b162a180..fd8c15c8 100644 --- a/conductor-support/src/main/java/com/bluelinelabs/conductor/support/ControllerPagerAdapter.java +++ b/conductor-support/src/main/java/com/bluelinelabs/conductor/support/ControllerPagerAdapter.java @@ -53,12 +53,17 @@ public Object instantiateItem(ViewGroup container, int position) { } } + final Controller controller; if (!router.hasRootController()) { - Controller controller = getItem(position); + controller = getItem(position); router.setRoot(RouterTransaction.with(controller).tag(name)); - visiblePageIds.put(position, controller.getInstanceId()); } else { router.rebindIfNeeded(); + controller = router.getControllerWithTag(name); + } + + if (controller != null) { + visiblePageIds.put(position, controller.getInstanceId()); } return router.getControllerWithTag(name); @@ -139,4 +144,4 @@ private static String makeControllerName(int viewId, long id) { return viewId + ":" + id; } -} \ No newline at end of file +} From 6ffa94ed3a0a6d55b57f00c20734b10d30752fec Mon Sep 17 00:00:00 2001 From: Eric Kuck Date: Wed, 1 Feb 2017 11:01:29 -0600 Subject: [PATCH 50/75] =?UTF-8?q?Added=20documentation=20for=20@Nullable?= =?UTF-8?q?=E2=80=99s.=20Fixes=20#218?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../support/ControllerPagerAdapter.java | 3 +- .../conductor/support/RouterPagerAdapter.java | 3 +- .../bluelinelabs/conductor/Controller.java | 38 +++++++++++++------ .../conductor/ControllerChangeHandler.java | 25 ++++++------ .../RestoreViewOnCreateController.java | 2 +- .../com/bluelinelabs/conductor/Router.java | 11 +++--- .../changehandler/AnimatorChangeHandler.java | 4 +- .../TransitionChangeHandler.java | 8 ++-- 8 files changed, 56 insertions(+), 38 deletions(-) diff --git a/conductor-support/src/main/java/com/bluelinelabs/conductor/support/ControllerPagerAdapter.java b/conductor-support/src/main/java/com/bluelinelabs/conductor/support/ControllerPagerAdapter.java index fd8c15c8..fd9c06c6 100644 --- a/conductor-support/src/main/java/com/bluelinelabs/conductor/support/ControllerPagerAdapter.java +++ b/conductor-support/src/main/java/com/bluelinelabs/conductor/support/ControllerPagerAdapter.java @@ -124,7 +124,8 @@ public void restoreState(Parcelable state, ClassLoader loader) { } /** - * Returns the already instantiated Controller in the specified position, if available. + * Returns the already instantiated Controller in the specified position or {@code null} if + * this position does not yet have a controller. */ @Nullable public Controller getController(int position) { diff --git a/conductor-support/src/main/java/com/bluelinelabs/conductor/support/RouterPagerAdapter.java b/conductor-support/src/main/java/com/bluelinelabs/conductor/support/RouterPagerAdapter.java index cc01025f..96e9c74a 100644 --- a/conductor-support/src/main/java/com/bluelinelabs/conductor/support/RouterPagerAdapter.java +++ b/conductor-support/src/main/java/com/bluelinelabs/conductor/support/RouterPagerAdapter.java @@ -101,7 +101,8 @@ public void restoreState(Parcelable state, ClassLoader loader) { } /** - * Returns the already instantiated Router in the specified position, if available. + * Returns the already instantiated Router in the specified position or {@code null} if there + * is no router associated with this position. */ @Nullable public Router getRouter(int position) { diff --git a/conductor/src/main/java/com/bluelinelabs/conductor/Controller.java b/conductor/src/main/java/com/bluelinelabs/conductor/Controller.java index 3085b1f5..1aea64bb 100755 --- a/conductor/src/main/java/com/bluelinelabs/conductor/Controller.java +++ b/conductor/src/main/java/com/bluelinelabs/conductor/Controller.java @@ -183,7 +183,7 @@ public final Router getChildRouter(@NonNull ViewGroup container) { * the same container unless you have a great reason to do so (ex: ViewPagers). * * @param container The ViewGroup that hosts the child Router - * @param tag The router's tag + * @param tag The router's tag or {@code null} if none is needed */ @NonNull public final Router getChildRouter(@NonNull ViewGroup container, @Nullable String tag) { @@ -195,10 +195,12 @@ public final Router getChildRouter(@NonNull ViewGroup container, @Nullable Strin * Retrieves the child {@link Router} for the given container/tag combination. Note that multiple * routers should not exist in the same container unless a lot of care is taken to maintain order * between them. Avoid using the same container unless you have a great reason to do so (ex: ViewPagers). + * The only time this method will return {@code null} is when the child router does not exist prior + * to calling this method and the createIfNeeded parameter is set to false. * * @param container The ViewGroup that hosts the child Router - * @param tag The router's tag - * @param createIfNeeded If true, a router will be created if one does not yet exist. Else false will be returned in this case. + * @param tag The router's tag or {@code null} if none is needed + * @param createIfNeeded If true, a router will be created if one does not yet exist. Else {@code null} will be returned in this case. */ @Nullable public final Router getChildRouter(@NonNull ViewGroup container, @Nullable String tag, boolean createIfNeeded) { @@ -228,6 +230,12 @@ public final Router getChildRouter(@NonNull ViewGroup container, @Nullable Strin return childRouter; } + /** + * Removes a child {@link Router} from this Controller. When removed, all Controllers currently managed by + * the {@link Router} will be destroyed. + * + * @param childRouter The router to be removed + */ public final void removeChildRouter(@NonNull Router childRouter) { if ((childRouter instanceof ControllerHostedRouter) && childRouters.remove(childRouter)) { childRouter.destroy(true); @@ -256,7 +264,8 @@ public final boolean isAttached() { } /** - * Return this Controller's View, if available. + * Return this Controller's View or {@code null} if it has not yet been created or has been + * destroyed. */ @Nullable public final View getView() { @@ -264,7 +273,8 @@ public final View getView() { } /** - * Returns the host Activity of this Controller's {@link Router} + * Returns the host Activity of this Controller's {@link Router} or {@code null} if this + * Controller has not yet been attached to an Activity or if the Activity has been destroyed. */ @Nullable public final Activity getActivity() { @@ -272,7 +282,8 @@ public final Activity getActivity() { } /** - * Returns the Resources from the host Activity + * Returns the Resources from the host Activity or {@code null} if this Controller has not + * yet been attached to an Activity or if the Activity has been destroyed. */ @Nullable public final Resources getResources() { @@ -281,7 +292,8 @@ public final Resources getResources() { } /** - * Returns the Application Context derived from the host Activity + * Returns the Application Context derived from the host Activity or {@code null} if this Controller + * has not yet been attached to an Activity or if the Activity has been destroyed. */ @Nullable public final Context getApplicationContext() { @@ -290,7 +302,8 @@ public final Context getApplicationContext() { } /** - * Returns this Controller's parent Controller if it is a child Controller. + * Returns this Controller's parent Controller if it is a child Controller or {@code null} if + * it has no parent. */ @Nullable public final Controller getParentController() { @@ -307,10 +320,10 @@ public final String getInstanceId() { } /** - * Returns the Controller with the given instance id, if available. - * May return the controller itself or a matching descendant + * Returns the Controller with the given instance id or {@code null} if no such Controller + * exists. May return the Controller itself or a matching descendant + * * @param instanceId The instance ID being searched for - * @return The matching Controller, if one exists */ @Nullable final Controller findController(@NonNull String instanceId) { @@ -355,7 +368,8 @@ public void setTargetController(@Nullable Controller target) { } /** - * Returns the target Controller that was set with the {@link #setTargetController(Controller)} method + * Returns the target Controller that was set with the {@link #setTargetController(Controller)} + * method or {@code null} if this Controller has no target. * * @return This Controller's target */ diff --git a/conductor/src/main/java/com/bluelinelabs/conductor/ControllerChangeHandler.java b/conductor/src/main/java/com/bluelinelabs/conductor/ControllerChangeHandler.java index 3e99c931..fe793882 100644 --- a/conductor/src/main/java/com/bluelinelabs/conductor/ControllerChangeHandler.java +++ b/conductor/src/main/java/com/bluelinelabs/conductor/ControllerChangeHandler.java @@ -34,8 +34,8 @@ 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 from The previous View in the container or {@code null} if there was no Controller before this transition + * @param to The next View that should be put in the container or {@code null} if no Controller is being transitioned to * @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. */ @@ -63,8 +63,9 @@ public void restoreFromBundle(@NonNull Bundle bundle) { } * Will be called on change handlers that push a controller if the controller being pushed is * 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 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 or {@code null} + * if there will be no new Controller at the top */ public void onAbortPush(@NonNull ControllerChangeHandler newHandler, @Nullable Controller newTop) { } @@ -137,7 +138,7 @@ static boolean completePushImmediately(@NonNull String controllerInstanceId) { return false; } - public static void abortPush(@NonNull Controller toAbort, @Nullable Controller newController, @NonNull ControllerChangeHandler newChangeHandler) { + static void abortPush(@NonNull Controller toAbort, @Nullable Controller newController, @NonNull ControllerChangeHandler newChangeHandler) { ControllerChangeHandler handlerForPush = inProgressPushHandlers.get(toAbort.getInstanceId()); if (handlerForPush != null) { handlerForPush.onAbortPush(newChangeHandler, newController); @@ -145,11 +146,11 @@ public static void abortPush(@NonNull Controller toAbort, @Nullable Controller n } } - public static void executeChange(@Nullable final Controller to, @Nullable final Controller from, boolean isPush, @NonNull ViewGroup container, @NonNull ControllerChangeHandler inHandler) { + static void executeChange(@Nullable final Controller to, @Nullable final Controller from, boolean isPush, @NonNull ViewGroup container, @NonNull ControllerChangeHandler inHandler) { executeChange(to, from, isPush, container, inHandler, new ArrayList()); } - public static void executeChange(@Nullable final Controller to, @Nullable final Controller from, final boolean isPush, @Nullable final ViewGroup container, @Nullable final ControllerChangeHandler inHandler, @NonNull final List listeners) { + static void executeChange(@Nullable final Controller to, @Nullable final Controller from, final boolean isPush, @Nullable final ViewGroup container, @Nullable final ControllerChangeHandler inHandler, @NonNull final List listeners) { if (isPush && to != null && to.isDestroyed()) { throw new IllegalStateException("Trying to push a controller that has already been destroyed. (" + to.getClass().getSimpleName() + ")"); } @@ -240,8 +241,8 @@ public interface ControllerChangeListener { /** * Called when a {@link ControllerChangeHandler} has started changing {@link Controller}s * - * @param to The new Controller - * @param from The old Controller + * @param to The new Controller or {@code null} if no Controller is being transitioned to + * @param from The old Controller or {@code null} if there was no Controller before this transition * @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. @@ -251,9 +252,9 @@ public interface ControllerChangeListener { /** * 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 or {@code null} if no Controller is being transitioned to + * @param from The old Controller or {@code null} if there was no Controller before this transition + * @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. */ diff --git a/conductor/src/main/java/com/bluelinelabs/conductor/RestoreViewOnCreateController.java b/conductor/src/main/java/com/bluelinelabs/conductor/RestoreViewOnCreateController.java index 3b9160ef..4669a2f0 100644 --- a/conductor/src/main/java/com/bluelinelabs/conductor/RestoreViewOnCreateController.java +++ b/conductor/src/main/java/com/bluelinelabs/conductor/RestoreViewOnCreateController.java @@ -45,7 +45,7 @@ protected final View onCreateView(@NonNull LayoutInflater inflater, @NonNull Vie * This Controller's view should NOT be added in this method. It is simply passed in * so that valid LayoutParams can be used during inflation. * @param savedViewState A bundle for the view's state, which would have been created in {@link #onSaveViewState(View, Bundle)}, - * or null if no saved state exists. + * or {@code null} if no saved state exists. */ @NonNull protected abstract View onCreateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container, @Nullable Bundle savedViewState); diff --git a/conductor/src/main/java/com/bluelinelabs/conductor/Router.java b/conductor/src/main/java/com/bluelinelabs/conductor/Router.java index cc8c9b1c..0d2927d0 100755 --- a/conductor/src/main/java/com/bluelinelabs/conductor/Router.java +++ b/conductor/src/main/java/com/bluelinelabs/conductor/Router.java @@ -42,7 +42,8 @@ public abstract class Router { ViewGroup container; /** - * Returns this Router's host Activity + * Returns this Router's host Activity or {@code null} if it has either not yet been attached to + * an Activity or if the Activity has been destroyed. */ @Nullable public abstract Activity getActivity(); @@ -280,10 +281,10 @@ public void setRoot(@NonNull RouterTransaction transaction) { } /** - * Returns the hosted Controller with the given instance id, if available. + * Returns the hosted Controller with the given instance id or {@code null} if no such + * Controller exists in this Router. * * @param instanceId The instance ID being searched for - * @return The matching Controller, if one exists */ @Nullable public Controller getControllerWithInstanceId(@NonNull String instanceId) { @@ -297,10 +298,10 @@ public Controller getControllerWithInstanceId(@NonNull String instanceId) { } /** - * Returns the hosted Controller that was pushed with the given tag, if available. + * Returns the hosted Controller that was pushed with the given tag or {@code null} if no + * such Controller exists in this Router. * * @param tag The tag being searched for - * @return The matching Controller, if one exists */ @Nullable public Controller getControllerWithTag(@NonNull String tag) { diff --git a/conductor/src/main/java/com/bluelinelabs/conductor/changehandler/AnimatorChangeHandler.java b/conductor/src/main/java/com/bluelinelabs/conductor/changehandler/AnimatorChangeHandler.java index cda62004..979b1006 100755 --- a/conductor/src/main/java/com/bluelinelabs/conductor/changehandler/AnimatorChangeHandler.java +++ b/conductor/src/main/java/com/bluelinelabs/conductor/changehandler/AnimatorChangeHandler.java @@ -94,8 +94,8 @@ public boolean removesFromViewOnPush() { * Should be overridden to return the Animator to use while replacing Views. * * @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 from The previous View in the container or {@code null} if there was no Controller before this transition + * @param to The next View that should be put in the container or {@code null} if no Controller is being transitioned to * @param isPush True if this is a push transaction, false if it's a pop. * @param toAddedToContainer True if the "to" view was added to the container as a part of this ChangeHandler. False if it was already in the hierarchy. */ diff --git a/conductor/src/main/java/com/bluelinelabs/conductor/changehandler/TransitionChangeHandler.java b/conductor/src/main/java/com/bluelinelabs/conductor/changehandler/TransitionChangeHandler.java index 021179ce..aea4cfb1 100644 --- a/conductor/src/main/java/com/bluelinelabs/conductor/changehandler/TransitionChangeHandler.java +++ b/conductor/src/main/java/com/bluelinelabs/conductor/changehandler/TransitionChangeHandler.java @@ -24,10 +24,10 @@ public abstract class TransitionChangeHandler extends ControllerChangeHandler { /** * Should be overridden to return the Transition to use while replacing Views. * - * @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 or {@code null} if there was no Controller before this transition + * @param to The next View that should be put in the container or {@code null} if no Controller is being transitioned to + * @param isPush True if this is a push transaction, false if it's a pop */ @NonNull protected abstract Transition getTransition(@NonNull ViewGroup container, @Nullable View from, @Nullable View to, boolean isPush); From a9bdf0dd06b5f2e0fd6a980cb0e86eb737cef457 Mon Sep 17 00:00:00 2001 From: Eric Kuck Date: Wed, 1 Feb 2017 18:44:33 -0600 Subject: [PATCH 51/75] Revamped how child backtacks are handled to be more reliable with unforseen uses of state restoration. Fixes #194 and #217 --- .../support/ControllerPagerAdapter.java | 4 + .../conductor/ActivityHostedRouter.java | 21 +++++ .../bluelinelabs/conductor/Controller.java | 65 +++++---------- .../conductor/ControllerChangeHandler.java | 5 -- .../conductor/ControllerHostedRouter.java | 6 ++ .../com/bluelinelabs/conductor/Router.java | 80 ++++++++++--------- .../conductor/RouterTransaction.java | 17 ++++ .../internal/TransactionIndexer.java | 24 ++++++ .../conductor/ControllerTests.java | 43 ++++++++++ .../bluelinelabs/conductor/RouterTests.java | 59 ++++++++++++++ .../conductor/util/TestController.java | 4 +- .../demo/controllers/PagerController.java | 15 ++-- 12 files changed, 245 insertions(+), 98 deletions(-) create mode 100644 conductor/src/main/java/com/bluelinelabs/conductor/internal/TransactionIndexer.java diff --git a/conductor-support/src/main/java/com/bluelinelabs/conductor/support/ControllerPagerAdapter.java b/conductor-support/src/main/java/com/bluelinelabs/conductor/support/ControllerPagerAdapter.java index fd9c06c6..53ec15d7 100644 --- a/conductor-support/src/main/java/com/bluelinelabs/conductor/support/ControllerPagerAdapter.java +++ b/conductor-support/src/main/java/com/bluelinelabs/conductor/support/ControllerPagerAdapter.java @@ -13,8 +13,12 @@ import com.bluelinelabs.conductor.RouterTransaction; /** + * @deprecated Use RouterPagerAdapter instead! This implementation was too limited and had too many + * gotchas associated with it. + * * An adapter for ViewPagers that will handle adding and removing Controllers */ +@Deprecated public abstract class ControllerPagerAdapter extends PagerAdapter { private static final String KEY_SAVED_PAGES = "ControllerPagerAdapter.savedStates"; diff --git a/conductor/src/main/java/com/bluelinelabs/conductor/ActivityHostedRouter.java b/conductor/src/main/java/com/bluelinelabs/conductor/ActivityHostedRouter.java index 352ba6ad..39d3947e 100644 --- a/conductor/src/main/java/com/bluelinelabs/conductor/ActivityHostedRouter.java +++ b/conductor/src/main/java/com/bluelinelabs/conductor/ActivityHostedRouter.java @@ -9,12 +9,14 @@ import com.bluelinelabs.conductor.ControllerChangeHandler.ControllerChangeListener; import com.bluelinelabs.conductor.internal.LifecycleHandler; +import com.bluelinelabs.conductor.internal.TransactionIndexer; import java.util.List; public class ActivityHostedRouter extends Router { private LifecycleHandler lifecycleHandler; + private final TransactionIndexer transactionIndexer = new TransactionIndexer(); public final void setHost(@NonNull LifecycleHandler lifecycleHandler, @NonNull ViewGroup container) { if (this.lifecycleHandler != lifecycleHandler || this.container != container) { @@ -31,6 +33,20 @@ public final void setHost(@NonNull LifecycleHandler lifecycleHandler, @NonNull V } } + @Override + public void saveInstanceState(@NonNull Bundle outState) { + super.saveInstanceState(outState); + + transactionIndexer.saveInstanceState(outState); + } + + @Override + public void restoreInstanceState(@NonNull Bundle savedInstanceState) { + super.restoreInstanceState(savedInstanceState); + + transactionIndexer.restoreInstanceState(savedInstanceState); + } + @Override @Nullable public Activity getActivity() { return lifecycleHandler != null ? lifecycleHandler.getLifecycleActivity() : null; @@ -98,4 +114,9 @@ List getSiblingRouters() { Router getRootRouter() { return this; } + + @Override @Nullable + TransactionIndexer getTransactionIndexer() { + return transactionIndexer; + } } diff --git a/conductor/src/main/java/com/bluelinelabs/conductor/Controller.java b/conductor/src/main/java/com/bluelinelabs/conductor/Controller.java index 1aea64bb..fbea16f4 100755 --- a/conductor/src/main/java/com/bluelinelabs/conductor/Controller.java +++ b/conductor/src/main/java/com/bluelinelabs/conductor/Controller.java @@ -20,7 +20,6 @@ import android.view.View; import android.view.ViewGroup; -import com.bluelinelabs.conductor.Router.OnControllerPushedListener; import com.bluelinelabs.conductor.internal.ClassUtils; import com.bluelinelabs.conductor.internal.RouterRequiringFunc; import com.bluelinelabs.conductor.internal.ViewAttachHandler; @@ -30,7 +29,8 @@ import java.lang.reflect.Constructor; import java.util.ArrayList; import java.util.Arrays; -import java.util.LinkedList; +import java.util.Collections; +import java.util.Comparator; import java.util.List; import java.util.UUID; @@ -45,7 +45,6 @@ public abstract class Controller { private static final String KEY_CLASS_NAME = "Controller.className"; private static final String KEY_VIEW_STATE = "Controller.viewState"; private static final String KEY_CHILD_ROUTERS = "Controller.childRouters"; - private static final String KEY_CHILD_BACKSTACK = "Controller.childBackstack"; private static final String KEY_SAVED_STATE = "Controller.savedState"; private static final String KEY_INSTANCE_ID = "Controller.instanceId"; private static final String KEY_TARGET_INSTANCE_ID = "Controller.target.instanceId"; @@ -86,16 +85,8 @@ public abstract class Controller { private final List lifecycleListeners = new ArrayList<>(); private final ArrayList requestedPermissions = new ArrayList<>(); private final ArrayList onRouterSetListeners = new ArrayList<>(); - private final List childBackstack = new LinkedList<>(); private WeakReference destroyedView; - private final OnControllerPushedListener onControllerPushedListener = new OnControllerPushedListener() { - @Override - public void onControllerPushed(Controller controller) { - onChildControllerPushed(controller); - } - }; - @NonNull static Controller newInstance(@NonNull Bundle bundle) { final String className = bundle.getString(KEY_CLASS_NAME); @@ -217,13 +208,11 @@ public final Router getChildRouter(@NonNull ViewGroup container, @Nullable Strin if (childRouter == null) { if (createIfNeeded) { childRouter = new ControllerHostedRouter(container.getId(), tag); - monitorChildRouter(childRouter); childRouter.setHost(this, container); childRouters.add(childRouter); } } else if (!childRouter.hasHost()) { childRouter.setHost(this, container); - monitorChildRouter(childRouter); childRouter.rebindIfNeeded(); } @@ -566,8 +555,22 @@ public void onRequestPermissionsResult(int requestCode, @NonNull String[] permis * @return True if this Controller has consumed the back button press, otherwise false */ public boolean handleBack() { - for (int i = childBackstack.size() - 1; i >= 0; i--) { - Controller childController = childBackstack.get(i); + List childTransactions = new ArrayList<>(); + + for (ControllerHostedRouter childRouter : childRouters) { + childTransactions.addAll(childRouter.getBackstack()); + } + + Collections.sort(childTransactions, new Comparator() { + @Override + public int compare(RouterTransaction o1, RouterTransaction o2) { + return o2.transactionIndex - o1.transactionIndex; + } + }); + + for (RouterTransaction transaction : childTransactions) { + Controller childController = transaction.controller; + if (childController.isAttached() && childController.getRouter().handleBack()) { return true; } @@ -957,7 +960,6 @@ private void restoreChildControllerHosts() { if (containerView != null && containerView instanceof ViewGroup) { childRouter.setHost(this, (ViewGroup)containerView); - monitorChildRouter(childRouter); childRouter.rebindIfNeeded(); } } @@ -1069,12 +1071,6 @@ final Bundle saveInstanceState() { } outState.putParcelableArrayList(KEY_CHILD_ROUTERS, childBundles); - ArrayList childBackstack = new ArrayList<>(); - for (Controller controller : this.childBackstack) { - childBackstack.add(controller.instanceId); - } - outState.putStringArrayList(KEY_CHILD_BACKSTACK, childBackstack); - Bundle savedState = new Bundle(); onSaveInstanceState(savedState); @@ -1106,18 +1102,9 @@ private void restoreInstanceState(@NonNull Bundle savedInstanceState) { for (Bundle childBundle : childBundles) { ControllerHostedRouter childRouter = new ControllerHostedRouter(); childRouter.restoreInstanceState(childBundle); - monitorChildRouter(childRouter); childRouters.add(childRouter); } - List childBackstackIds = savedInstanceState.getStringArrayList(KEY_CHILD_BACKSTACK); - for (String controllerId : childBackstackIds) { - Controller childController = findController(controllerId); - if (childController != null) { - childBackstack.add(childController); - } - } - this.savedInstanceState = savedInstanceState.getBundle(KEY_SAVED_STATE); performOnRestoreInstanceState(); } @@ -1203,22 +1190,6 @@ final boolean optionsItemSelected(@NonNull MenuItem item) { return attached && hasOptionsMenu && !optionsMenuHidden && onOptionsItemSelected(item); } - private void monitorChildRouter(@NonNull ControllerHostedRouter childRouter) { - childRouter.setOnControllerPushedListener(onControllerPushedListener); - } - - private void onChildControllerPushed(@NonNull Controller 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(@Nullable Controller controller) { parentController = controller; } diff --git a/conductor/src/main/java/com/bluelinelabs/conductor/ControllerChangeHandler.java b/conductor/src/main/java/com/bluelinelabs/conductor/ControllerChangeHandler.java index fe793882..ba73cfd8 100644 --- a/conductor/src/main/java/com/bluelinelabs/conductor/ControllerChangeHandler.java +++ b/conductor/src/main/java/com/bluelinelabs/conductor/ControllerChangeHandler.java @@ -10,7 +10,6 @@ import com.bluelinelabs.conductor.changehandler.SimpleSwapChangeHandler; import com.bluelinelabs.conductor.internal.ClassUtils; -import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -146,10 +145,6 @@ static void abortPush(@NonNull Controller toAbort, @Nullable Controller newContr } } - static void executeChange(@Nullable final Controller to, @Nullable final Controller from, boolean isPush, @NonNull ViewGroup container, @NonNull ControllerChangeHandler inHandler) { - executeChange(to, from, isPush, container, inHandler, new ArrayList()); - } - static void executeChange(@Nullable final Controller to, @Nullable final Controller from, final boolean isPush, @Nullable final ViewGroup container, @Nullable final ControllerChangeHandler inHandler, @NonNull final List listeners) { if (isPush && to != null && to.isDestroyed()) { throw new IllegalStateException("Trying to push a controller that has already been destroyed. (" + to.getClass().getSimpleName() + ")"); diff --git a/conductor/src/main/java/com/bluelinelabs/conductor/ControllerHostedRouter.java b/conductor/src/main/java/com/bluelinelabs/conductor/ControllerHostedRouter.java index f2a6c4ee..b8c70cba 100644 --- a/conductor/src/main/java/com/bluelinelabs/conductor/ControllerHostedRouter.java +++ b/conductor/src/main/java/com/bluelinelabs/conductor/ControllerHostedRouter.java @@ -9,6 +9,7 @@ import android.view.ViewGroup; import com.bluelinelabs.conductor.ControllerChangeHandler.ControllerChangeListener; +import com.bluelinelabs.conductor.internal.TransactionIndexer; import java.util.ArrayList; import java.util.List; @@ -197,4 +198,9 @@ Router getRootRouter() { return this; } } + + @Override @Nullable + TransactionIndexer getTransactionIndexer() { + return getRootRouter().getTransactionIndexer(); + } } diff --git a/conductor/src/main/java/com/bluelinelabs/conductor/Router.java b/conductor/src/main/java/com/bluelinelabs/conductor/Router.java index 0d2927d0..b8028f1b 100755 --- a/conductor/src/main/java/com/bluelinelabs/conductor/Router.java +++ b/conductor/src/main/java/com/bluelinelabs/conductor/Router.java @@ -16,6 +16,7 @@ import com.bluelinelabs.conductor.ControllerChangeHandler.ControllerChangeListener; import com.bluelinelabs.conductor.changehandler.SimpleSwapChangeHandler; import com.bluelinelabs.conductor.internal.NoOpControllerChangeHandler; +import com.bluelinelabs.conductor.internal.TransactionIndexer; import java.util.ArrayList; import java.util.Collections; @@ -33,7 +34,6 @@ public abstract class Router { private static final String KEY_POPS_LAST_VIEW = "Router.popsLastView"; protected final Backstack backstack = new Backstack(); - private OnControllerPushedListener onControllerPushedListener; private final List changeListeners = new ArrayList<>(); final List destroyingControllers = new ArrayList<>(); @@ -53,8 +53,8 @@ public abstract class Router { * of the controller that called startActivityForResult is not known. * * @param requestCode The Activity's onActivityResult requestCode - * @param resultCode The Activity's onActivityResult resultCode - * @param data The Activity's onActivityResult data + * @param resultCode The Activity's onActivityResult resultCode + * @param data The Activity's onActivityResult data */ public abstract void onActivityResult(int requestCode, int resultCode, @Nullable Intent data); @@ -62,9 +62,9 @@ public abstract class Router { * This should be called by the host Activity when its onRequestPermissionsResult method is called. The call will be forwarded * to the {@link Controller} with the instanceId passed in. * - * @param instanceId The instanceId of the Controller to which this result should be forwarded - * @param requestCode The Activity's onRequestPermissionsResult requestCode - * @param permissions The Activity's onRequestPermissionsResult permissions + * @param instanceId The instanceId of the Controller to which this result should be forwarded + * @param requestCode The Activity's onRequestPermissionsResult requestCode + * @param permissions The Activity's onRequestPermissionsResult permissions * @param grantResults The Activity's onRequestPermissionsResult grantResults */ public void onRequestPermissionsResult(@NonNull String instanceId, int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { @@ -173,7 +173,7 @@ public void replaceTopController(@NonNull RouterTransaction transaction) { final boolean newHandlerRemovesViews = handler == null || handler.removesFromViewOnPush(); if (!oldHandlerRemovedViews && newHandlerRemovesViews) { for (RouterTransaction visibleTransaction : getVisibleTransactions(backstack.iterator())) { - performControllerChange(null, visibleTransaction.controller, true, handler); + performControllerChange(null, visibleTransaction, true, handler); } } } @@ -192,7 +192,7 @@ void destroy(boolean popViews) { trackDestroyingControllers(poppedControllers); if (popViews && poppedControllers.size() > 0) { - performControllerChange(null, poppedControllers.get(0).controller, false, poppedControllers.get(0).popChangeHandler()); + performControllerChange(null, poppedControllers.get(0), false, poppedControllers.get(0).popChangeHandler()); } } @@ -252,7 +252,7 @@ public boolean popToTag(@NonNull String tag) { /** * Pops all {@link Controller}s until the {@link Controller} with the passed tag is at the top * - * @param tag The tag being popped to + * @param tag The tag being popped to * @param changeHandler The {@link ControllerChangeHandler} to handle this transaction * @return Whether or not the {@link Controller} with the passed tag is now at the top */ @@ -337,7 +337,7 @@ public List getBackstack() { * Sets the backstack, transitioning from the current top controller to the top of the new stack (if different) * using the passed {@link ControllerChangeHandler} * - * @param newBackstack The new backstack + * @param newBackstack The new backstack * @param changeHandler An optional change handler to be used to handle the root view of transition */ @UiThread @@ -345,6 +345,7 @@ public void setBackstack(@NonNull List newBackstack, @Nullabl List oldVisibleTransactions = getVisibleTransactions(backstack.iterator()); removeAllExceptVisibleAndUnowned(); + ensureOrderedTransactionIndices(newBackstack); backstack.setBackstack(newBackstack); for (RouterTransaction transaction : backstack) { @@ -358,19 +359,19 @@ public void setBackstack(@NonNull List newBackstack, @Nullabl boolean visibleTransactionsChanged = !backstacksAreEqual(newVisibleTransactions, oldVisibleTransactions); if (visibleTransactionsChanged) { - Controller rootController = oldVisibleTransactions.size() > 0 ? oldVisibleTransactions.get(0).controller : null; - performControllerChange(newVisibleTransactions.get(0).controller, rootController, true, changeHandler); + RouterTransaction rootTransaction = oldVisibleTransactions.size() > 0 ? oldVisibleTransactions.get(0) : null; + performControllerChange(newVisibleTransactions.get(0), rootTransaction, true, changeHandler); for (int i = oldVisibleTransactions.size() - 1; i > 0; i--) { RouterTransaction transaction = oldVisibleTransactions.get(i); ControllerChangeHandler localHandler = changeHandler != null ? changeHandler.copy() : new SimpleSwapChangeHandler(); localHandler.setForceRemoveViewOnPush(true); - performControllerChange(null, transaction.controller, true, localHandler); + performControllerChange(null, transaction, true, localHandler); } for (int i = 1; i < newVisibleTransactions.size(); i++) { RouterTransaction transaction = newVisibleTransactions.get(i); - performControllerChange(transaction.controller, newVisibleTransactions.get(i - 1).controller, true, transaction.pushChangeHandler()); + performControllerChange(transaction, newVisibleTransactions.get(i - 1), true, transaction.pushChangeHandler()); } } @@ -379,12 +380,6 @@ public void setBackstack(@NonNull List newBackstack, @Nullabl transaction.controller.setRouter(this); } } - - if (onControllerPushedListener != null) { - for (RouterTransaction transaction : newBackstack) { - onControllerPushedListener.onControllerPushed(transaction.controller); - } - } } /** @@ -424,7 +419,7 @@ public void rebindIfNeeded() { RouterTransaction transaction = backstackIterator.next(); if (transaction.controller.getNeedsAttach()) { - performControllerChange(transaction.controller, null, true, new SimpleSwapChangeHandler(false)); + performControllerChange(transaction, null, true, new SimpleSwapChangeHandler(false)); } } } @@ -575,14 +570,10 @@ private void popToTransaction(@NonNull RouterTransaction transaction, @Nullable changeHandler = topTransaction.popChangeHandler(); } - performControllerChange(backstack.peek().controller, topTransaction.controller, false, changeHandler); + performControllerChange(backstack.peek(), topTransaction, false, changeHandler); } } - final void setOnControllerPushedListener(OnControllerPushedListener listener) { - onControllerPushedListener = listener; - } - void prepareForContainerRemoval() { if (container != null) { container.setOnHierarchyChangeListener(null); @@ -626,30 +617,27 @@ private void performControllerChange(@Nullable RouterTransaction to, @Nullable R changeHandler = null; } + performControllerChange(to, from, isPush, changeHandler); + } + + private void performControllerChange(@Nullable final RouterTransaction to, @Nullable final RouterTransaction from, boolean isPush, @Nullable ControllerChangeHandler changeHandler) { Controller toController = to != null ? to.controller : null; Controller fromController = from != null ? from.controller : null; - performControllerChange(toController, fromController, isPush, changeHandler); - } - - private void performControllerChange(@Nullable final Controller to, @Nullable final Controller from, boolean isPush, @Nullable ControllerChangeHandler changeHandler) { if (to != null) { - setControllerRouter(to); + to.ensureValidIndex(getTransactionIndexer()); + setControllerRouter(toController); } 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(); } - ControllerChangeHandler.executeChange(to, from, isPush, container, changeHandler, changeListeners); + ControllerChangeHandler.executeChange(toController, fromController, isPush, container, changeHandler, changeListeners); } private void pushToBackstack(@NonNull RouterTransaction entry) { backstack.push(entry); - - if (onControllerPushedListener != null) { - onControllerPushedListener.onControllerPushed(entry.controller); - } } private void trackDestroyingController(@NonNull RouterTransaction transaction) { @@ -695,6 +683,22 @@ private void removeAllExceptVisibleAndUnowned() { } } + // Swap around transaction indicies to ensure they don't get thrown out of order by the + // developer rearranging the backstack at runtime. + private void ensureOrderedTransactionIndices(List backstack) { + List indices = new ArrayList<>(); + for (RouterTransaction transaction : backstack) { + transaction.ensureValidIndex(getTransactionIndexer()); + indices.add(transaction.transactionIndex); + } + + Collections.sort(indices); + + for (int i = 0; i < backstack.size(); i++) { + backstack.get(i).transactionIndex = indices.get(i); + } + } + private void addRouterViewsToList(@NonNull Router router, @NonNull List list) { for (Controller controller : router.getControllers()) { if (controller.getView() != null) { @@ -750,8 +754,6 @@ void setControllerRouter(@NonNull Controller controller) { abstract boolean hasHost(); @NonNull abstract List getSiblingRouters(); @NonNull abstract Router getRootRouter(); + @Nullable abstract TransactionIndexer getTransactionIndexer(); - interface OnControllerPushedListener { - void onControllerPushed(Controller controller); - } } diff --git a/conductor/src/main/java/com/bluelinelabs/conductor/RouterTransaction.java b/conductor/src/main/java/com/bluelinelabs/conductor/RouterTransaction.java index 67a5813f..a75759e3 100644 --- a/conductor/src/main/java/com/bluelinelabs/conductor/RouterTransaction.java +++ b/conductor/src/main/java/com/bluelinelabs/conductor/RouterTransaction.java @@ -4,15 +4,20 @@ import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import com.bluelinelabs.conductor.internal.TransactionIndexer; + /** * Metadata used for adding {@link Controller}s to a {@link Router}. */ public class RouterTransaction { + private static int INVALID_INDEX = -1; + private static final String KEY_VIEW_CONTROLLER_BUNDLE = "RouterTransaction.controller.bundle"; private static final String KEY_PUSH_TRANSITION = "RouterTransaction.pushControllerChangeHandler"; private static final String KEY_POP_TRANSITION = "RouterTransaction.popControllerChangeHandler"; private static final String KEY_TAG = "RouterTransaction.tag"; + private static final String KEY_INDEX = "RouterTransaction.transactionIndex"; private static final String KEY_ATTACHED_TO_ROUTER = "RouterTransaction.attachedToRouter"; @NonNull final Controller controller; @@ -21,6 +26,7 @@ public class RouterTransaction { private ControllerChangeHandler pushControllerChangeHandler; private ControllerChangeHandler popControllerChangeHandler; private boolean attachedToRouter; + int transactionIndex = INVALID_INDEX; @NonNull public static RouterTransaction with(@NonNull Controller controller) { @@ -36,6 +42,7 @@ private RouterTransaction(@NonNull Controller controller) { pushControllerChangeHandler = ControllerChangeHandler.fromBundle(bundle.getBundle(KEY_PUSH_TRANSITION)); popControllerChangeHandler = ControllerChangeHandler.fromBundle(bundle.getBundle(KEY_POP_TRANSITION)); tag = bundle.getString(KEY_TAG); + transactionIndex = bundle.getInt(KEY_INDEX); attachedToRouter = bundle.getBoolean(KEY_ATTACHED_TO_ROUTER); } @@ -101,6 +108,15 @@ public RouterTransaction popChangeHandler(@Nullable ControllerChangeHandler hand } } + void ensureValidIndex(@Nullable TransactionIndexer indexer) { + if (indexer == null) { + throw new RuntimeException(); + } + if (transactionIndex == INVALID_INDEX && indexer != null) { + transactionIndex = indexer.nextIndex(); + } + } + /** * Used to serialize this transaction into a Bundle */ @@ -118,6 +134,7 @@ public Bundle saveInstanceState() { } bundle.putString(KEY_TAG, tag); + bundle.putInt(KEY_INDEX, transactionIndex); bundle.putBoolean(KEY_ATTACHED_TO_ROUTER, attachedToRouter); return bundle; diff --git a/conductor/src/main/java/com/bluelinelabs/conductor/internal/TransactionIndexer.java b/conductor/src/main/java/com/bluelinelabs/conductor/internal/TransactionIndexer.java new file mode 100644 index 00000000..5d507f7b --- /dev/null +++ b/conductor/src/main/java/com/bluelinelabs/conductor/internal/TransactionIndexer.java @@ -0,0 +1,24 @@ +package com.bluelinelabs.conductor.internal; + +import android.os.Bundle; +import android.support.annotation.NonNull; + +public class TransactionIndexer { + + private static final String KEY_INDEX = "TransactionIndexer.currentIndex"; + + private int currentIndex; + + public int nextIndex() { + return ++currentIndex; + } + + public void saveInstanceState(@NonNull Bundle outState) { + outState.putInt(KEY_INDEX, currentIndex); + } + + public void restoreInstanceState(@NonNull Bundle savedInstanceState) { + currentIndex = savedInstanceState.getInt(KEY_INDEX); + } + +} diff --git a/conductor/src/test/java/com/bluelinelabs/conductor/ControllerTests.java b/conductor/src/test/java/com/bluelinelabs/conductor/ControllerTests.java index 00b7ff30..25d51212 100644 --- a/conductor/src/test/java/com/bluelinelabs/conductor/ControllerTests.java +++ b/conductor/src/test/java/com/bluelinelabs/conductor/ControllerTests.java @@ -21,6 +21,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; @RunWith(RobolectricTestRunner.class) @Config(manifest = Config.NONE) @@ -362,6 +363,48 @@ public void testAddRemoveChildRouters() { assertNull(child2.getParentController()); } + @Test + public void testRestoredChildRouterBackstack() { + TestController parent = new TestController(); + router.pushController(RouterTransaction.with(parent)); + ViewUtils.reportAttached(parent.getView(), true); + + RouterTransaction childTransaction1 = RouterTransaction.with(new TestController()); + RouterTransaction childTransaction2 = RouterTransaction.with(new TestController()); + + Router childRouter = parent.getChildRouter((ViewGroup)parent.getView().findViewById(TestController.CHILD_VIEW_ID_1)); + childRouter.setPopsLastView(true); + childRouter.setRoot(childTransaction1); + childRouter.pushController(childTransaction2); + + Bundle savedState = new Bundle(); + childRouter.saveInstanceState(savedState); + parent.removeChildRouter(childRouter); + + childRouter = parent.getChildRouter((ViewGroup)parent.getView().findViewById(TestController.CHILD_VIEW_ID_1)); + assertEquals(0, childRouter.getBackstackSize()); + + childRouter.restoreInstanceState(savedState); + childRouter.rebindIfNeeded(); + + assertEquals(2, childRouter.getBackstackSize()); + + RouterTransaction restoredChildTransaction1 = childRouter.getBackstack().get(0); + RouterTransaction restoredChildTransaction2 = childRouter.getBackstack().get(1); + + assertEquals(childTransaction1.transactionIndex, restoredChildTransaction1.transactionIndex); + assertEquals(childTransaction1.controller.getInstanceId(), restoredChildTransaction1.controller.getInstanceId()); + assertEquals(childTransaction2.transactionIndex, restoredChildTransaction2.transactionIndex); + assertEquals(childTransaction2.controller.getInstanceId(), restoredChildTransaction2.controller.getInstanceId()); + + assertTrue(parent.handleBack()); + assertEquals(1, childRouter.getBackstackSize()); + assertEquals(restoredChildTransaction1, childRouter.getBackstack().get(0)); + + assertTrue(parent.handleBack()); + assertEquals(0, childRouter.getBackstackSize()); + } + private void assertCalls(CallState callState, TestController controller) { assertEquals("Expected call counts and controller call counts do not match.", callState, controller.currentCallState); } diff --git a/conductor/src/test/java/com/bluelinelabs/conductor/RouterTests.java b/conductor/src/test/java/com/bluelinelabs/conductor/RouterTests.java index eae0661a..fcbd441f 100644 --- a/conductor/src/test/java/com/bluelinelabs/conductor/RouterTests.java +++ b/conductor/src/test/java/com/bluelinelabs/conductor/RouterTests.java @@ -1,5 +1,7 @@ package com.bluelinelabs.conductor; +import android.view.ViewGroup; + import com.bluelinelabs.conductor.util.ActivityProxy; import com.bluelinelabs.conductor.util.ListUtils; import com.bluelinelabs.conductor.util.MockChangeHandler; @@ -312,4 +314,61 @@ public void testReplaceTopControllerWithNoRemoveViewOnPush() { assertTrue(newTopTransaction.controller.isAttached()); } + @Test + public void testRearrangeTransactionBackstack() { + RouterTransaction transaction1 = RouterTransaction.with(new TestController()); + RouterTransaction transaction2 = RouterTransaction.with(new TestController()); + + List backstack = ListUtils.listOf(transaction1, transaction2); + router.setBackstack(backstack, null); + + assertEquals(1, transaction1.transactionIndex); + assertEquals(2, transaction2.transactionIndex); + + backstack = ListUtils.listOf(transaction2, transaction1); + router.setBackstack(backstack, null); + + assertEquals(1, transaction2.transactionIndex); + assertEquals(2, transaction1.transactionIndex); + + router.handleBack(); + + assertEquals(1, router.getBackstackSize()); + assertEquals(transaction2, router.getBackstack().get(0)); + + router.handleBack(); + assertEquals(0, router.getBackstackSize()); + } + + @Test + public void testChildRouterRearrangeTransactionBackstack() { + Controller parent = new TestController(); + router.setRoot(RouterTransaction.with(parent)); + + Router childRouter = parent.getChildRouter((ViewGroup)parent.getView().findViewById(TestController.CHILD_VIEW_ID_1)); + + RouterTransaction transaction1 = RouterTransaction.with(new TestController()); + RouterTransaction transaction2 = RouterTransaction.with(new TestController()); + + List backstack = ListUtils.listOf(transaction1, transaction2); + childRouter.setBackstack(backstack, null); + + assertEquals(2, transaction1.transactionIndex); + assertEquals(3, transaction2.transactionIndex); + + backstack = ListUtils.listOf(transaction2, transaction1); + childRouter.setBackstack(backstack, null); + + assertEquals(2, transaction2.transactionIndex); + assertEquals(3, transaction1.transactionIndex); + + childRouter.handleBack(); + + assertEquals(1, childRouter.getBackstackSize()); + assertEquals(transaction2, childRouter.getBackstack().get(0)); + + childRouter.handleBack(); + assertEquals(0, childRouter.getBackstackSize()); + } + } diff --git a/conductor/src/test/java/com/bluelinelabs/conductor/util/TestController.java b/conductor/src/test/java/com/bluelinelabs/conductor/util/TestController.java index f9eb9d71..779d78e5 100644 --- a/conductor/src/test/java/com/bluelinelabs/conductor/util/TestController.java +++ b/conductor/src/test/java/com/bluelinelabs/conductor/util/TestController.java @@ -33,11 +33,11 @@ protected View onCreateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup FrameLayout view = new AttachFakingFrameLayout(inflater.getContext()); view.setId(VIEW_ID); - FrameLayout childContainer1 = new FrameLayout(inflater.getContext()); + FrameLayout childContainer1 = new AttachFakingFrameLayout(inflater.getContext()); childContainer1.setId(CHILD_VIEW_ID_1); view.addView(childContainer1); - FrameLayout childContainer2 = new FrameLayout(inflater.getContext()); + FrameLayout childContainer2 = new AttachFakingFrameLayout(inflater.getContext()); childContainer2.setId(CHILD_VIEW_ID_2); view.addView(childContainer2); diff --git a/demo/src/main/java/com/bluelinelabs/conductor/demo/controllers/PagerController.java b/demo/src/main/java/com/bluelinelabs/conductor/demo/controllers/PagerController.java index fec0430a..f5def56b 100644 --- a/demo/src/main/java/com/bluelinelabs/conductor/demo/controllers/PagerController.java +++ b/demo/src/main/java/com/bluelinelabs/conductor/demo/controllers/PagerController.java @@ -8,9 +8,11 @@ import android.view.ViewGroup; import com.bluelinelabs.conductor.Controller; +import com.bluelinelabs.conductor.Router; +import com.bluelinelabs.conductor.RouterTransaction; import com.bluelinelabs.conductor.demo.R; import com.bluelinelabs.conductor.demo.controllers.base.BaseController; -import com.bluelinelabs.conductor.support.ControllerPagerAdapter; +import com.bluelinelabs.conductor.support.RouterPagerAdapter; import java.util.Locale; @@ -23,13 +25,16 @@ public class PagerController extends BaseController { @BindView(R.id.tab_layout) TabLayout tabLayout; @BindView(R.id.view_pager) ViewPager viewPager; - private final ControllerPagerAdapter pagerAdapter; + private final RouterPagerAdapter pagerAdapter; public PagerController() { - pagerAdapter = new ControllerPagerAdapter(this, false) { + pagerAdapter = new RouterPagerAdapter(this) { @Override - public Controller getItem(int position) { - return new ChildController(String.format(Locale.getDefault(), "Child #%d (Swipe to see more)", position), PAGE_COLORS[position], true); + public void configureRouter(Router router, int position) { + if (!router.hasRootController()) { + Controller page = new ChildController(String.format(Locale.getDefault(), "Child #%d (Swipe to see more)", position), PAGE_COLORS[position], true); + router.setRoot(RouterTransaction.with(page)); + } } @Override From afa4b69d7ae81cb51a7511c591bf232f15f8f712 Mon Sep 17 00:00:00 2001 From: Eric Kuck Date: Wed, 1 Feb 2017 19:04:01 -0600 Subject: [PATCH 52/75] Version bump --- README.md | 16 ++++++++-------- gradle.properties | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 9675040c..44ec124e 100644 --- a/README.md +++ b/README.md @@ -20,26 +20,26 @@ Conductor is architecture-agnostic and does not try to force any design decision ## Installation ```gradle -compile 'com.bluelinelabs:conductor:2.0.7' +compile 'com.bluelinelabs:conductor:2.1.0' // If you want the components that go along with // Android's support libraries (currently just a PagerAdapter): -compile 'com.bluelinelabs:conductor-support:2.0.7' +compile 'com.bluelinelabs:conductor-support:2.1.0' // If you want RxJava lifecycle support: -compile 'com.bluelinelabs:conductor-rxlifecycle:2.0.7' +compile 'com.bluelinelabs:conductor-rxlifecycle:2.1.0' // If you want RxJava2 lifecycle support: -compile 'com.bluelinelabs:conductor-rxlifecycle2:2.0.7' +compile 'com.bluelinelabs:conductor-rxlifecycle2:2.1.0' ``` SNAPSHOT: ```gradle -compile 'com.bluelinelabs:conductor:2.0.6-SNAPSHOT' -compile 'com.bluelinelabs:conductor-support:2.0.6-SNAPSHOT' -compile 'com.bluelinelabs:conductor-rxlifecycle:2.0.6-SNAPSHOT' -compile 'com.bluelinelabs:conductor-rxlifecycle2:2.0.6-SNAPSHOT' +compile 'com.bluelinelabs:conductor:2.1.1-SNAPSHOT' +compile 'com.bluelinelabs:conductor-support:2.1.1-SNAPSHOT' +compile 'com.bluelinelabs:conductor-rxlifecycle:2.1.1-SNAPSHOT' +compile 'com.bluelinelabs:conductor-rxlifecycle2:2.1.1-SNAPSHOT' ``` You also have to add the url to the snapshot repository: diff --git a/gradle.properties b/gradle.properties index 50b9bc24..9ce0f960 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ -VERSION_NAME=2.0.8-SNAPSHOT +VERSION_NAME=2.1.1-SNAPSHOT VERSION_CODE=2 GROUP=com.bluelinelabs From 769d552e885c00c82e9b53cb6764ffa69907e68c Mon Sep 17 00:00:00 2001 From: Eric Kuck Date: Wed, 1 Feb 2017 19:13:50 -0600 Subject: [PATCH 53/75] Fixed typo in lifecycle diagram --- docs/Controller Lifecycle.jpg | Bin 66705 -> 67019 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/docs/Controller Lifecycle.jpg b/docs/Controller Lifecycle.jpg index 7247e612b8a6b5dbe8656e887b65a9b0fe4b5d1d..6eea5d32dbd642e6db1fbf593823b07d12625263 100644 GIT binary patch delta 35441 zcmb@tbyQnh7cUAGDwMXA;&yN-R@~vVMT!@P1Zk0=f#RN=Q^9=$!D-Rrk_49)3jqR! zKq!77NRi;Kmvg`S-TU7A=Z*2k+heRf*V=3BJ@?piF4@1|oQaXQ`lD}EOWr4+0;KM% z-MD=Z2)Qq3A?foWkB8UD_$!~P-KYH8AH#TRaCNBa+zbi3p^*A*Jxz`i;JNKBz34@`d}O z^W@xGD~0JmmoCH^@9kPQ6%B`Y!19W$3u_OJqb|$cSh~pv|D-pBZ#RpdSGb6~aeUN4 zb0RE(TBKi93*}V7HT~Hq%Zof~JZ;-|4bb1%fRC9ic`Suo%*n=g=S$ZhR<|u(3FL@< zI^A|TIHm2*^P=R*^2>vln6WFeL+fvW7kA1pH({PfwN;-k z2lX#Z;E*dgS>G}mQ&eYmxmO`=|K#{g%e->;ii}EWK4u$g33n78-HxAFKXO^!nN|Mf zSME8MKi1zVxVP4EjGLC;)DYbWr+blmNM+P1WpI(wW|$pa&vO4_a$1EYCBE+6bPo|8 z=FF=ZtXNYjaYYt1MUNG{+a6U!5Lde*OB;jI1s%N`8Qx9_6B&6wfUi^16AZ33g<&0h zr$O7D_65>|7vJ-Gy&D?{^gq8S=3m@%0hgB#9Z^%!6<|Vy#UPj@nZi7~359wp3Xd=c zXP$)#Uy;|7(ItYVxlHdE0|OJQ7kY#TfWJ>^fajEV$?pJvA4^+GWB5~Qh z22?hM5|!g%#!*;`cs=F5xAOeEkDpzTfekA}JZS>6qdfOsgcUDcnu*Cn4mlkWvheJ5 znaxBA&q*Mn3w{6-4o5lmDTTd)K3moh9a+5l;02V|=Botl`D^gIjke&x!}3t?qfqxd z5yxKhY4x$vEa`mX3Wms?S?F1X1+-Z!{!pql*)8mNS~1tb?Lk`h z%9NOlP@}4e_qE+?)RMbwuE+}bi2umThf-bashoOcv{SO?k9tsli&8xm)Y2FilRo`b zCf^K3a(E3<a#k+JpZ(Ai_n&WBo38sL*(rL$CY^)`G@US*6PHpi@~FqYxOl;atl z;*)hW;G+W^W@ctw$jXmwl$MP?80WfZ#P$p7ds2n2GtB&ZF5=7CyCA6YMEaTK58yS~ ze~z2WdqSFSQwbwBPUqizB6_dLBH7>Ts2bmd z4&}*Ki+MH>h`LQdj7D4Z?ejHL*y9E~+z*IyZl^89+4yN|j5XI*_Dp@{P8CjaNb`3* zk=~ikSS!rCnE9kxA?a{Mc0bAC?2}LyjRx%8b2~l0)`|}3@Nh!S2$rHSD{3AGlSmct zI}c(cf#*@cJYWu%A_6MuI9>5hB$+${{n+xx4ND5}VJ;&mA6_MgR6-8*DfBJ8n;QfHo8_p4jTAqXHqnNpP{@>!O#x{6(Q=Dc7H3d zpBWAIUm4LKK_jO-S?x}j&Bp(}kf?SZC3F#jiX4NnFi z*gb;_E5XAF#b*%{3ss##Tr^u8R{c&bd9e7;D`PN`RweHdWw>`Di5_~aPacc5u*)6R zIx))~iz8k5O)8bpi_8YEyk)MKSkZNzG;2!A^1tIoly_3$v}sD!(*{K!5TDwdz~B;V zlC21gbz_BeuCY&i8?3nVcqN?59lH9ZlVSbDYI9t&SCl+gH2uH|)W5FOnf{=0 zd6Z->j6L-5+VvwmN!|!oEIuA&XvhD4?|el@|2n>YW%fjdjwC~~fRrd3wcb&V%r4t5 zfSMn_`s0c5+yCXZ|5E_Mly~3{b5_{C_L}Rsu9?dSNr&o6i7R;c2=-}dfb4hTC*zBd*RcXgPcw3GtF;G3`?-$hC;D@binnbZv^c7XS?R+?f?)mt{PQ|< z2o(G*gex|Ti`nF?m=aFA%e|N7imoqI-om6X@(XOAp{#11VuW}5m%RVgDCvau7 zHJmlixo*sTwx;SnEZkb~8XCrq@WQ1p-40f%CFpVu=P?(V7*~hh_xY9_ebUy zBG-1_=zWFhNy%fWOc5HdasdC7)9OSy;^_2ZW(a51wB4F%;OZP;yi1G&5Ub#Q72fvj z75(9*tt{4v4b^vP!;CJH<|M#?D6aa6`%n6y!@~!mueR;^GK${p#fn}(DLLh8bugtzUCt;CugLE3UGRI+dIFC z@I<|5%9hqu)+{7Chp+41>)CIy)7CZ3m>#VkW8I{a-!v-?|D?ithFe z_@48Qex#@MX0GGyXt+t3nHs8JoGG|L^o8eHgr53m%Q*|i8lHwWYK~6nEKk-dd&iNw ze#DE3Rwb{sz>0mlfTJVjSM!-M4#V%AI-~q{#-l)R31@WvL0v?ym4CR7!qMWF|7xbF z{6#OvIK9B2E?|$NDze%4N(tuF9nj)Y-^rIE=4b5C1{sXfd;RmCZYZ4Vy6g{ zM~iQ_E5bEQq`=`wWMw~I!G`^zXG9Hgrnfp{wfem^^b;nkRw@$`4iA)8RjTa7I+8r;fhYUie;n} zTbH?j_Egzgf9>}me&lJ3|}`!9_k9mk)$Jjdc`h87k~*tsBQGXrYw zfp_ja{mgm_-X7HOemty3f@ZF6M&ao9OneQHIz2k}E{jcMLecHGx9ArMWR0{aj*0G? zY&HY>W}9`k9xY~1Jd|V_&0+W4$mrNKRoYM+FG~w8N%*y0e9tQI};UZ|kR`Mwcz$VVWU?k)_1k#b(Lnr#+-roDJ-V`^?=4G6DkK+YF>u6a%=9Z-ig>lOiYQM&^=MthCL zfw5(aB=``G`5?caf*GGuNkDgxl6YHe)@R>32Zr2jxrcqwB=L7gks|zg8L_;fU3px} zA-x3)1n;d@2$FVr(r+{S!bmAdz;`A)TSf>kt(a10(T&q{w+`y&o%MBXwaeiOh-=oH zkxs|VOb^dMBPAP@H4=V6D%%z=Eh!dqqwOXn;rg|cQ_M}W zWOU35iDQqqW8(DGg5^8vOm2X~^a#DB8+w`po`#WmZiyU}T2mmbrsVFH!Q(;M7#%{5 zwztjz6pvk+3Oyy&TOGv?uhi7TE5w)DiXYfwK-QwIhgi#&=^PQxtm&x{S1FXKtC+%m zWa=v2!{`4U!sK(V4?eh4x9uNlf_5|X4BiK*8&LCxN(e$Hd=0Sn8ogs|t~yPh8QHah zRS}$?>T;EM=XEzR1m0q5US<8<#(kT;dv|^zP!c>8jn%B1Eqjc2Z^Nxi(5z3k6(c`> zh7|%zy&SiHbXhjL0gEhuQ!sle&rYEMS5{v5ILq5LIXwS+QsIXYIT647Sb3^8KI0)7 z2cq=7cZP`%71Xw7!WPwSpeTeChe+K76Cp`KnYuETEuiHDITAYed%oq5L+hEmc~d;` z910QDJ!>p$fvOZ*WSr(fc!Lv#p~;jHk4Y}e67Rro`#n0-IzQEALpzo~e^r~dw^xRz zXcG>iP5mQbf}^Xo&b44V5YEFQEiG-*)~=10W%&PWS#JzJ$EM5whB}K*_lSpBgOdb= zBR?c#0bHgR7dECaAMHE}6MHg}C$5#0Cr<4vAK2gTGL<@(1I%qj#_MPueejHOvJ(;d zl`q1Aj%f8?$^lxT+9bGQefg0GU1a37%2aAWgPG~$ku6SKAIzIm%{YVE{Gb<7_iZ&7 zypw5rW7gM2i?DEJ2VO2r+|^3#ZB(!Oa5OvKcC?BjEsm%9$CkC62W=^L&x#P7_ove& zPu;B~)nB(l_TUJRwhZ~U%!rOdM%=b zR5PQ@Z4WOhJj-JxSc6`PynLah@v)v2gA;JVb!@<~{A*kyHKnrl-Ey24yhw}H^;tXj zR!*7Cb$l^eKG-wvG`B;vI34Ycm-1_ywKuJc0kDdPt*$gOarz>VDII-5!2#b{wLqx+ zIOD)%_gL_s=AXmU_O}K) z7Qrv%58`T22f3iKPktY@3p*`A*@{a$fgw=oc5ZJSQ(-Vh_@qoRWivxxa~;)W@1he{ zg8Ufeh>81AUtU_?XFTR9VmnwT&zYQYa+{<5wlCrFYYC7w_pVlwW$(#)?Cn)N>xr!` zXOQo%9dun`KRrIk{t&5~6L)DrgXr{f;jis7QXbS| z&L|INQVY~6r`7)uT8|DB8C@-Y`VRMjP*`I|r)QbfGAlCdW>%c&R9IF=h*sBvPxAUj zHHX+)v}**K0p|969zMCON+Bm|8_X%ZOYe0?`|fr730Ft(L+?shr2CgZDj~t|or+l( z_Mj_$bHyQ39?2Rav$;WhG$bBhQ1EKa!XoCv*if#q2;Ir3xH>6U5!f${Zb^&WtejGX zdP?>E_1{TPy8VIqEgZbK?wL37-H>amg1GjM@Kc+=>iXzee`&@u4DH5_OP`hbI&1}d zLvQ1B8#EW1HM?9>?QZi0M9zj@R)rT|uos0;`N>E})h%R1?G9?~5+k=3(<3tnV1C2> ztCPIJ6_3m@&(41SSLJ`@&8zXF?f*1@C{A7Y)Hz4Jd}xkqfy2vg4-&hVpT-B>b@!-c z?JyV5!_;A6lMzdaHE?WQU6xsJ%t47>N;?&i(Z?I*TU@qzpd2_QdPn}@Feaw{ASfa( zol^y4oi)4@8L!&qlNL98#$rt>H&euHJZCA{lk6^RdI{F)=0g~M(Sd5mit!Bfbw*1p zUG8w7B>J6-2p5m}RMgd%2I-%kv!|VD|6&vV4sldgb~CsrR4j#(_QwzMly+Ar>FKWb z%na}~KI=dK7N@mMJ@`D65&QIbCRhZAOsnNmPOHu<#LOsf9X$?d6Js|j>ny4~5vD#o zZyr}!NmqLpY}*-IR|Xo#1WPxB_g66gi)qNYW@hI1d)VdM0ABT|Gn6A z@q0`7pJ;lEWdZy(UxrSmVi{)X_>ABbVm}A-+FUp3RC%vLBX_NPani}jJFk7ZS zBHR*GF@6E7A3EOp$abf@9$kIhJT!t?)bAfiKg26iJG3GkM)6CWnnC^0A9S&S6d?!OZPL?1sO*v?K09bnmkfDG_V zi?`}fNA(DDk4a0eVr)qZ@nK34qBH7{P-wSnX%MKHT0R+5tfDXJON|I*L{0!?V7d22-nLIdwYA>D=rZt z&4Ry|Upc`qZSvjOoxlF;=KqxstRb#ZFRImEjVH1-`>LmnikZ!uihOG+n3i26u8Uuh zeWu`o5dP_0ndKsmFU_{jx2D4vvefOip~~7_r?$xcGb1#tt`8$sw~52uiCkT8_?ctI zJ-e))W&1?sMI)Q@v!TSxZ`qqv zrrm{ysxD+bwKwG*ttdg!GJf_cnFim9{|A~YD(C^$<13H{E+C9i?eJNCU#tAlrB&S$ zw!tg1tZM`{*W@4A9a-scR~OESEp~*05!^wrZf65EVY6PLRDPhUL43>L^B@pg!aV^h zK=Af?=m+UJ#H1)Z(?oQ6$mc)x$GQ$l6+uF)N}N#*J`F@RCe!4&-{oqyO2e$of4)un z@7MaD5)h%J03Mt0U;85K4OwX*fsYMt^FFjQbUivD*1{@U$5S0W(8Z33ZgOW2tk8J9 zFM^cVQmz$OIckjkg_iRSOieq|=|I~2K*|4}G6+lO+FPePvjydi{|&lz7-!2IE%!y@ zJT^3OY^GD=_ZDzZg7%{JbDW4$IYt?Fc^r8~X2x8xZ89-qx+f3Q%+(ckA)Uf&6x*8! zhn7qp@(ZAEhcN2Y-E%-XIaShi>GbJ-AR$uXVsRMWCnR=QtQa_2eoj2@2gZ&m=Mm?6 zA*fsr;rw%{+=>t8?{~Uyzu=!C{OVD6KCT(~mg*upoe4Uv<+|vpA{}2t0;Rx;UD}b} z6abOzaXYfnT}&m;EXHJ-6EHxk6WMI z#34qWxMKE=_79Ksr;GakL7@*k8wZMfTdcy|tL6F+1$u z>-lYK*hOW>=EMdLhsIjWT#=CkX}5OahXbNlWE)E7D*6{oDlW>0?Ayf*S7gYcE3&97 zvc7YGTbz#|U4MspS&mh7aML$oN_@#vU~*d9|9{g%qEA zc)2UGM|a*0+L<)eCAFS#h|+%?R6*cI?2o%Nrk-!vO@Z{57AkyeDvhf+|9Ly1fV(1d zjV#~jKQoq`iu;|vdSP@VR1+XT;?-;aKw_IBUWQ&4>E5BArt1Q}{bYXVXtofb8zNt| zjDP2KMYarI~eyTeD#FCvyuxT1*`yEegE*U;%-#E-r zZ1KY?H_y&F$h|{39}CPMHbNV{VnN<5tecHhcM%uE^N=mxhSPpWUrs zFGAWvNh*v}iyJ7`)Oq=P@jv!|m58lIX~>YE?S#A?9p?K#{s<_$)4BS5vUaJjE~-t+ z2e!rcPrmeIaQk0T?Gq{2SRFm^Xz<@jg%_zKq;iAyicIex_LZqfYhl^1KeM!kT>{fc zdcYTvCaciDbBRskEWtdz(+PD7es4!de>fPH3vKWSWk%n)WgmRBs63za=EKFBLE$~a z2&t!mS!PSZMT4xc#?gcVdP^Ozi%g-~;kzn)VG36Ad4hWE$v^w%v_d|uwS19N2F{*8 z2Tq@h{jc&sl8Pt${ZO-?$j`B0INyM;WO=`Squ7AK{=4xK2S;I6f$ah|J;=((s_pV4 z#H*Y$qBwbT2*C1O3E+D!{%>((monS&d>r>uob)QB=&bMC&eZ9L;7fL@of(9~(EjnY z+5Y~k|6p*`TNqW}vAgl^!XG8pOTsmF6kt^exgU0MROVy)P~ki`wenbs+Lv^b7et(7 zz9zrS-T&LP{BPqC@D=;-CuF$A8xto8Oo_SQU3flmZZ)s<7jR`P_iqo7@H>)1mekVU zvj&Js^xc9eRaPLcBv9n{EPhnSbV>oVSfEAf)Jmd>*H*s!@!&(o>WbA`1w{;7bd^_m z+u(~4ixfA_2l$sTrqX%bZ%@4OiBjHECkFvfO;3A<{18>>X>skviTFqz;e-Ao1_}po z3B%uS-6T8`u+~NcDhCL0yK6fR)&TLaFP*zKuZ0Ic4WrnR(J5% z0bgZ=$hm+)nFaFO$QoHj@*B&oz<``QIS#lZZwYUseP!-l>+;Ul4v5K3v^gSz~Oy|Wv#j`yM(T63l1$Uks*#6){qDEb>MW?Sw!{I0SMIkjZaU_v0Nt;CQH-?PuZI)@ z`KmJHw!oCC_(N;f`gvWoO98|(^9@)Dz^ul1qqr4NReMFw3Ph;c+yi?_2)Dn8a%=>) z)db1i0T%UAiXDze)IIgUfVvWS4sc&X;?63sJ#@{|6HwLQr3$pt!%qk0%sGNceL_fo zjU#fnP+QjrP?~RjWHB8+?DLaI!58}#clvX=G!a~)5flv`+Rlf_6t`uxHRmh_9JY%b z9r+>n(PwHSwlhoTa0W8hvbMH@pBX~De`t);E_0Je3l@hp^zgd5Eb=rkP92D`G&u0T z%+!>T(`Cl4zKrF;T6ICJ|71NgkK^wOc>fFDy~Yp;b~;!JF`a*&`U{n?UezjB@I;Q| zh&6PqWJCll;A)U7#hB*%>#|`ytzZQz7DCC|Q@+@$tP{=D)Q~U-CR9EreT?!=O~^Mc z>~YX%%$1=+F`Z=4^vd_eDwTW_cRe2g%ombRV1`pT`w7)&0XpX9GCZpF!_Ty#G;wgC zlv64xIaJ+pY~Z_h(K;*CPS-JRX#Cf7A7$yWFWr8;iK?Zh$w5~^N5mfQ_{)hHEoJO| zF71$^FA;Y(KZ0G$^^mNEJhR9m4`rvr4bfjB zwX_K85G}Lk|2SrZ83?RE!X@`Y!)1EmhF6M}~<)!h}ME zY1}TNC;nboW~oQDZ0lpgWNHolw~%q(Q|&LOaQX%cm58-atG_Mgtlpl^F?71a#Xo1J z3s>;y$UYHz>S`3`l^r+obgLbdvK4bSFw_URSzqyEyVMR7m;b1sR8fm-TkKW14dci1 zNWm|Q8lNZXa(>hYbNha6OD-!^>^*t>kixkfJ&1Lw$PWM8Zeq&VOV2^V;JsZI?^GQ@# zHRdB+2To7^!cc~@u`6iRhvpp|S+NF>NtPW-nGJH^qu9-k!knr6GiiYh82z-Xc^JKU zw6@H;h9cydY>Fl4RCdMlcD$>x3b$wD`8bb$vrsV1rpU+|@6-w9QGcxsQCCfpSmj*4 zDd0-o%>XHqVCXgy`|a53s>a%&!}GYfmk+z@ne>{|c}0Sf zNrM&j7P14|9xsD|Djj}uDdaC5%9|8MEXo$sb-=1VFF7CZ_)X1&^=Fys6Yj(59)Eh2 z0=PG0ETj7RA;C9((eL5VAsYV>l8U-*wmnnmSN~v%kwLZ@jNKs51d@$0%pA{{K%0{Qu*#*o-_=I&7$?|MalrdiVpW81JO>eO+!bGgz=H?@L*cm)&QM2x0Al z^1l`lOx!f3FL;)?(3*w(%gE@;xIb zL*4r?jZ^5glI+#hZSoPn$uFd*42=OOZwi8kySI@M_L2OQ^FPO;s`bl1n6Ahq$Dhg# zl!6uRY&@&nITRuq&y8Yb0Y=+drulyREKV~?q2zE)UUgNmb)5$56I42db}SK<7N(*R zMf4pQwfi-Fcg!cn06#M7l*p_tB>v$z=@WOvU;ban$N+0ue&B?f3Lt@WfE3WdU97rh zwGSP-97qBKwsLhe(HlCDp-0!s8!@D$4=GeGU>B`Pkd?!-wexGwVvwf**j>7yf|AqF zSc~&HleIE=m*2B^y7?(Qdmq2WavD#Km>Efg!7YAPx>-It$mcaTqIJzrtRbYaqFXRM z%?lxRgFH`!X^$H#i^6!fx_%Or9Q8s}PuIWl;|ZVpBk#DB{d0OI3_DbXI=2z<+CBQ~ z7YyjJdJc43U4vj&VvcM)3v)}%Z%%1L*Iz&Cs2LREEm*)#fqF6&T8GR{{viwwKvme^ z8t(ct^1!aw3(kvIFZ_= zYzj`!tXkswhRKB#lYpxtE8uP|1h?p2)~eKdWw}x5uyl=3IUh7%k&QgEMJjQG_Y#f5 zoX)Y#qMau&Tv1~+*X5ljlm+l#VwnUb0n|LGB?rO+Y?fbO*F_fUH79oH3-%&pES|=g zJ?Y$QYWXlD3wxs(FS4vI+aSEpY9QM-_S{Cx7ULor@A78|5VhtoDo0f<(OXI)6TRkzPsU=pGz(V)kPz>UL&7oENi#USx>Y}mC_7rQTUH4s?KrADWNmRRArM%cwm)T8G1kRvx93?XD^B7xSsKj z$v~~dZn|cTad=?v>Kc$sDtE1}tcthB26FX4 zsbu!SoE%lf0k!DuQLMbbifX)T+SoZDUln%~kxO&>2-9r^ZX zUT#{Qm6Szp#b$Xt-nJ>hjO(V>AL;`tJ<8tq6(|w#mpM}*1w`Bj65R!^;}-7&?SDQ3f(AL^KmX+bD85XAkvB&U#LB%^huR&S z&{A=>QqTaa?y{1l(Fo6bno>^!5#VS^)C41Q+#k)DRFtVqayD``Blq)AmY{Q;k);&*1jugdG}-t;pB~*h3gqirb8Q5v8dEMGG`eH3-S$ zxjM@GVbSWb)pDarR&=hWhC!`*$QY}wx{^uRNKofWi1&{{z^rv2xlpxsN^|bIJ*y%G zcaGz(Zf#ks8Mk}=qfS-&=|yT&0~khE1UA!JGHaX2G|KnBBI|ZKDNd*P_T1TVL`Wd1 zE_Qw=N3cW6QbIIp)X-Hq-uTQmxajG7tmXfk0cnS?4R~v->*3+YBK|V2@gM4HHAp^S z(Vhb?(_$!-5`KT);y5+0DMYNbc!_#7{ZQj_D-^krxvJmE7e2ECjU%j0w+q*iH(wJIU(eng$}R9*9A3y zE%|@&O7g)$vVJS%D|F{@X06EE(&3zct>v4WYQJFQ)RDV;gpLxo9LY1(83`op zSf-p$qU*Bl@rC;r;wYZ;u{7Y4$BRsA%;(!W5}| z>MGko4;BL7bGZR%7!~m5&xb7V2Pf3IA|Y*@^i67E znFdMX6`7xpPg$1ZqjVUBofh2EF`5YN4hd;=!@#uNzmP;Js%VF89$-@DEDk^lZK3!L zL=}iAS2EG?kh%I>Qr#L-%gO(7ntvm~NI1F4t;sSPh-HeU5iD!JeB3tJV z&-}jK9s}j+yqqM8nlCdH?TkZRTdO!kHU(t^P2@pkK_vpl_0u2-wRFjFQZg!}b?-tQ z9>e_oa5vu}!kWQleSNh=rl=;;!i@2oZ_18Wls&4;@3Afl@1#?yn#{wks% zEANVvp8`8JY;gS1Hbu_D!@f}Ek2B-6T82Vv)elk~ToRAF&S(uSo$zO;NtSj#zmB-4LImK!Joq=qegK3s0j}R(;5lo-`C6nqMsxn z&XO2y!;ZGA*;(<{q^?@$`!k;Yk9V%dzn_nZD|!VMXn4u-A*JZG)kDsD&QoIZp-p;o zcc{Kg)puqbw5v$F=-G(Wm8+Z2^%!OiTP+AmPC?Hzj%pd1ja-RoP?!Z=l(yd5=x>n2 z)9NoT5Y+JJyx%`{^-Ox|>4q$cqgt^LtBi==Mt^qytmO1bnNCgXV!blu zQbg2bOI?NSFiq=92D05o9Bf0=I`kD19^pv$6cL5|qOdkGT3ujvEIyGCvCAb~zobo~ z#w{|t`*IH@H35qZplj}G-SYPVQkozcnES$NitWcfI>h|^U}x^0Ibos*M2pI`gz z=_0d)&+}GIyP8FgwJTNlF7VND?nta9K19lrQ|23E3O#NVehRt;D%zLsz z(Q({m!H3YVFv4j&Rwa7idtPR0H{NP%Ar#W?{5IvtQ&i}gh%5RbxrJS_0KvZ9&eNsA z3yyuQgAWyewh`owi=TERI}9~6rXw;9?TU*G*DJ@JoP(CoP%Gv8lQX;?N$2&a;QiRu zmVprmTyGuS@c6Exc7kqCZJAqANuz8_ukY^qua8PoTBrH$U=0z&*|?Mmjt-FVWK2Rq zu1KC_nbuUf2Qz%@SlXo_r~QRux+5zI9@Is!MVeO>^C|{|$d*0z=$2b1@49W zSX=GT(IYHeBDz^skr`{2U^x6s4|u}Sktn?M8nd94$kBbEvFi5ognJ|()aVD%GDYXq z99{<&H_h@@3`3(13#(jJ)ylLdZRMcObGM-~>AQY$U?^%!*(0SR5qVhlYX&YzGOoN? z=xs-F#^Xp|o3W!Bm5R7;C^kfC>#Z4F$KAY@C7lc`=cG_0ve0uE@OW>NhW5urGqKy=a9AuK0b7h|<)B;A z2Q-s9@SIYh{cNO>}UXGmI*NU$@s+4#4FQ~JY@6zzY*JCyFT3}QmCoL8Fv4vi0 z^}BpM1Wr^$+ERgPcZtGDs3^>R>87v%_fH3-*p&&@v+t8DV|Bp%Ko*ed-FNIA_4zt6($U%G@aR%ap+PQKJ){SytX zgEDNwvDukYeeOEoJ6V3(x=%kfrPLMp`jp(4Oiu)o_^!x2g%5}seO-j%mZ?-h=Wc+? z)pb^kB!!ibj#!RX?dmzx(VHPfS4{Q&`gNwxYX!COhT9GZ1^?hP=?bExj4N8s(Dj@J z_>b6454zmB)g{@Jmx=6Q{5Qo5tF6M%ZF>h?O(jZCHi}WLEzV!4X`hZ=>cZya!^#yN%h#nw$Lx3umQ(fhilo$biMfR`*_xz9Y_8@yk zSfW}vN#?=urHp~rP+9285_4a>4DHDL-mcm57@@@-Qk#O0a4P6>aG2o}#@cnjecNX} zOKqok9M7745a-tkD93v94CmN7{oy#99UQG(uvuQQzf|k+(Z)EPqQ0jDlyHrTGKBcT z1m8FnPiX0Hr#!@?B?B$qX3Gb<{s;*RsX1-%CG(_f@sK`kHP=X$)<>tNn%D z7#Li4-6+ec!a&bQF2ZQf?K(4Afy>eRP>oM884npLyM1SUBAsfxvoC)C1V(*c4soyR zxV~#4-K+;5Qx@s9(RfYrJqVnIPYUiG4#hxL<)dL0b!T!09zEzf_!ic&hxgl9b^DR7 zM|778!99n`*zr^cn*$LnMzv_|jhkesT!#f^Cl z{ma;dVVH15`YsYQcL|*1Xi9M&@`ooAzoay@#=U(XXNVr#N!y%qt7Tg|@73xL4utsW zTjko&nbLcjbuV2TPqTeLCM5KV?AcuXgAj*SkG|D4%Vj+iRqsm(lgfpmM69XPR?Fq+ z^-umm|6iLtQ7w*BL=!`~li=5$*I8<%58UIpN-zrJJeS_*7G_3C%2}8AZ$LS^YeYP= zss^hHGYraO{sh+qo|Iu4Qm#i1GNZi;z6`B;Jc`Loy4ElEp120}xNYSd;OonDvZtM* zd)#oHFXY)J-3Wi15{=HXq4%reTi2N)ExT7SjA0HD7e%^ssiFRyg+s`V0Q_-D8BUx( z7O^P}uI5d)X?!i_9N+PXrTFlCu22hpcUi002|avKJjUMF|8}{#GMnYw@rS{ACiv4L zk)kE|bt0FwOhrb%=%qWn1@vyatlJXS->yVE`g4E%615e6{y@*PF7{0E{ok{ z*~OW?P%)RdWHC?75Sp3bA}(H3tbX<+FF1T!wEq4gKhAQxxfdVzbU6*JZ?G%U#I{N? zUEr;}u;v!75iRVcA;rt96fJKp%1rQge}PWBhD&J4ZvypRT=0_5ooLMZ@>`302M+Qb z-VJf;TAW&0LDfkSyQi;*k|5pE4%c{+(c&(xsmm_Nrl^^#YrRVrBDl3=DhJ)F(yx@R z??%Lmq>S49G`2qwcdGiD603uETIvK%Lm$$(YL{r??rUb~z7{D+-!<^oiWq@tZ0K}t z-962gR??Hhz>{Rkk1$ovbHj&0!NvKN*d6(n^ly3tF$kd&6OIj-MFSB=#jB%{C(yYu znuE&p5a3OrE;w&#Ok@nUNOK+gy|X3<8D26AEI(M4ps~@Ybu8FF&47tMGC#0Jqq^6U zP>Y8FcE4&xR`hBzndCI%blVI-$$Q<5DNU+qj^tu^F6ZVZr!bHKnHust`&Kd$!+7rjYAIh{nQHvhcQGbP; z3)QXt2#OUh!14_BK5x^k51M<`;WH_a-c8ferM~BC?N{ZaItvw}UWcRbtYI zB$k>3jjub`5gtpg+f$y^r0Xrc$*63H;*D_qQ5{yVptATdr`6R^`;hBE(zBBzuf4$4 z4Lc~CpziY6Tn?vjyHA$~-hE4_dhJ54N)W!jDX6G^kflAjTv~j*(BYf;nLJ-giWX7S z8`uMS8DAm*)#Nnep>N|9AN0~0{4Lu+7tboJXMqCAIc5-Ab}_!T9@1$MTM?0Qt-*66 z_=Y=CGZpfUWkv)li_)cjFwfM}9_9^DC|y*N_ZyTHu6ETYA^{TLEy1O(?a_XA0#~~H zFud#KH53BQ%(#@u;Bv}k#1Z8+^i`URy-zoDzng&#%)9L;y9`=IbbXU4weR|IZ|tq5 z5dk9B-KdzuGInsd+c%xMEIrZdsAnn^`wvL2(3v)EDLpM?a;)xfuf7!`tT?9~tapiE zQcog`7*NMkV`HBP2TO_mh;)1hr&H`TNG_D@S~gGmq@&3ky)u8Rt+LS9d)aP?SxEG| zq+djwMMPA$bTp{O1> znk(3623}_37oB%!ToF{_yL;;A$d+JUl|zghn!pP6m-!=GcXDb1eLEA*sQWspkDId= z#M>d00qv4TOYXiNL359);aE-|jKI1{b-n2U)|ny}{Vpd)w2|v#?L3XEEvh65u!iOo zT*GhslF#zpEd-O--^H0hN?kHUWnyLBV#o(W$V44VSo>wt8J35UF?g<9Ln|=hC?^!Y zd2c?*d8*mpuc$aR+ zia>~TPR)2(TlaGoJ4!!-?k!E-!8Rj3OY^~Oksjkh3=dCX3;cXM$J6DdsWX?v%7~`^ zLLs;LU-i;U-&ErV1h1haXM!44eMjZ_SxB{Jv;jQ#lw()P4 zO6%CkVjb7%S9BA)m6xT+4zI%6JR_He8ei;T6jap_$0zct2w0hYxxnQ#aiVSXtuCal z-~CKy;B2Mr{*W@gqRjc+#uXWQfs0TG%sO7DLuB4`S|@=)xXSPSt~(L6qebf$N%N(~ zFZw5>}y-D-*@K?eWmdz(r7GC4D5JFl+yiRrN`s-OdimSXiR(pz#Ao7jS|B6CO zTh7mY?Mp4Zbe)!(3hQ2QMP~Xck^ZbD>zGY+p6~PT`XWI+*GtFDGjIG-a}L2TU;87x zP_gmQKje~xPt~oKVe@gPv#a;eqA4EuEoO4YSfv@_thGLBdSCP0Imqsl!%Tnw&`Oq3 zNBs4#K3yNmk#B65m`#Gsbb*^q`y8NV#ySt*!K>i(IK!x!_zCqNv1<|3W0ge{qukEy)Q} zZ-y7C<9H5)m>a@}vYdA^l7`@(XBfl0t;-ujn&qR;j=8kUz=?r${5G=4m6*LM9S!r) zu#3|kcy2v#FpKGX?>C{(`)t9yG}Q2J`;4w`DWt=#jSIdp@v;%#Vj0n2JAX+X(1s|i zHRFcq65+Fz_%kC1LMEzdT4`^{#^Y@H&2ce(rZ%Ek5lMWlS?0E%|oBQsZlfN%563@oult(qr{0 zKS^smoW5?OzCFnkdoM>I_VytsOvV7$zdRto#lXNs8 z2yAAWS;07%KptHv^@wm1+hKEk#5Jq!owyU!blFE$3B6vvb|*Vi>tzqYmkD*Y9&?PLy4D zT~v;?defi}k>2g4+rCBw^`YBSBiB6bTle&H9*xgD=bVPjpEm*;XbIFOHFHdXgVZ|unBP&;k+2Yc@bhTTp7nqHLM zKX{L^5klDujx<@`SW2f~Is!Tsa#ug|nk_-r;X*O@bI#%((%a0ENE-CtgB}ae8;{k`7YXsK{?Qhz94k?g@Euv zR0(QBO|T}F2GNDK(V)^rX;6~S?Ag@Fb{!ENz-?+A@oMaSfOZ;kK?G!&OLXG6Jcz{6so9wjJ)=! zOsq+YGiKjHl??_vBe5LUgYUN*+3P2D)Ci<1E4q!Y;i9+K+6yAe`(GyeOwKlLMt8Uz zE#qu5=mFC+73ky(n=}kw&Ek|<`DP-Uuq_lm<8vb zqvFw^zR=L-(9ka-c(?x#sS9Q#&f}Bf3NFn^ex>n2BjMYpkf^$j_<{HkofzH1V1iZ- zC@R;hLpGNIrm#Hd`1RU7E{S2mLBxT$^8ldV;1W-`a9nZxqA+SjPrpt!ZVR*BR#&_o ze~hTL?J*Ul^WqD?v4l{+-Q)ukV`EAZMnT53ZXNd`P~Q_8k82|GwF$ zkQ6Horvafdv|OC|X;utDT@jULAwOuyoo}NTUaF)-9nBe$8W{!}miG%aIc}p3U?@Cs zwwvj%&6_@hE0&${-#`?{ZA&xn66$%zYg;u;MTg7-1qP=)=>U=M5oBz~8EMAure+%k zzvCSOJ;d`z#C3R)J0O3htw8C%gz+}3KDBA5R-Ul*Ci@3|+Y(h+Ciku3aH{;}cFgov zIWVldKW`+Se&ymg=wMt*t-hm>AU1Q^8a>@?1NXHdyGTe`!W-EAL%N)kGQA@uG>P=* z{EBBe8rLASpPe6rBn)kAmgwSLj<3V3O}kWJK03ZDPcugTrb<=Obsk1YEOBj$8XC`) zz$<5}JY1=YeY`H5b}-Pt!TkNQDbb)BRJ;S4Sy4KEHzo`7vzA;kPDp*ufhh~(q{cCR zQL<2?;)X^lOf1nY$obz^9%rbYUX>|DxC}QFjK3rmjzaG; z_4&U;gbz|7B37v+r^TZr4_FX$8qtUVC6aB_D9+A+3CZ4`@!^46K%(&rB*I)o zPzR|OoX`7ZX#STVFl!~R#=vesl#kz5rJzp~FDq*_n8j@=F+4YHWLItWBmBC>xUROC zEUP+g#5#aF)y0&Kef|y@#7xEC@^`fajJ(x{n4fy%UsRr)qWXsn{o~rFHTsS7)nW2w zNWVsxqt{Y7=gyz!v&fO;rul;o>S7KLhyOeRksli^bM&~Vvz8})E3FtUEUcm)h?pa^ z*()gAJIKQjTS4f+&<^e70t9Rus?l#x-UjhJCv-VHiVOS#oBUX&qElP$yBr6dX7LM+ zxTkIBIQ6FsJz7wuJ>7_y7^uy&NNgKGP9r1gdcpg8eBpQdnh)(vA(Wjl?8biaN{D*J zFyj|mb{d4IV5wCmc!S9|1)mv>a9hE$2W^A)^_%%~D!(T01uicAyRFu!N>{@O&Qxyx zGK$*2ql}c$EOqFZ6-0FJ=9u_Gg=v7H35Rzw%dnr zKS<_0hX%^d-;t?2RVjxFa)!J!y==rE`(8T{og($D9dtmbKfj=Mzu5NABZboKRjO!3 z?3Tp3x&O!$0E@nuaNFm3)4}pa%;u!ezb@=`vH*Z0for(fKCLI;YgJUxa0$ zg42kU-^HlVYx*wBfOm)O!Mko`o0IFQ*k9^LwPdZ1N+xkG#Yz^L+y30n40@Nucj@@#{%b!9 z@wH!k%;0R?-vrs@?>lpz(QbJab-9P7h*Z;qd|0n+BDPj8F5IyXD zW7PH)+QAUIK7R79(u{{#ovHN|Z#8#a#=A>&&oGD5Wp3Y(11?C$MsChfc_0lcGKp1X z-%8}kpZrWaKIzgW#vfwc{H+p!s5#8eH z?oS?arMT7%$our4S0QTHl0?&Ekv@caXr~XAwG$yD zN)4Z3Jk2s3e22v=IsB5Jv^(A@9!@;Z^*P`(t-a~RST(L#8uS`i`QmUVtvWD%*FlI} zKJXbD7Lf=M$t@%OBceHy;#gp^zI%DKtL&Dwl*JT5dI{;udxmszn}7Kw53@A zFXDcj@sP-zQB-F8CQ9DuGCc($SaUfvawbKIFxz_%D4Po)pQSG%%qQ%?tycBpb94|Y zntgu0>!~oU3VK#@ zT8G>OZK?1$vho*(Bv04aVb;V#6(EYVd_$Jqvo%X`kIv#k#Mrw==mg z-A!^xWk-(Lo>NW*o>{ny@6D#yx7U1m1*FQ7a`3Vd?Fi9pp1PPP{#$-@Vtiyu2PA-g zNnaZGg9jBI1A47Z)=u1hfi-fO1Ic7(5GQG*yLg#4(^R*)(AxYopDZaqa(nsq$oLMK z37D$-ZX=6FEE`8iQ7R3?2W&P27W`&r4!Uwyv-@R+lX^rf1JNrsHK3O)s(%95?=L?i z$djIZnYi0+>PH}*N=sdY^-6!rLa#-*reEr%W4jO!q+Mw0n?g}9X*YSMDF>@i2Ig$M zlHM@T_7jFj7bZC7Tq9vECRH-9kc)Jl4f7Z$^>rd$-SwK>P|{~h_)Rn&f1cZV0y0); ze?~EJHKcvgCg(QOIXWm_(|UbZ+N8DwZyR$+JoEfD7oiu;1D(GYH!d9BbW2$&zNIG` zU#AcDY|r+k9663BdV6d+5Cjh|1RvbrL?isEAzwFX)Ar| zjP0~9UAUweL-<{==E`@tb1_v(fEBTSd>=eE|3hZ8Zk9q|ljifL4a$Nhoyj2IWj(Tu z7Af)NYJ2yGwrj>YCY}WHO=NPdpZ3gPi9#AML#N}DC*Q1^I8$0mADMP$P=A=wvaH%t zurP5qyjH@?M&TZ`H^Gr&rd<5BxU53C4^>Z5Tqf&YqxL(yQ`zC%1;RjU8Z~hv5-rig zQPTYZ{gA0V{5xwW-0yhl@wnP2d{Eoum^;_pI8rY&r9XPJNWM9y(N$p7{U-Y-HF zpHRq8j414{s3LC9SnKMR^j)DA4N%D(OqoC1uAUR{y&;d51-t76Aju>-GMsgP+M*6yd~}%=xCRl-inQj;_;{EF}4Q^92U;Fp-vp9A09?La?K^A4UP7 zT=3QEVu-he{H8D1$Fp@fEe zP)h1HM$cDYPtBbf@eaT?&MnJvQ&?{ zp;lkHP>w|^>)0T*+h#qcZMw$$!u}vPElQt})KwV#YxY};Gkzg4s|Yv$QQcywdW%XN zDC>KvywRl5v?9065LGKdcukpvw(1>HN?i9M9H|G_t*bHNXh$Z|14~2Z!-3B@wFECt z(vC>8SX!$0LeH;ttec|qc?0=5Q&3&C=6L7tO!jY(5*!QbkBwwOAM+B+A|KYay*Z*Am zzmBi|`42qt5s($hjpS^2Gj?=dOs8x5cSG4JJ7><%jj#VaQg+I32Qx~2)@Qu$G+NKS z?BaG?%3b$yJl^H}_6^vd+ zxuSa|gO+)oi^DRm8JBNn?kb6T=~f4wYG6)YpSm%G0qmT-B2Xk2Uqag3|AFV>??_v= zgAX$uo<`CpF<=FR{R&E|wBT71CXY_5M`!7?yOVPhhEhGV% zQqlR)ekDznzP|6??$ur{BLV}NX<-VrbE%czb}cHQd6pmUT(<4g% zY=~r@n=ggB>pr^2!026yw!^SPs^@{~sd{|oQoGG1FNE>;K z(LC*}xk;o!g3aU;eb<6NE@X2sd&60bQUw?AA0zq3b;hu(lK}OJ_Rgkuv=k3o zLI`hv8ll>F=1p?Hhvb-~mCCP_;EM32>G5NHj4hN%R`L=(VLrYd`JtFJ*DW;U=kZz|l^h$jtl%nY9!mgy4;(kW0vv+z zusVrqhC)I2`c>P|KJq_wNz;@sN4KszLjxh_N!-xX@iY(4WuwrDjufZ9W|S>yx_Y3^ zRe}gac>UzOx5;;7HKOR}>XiJPEm2I>GKEkbm4lS*lKd(f-uldB{jFn@_a*b%i2HjJ zFc15Hq4?;D0k!^2aB&7F_Z_f#Lbq>l8-p8r5%v4BE1tj+ep-28m0 zEz9_s%{8&rw&D9_#J{)Bf;LsG(F}FW|2!h2ab`WCcaF($rt5Y_JX1-AjY&Z>jRVSs z&g)zaqH&9VtVA!ijdI*^pS+O4;&N&l7Q#RRyLeGpodYl!d>R*R@H^eL-(Byq7n1~b zNQsedL{%*T5~*(&syR4=mi>eI3_>^&w40mzs~s}qdV?te9M&fUG4JM<8L)A_J+s=q zYonIRTaV3{Wt9o11GW4Uw-hYTd~_Ibr={dray;)+{7CJk+73}`tToXKqMKI(d3NSk zvfXdnHQ!f@`{u$x`{>wl zsCz2iOZ2!qTD;uoJnQoCL*+&#VX%3(6=6;ff=eNKTnWGK-)I~I+rJbTECJ@-pA%e0 zu~;w-36Lx;0UR~G{?|CL=YIk>CD;q-p^K1qd0nX6uqnJZ|cU{B)=QU-D@ z>2e#o9j&#pv|bs^@+zBlDrKpXD(_Z)NH7nmF}e|_#D*Rf>)WX8w*S6uc({GK?12E$ z3={HGDGIS|-h4SQ-!Wpm3WGvYyw!_0FpxHUVRNewsa z&O3E?q1hT5Ca(mYN$Ep35kmw05&t~87OBwfcg3p4{`1HpdGd;>L}lUWw{u7JGr9K9 z`8*bvXJEgL`}-(Yi!X-*e-@nLKThj}O3~g->Y6gh-Snx7@5E3BY}h2+hDa0~8+JR7 zLJ-l*JM}^{gb$zqwSnE8E#!gY^zH8V?akQ2SZmY-<_f94DG_Jksg226FZ`LT&a*O_ zbVTCajN8kLoEY7mXWg&)YPCwW>pgpLyjg!`^xRO&aL!dV02w`ODhb#YD~21W|7QF1 zNQ3?mOiGE3!kxIcr7;I!Q(x?~R{=R0S4X$tLGxL?5p^TB%5A13q+}hPO!wf`*C9PS zx69t`?r*>2@&L6fmp{qg{AV?MHCR^Ff#3fsI~dq>?k3VSo%ah>%%fiRW|wv7Vcl-v z(s;XnCi-GsVI#emqU7{J!Xb&lA}|ns`oN=(@}EpYCa^bgULN)eUg>w2&3Z-6p_~U< zPalxs=^l6s-}I1oZ9L`*dKWMKB*Hc*@0KJUB(XNUGXO+~b|4oE;G-r+r}T>)hIvV?K_My1bqr-jP7q}7q6e8&XYKavqsb+d5UrO<{<^5`sD zI}0nupwgzV9%!Ja#ccfstglV;t8ydkgXj+r6x-Knkx$cp2>L4A_2e-k1z7?bYmh+} zA(fX|Qp^fXSY{B1^~AA$6(@c@_wp88UYzQOi zZH)~%S4sCCUPM}&l5?L-{SxEv6p#x00Uh%3SYD|WTi7%DxMpBPh_Q&N3vfYK0@R6b z{^Uu-EBP09lJFkq)qhxD@BexMG$x88P4u4n@hS26=I^Wq#E^?{wR z%i_t3u09I2t;6#;D)GzUPscwVsoqKlpR)dXn-Z-@5^+UL_6x(X@VC?T(Zoj9WclKR zD89huM*eA48U(6j} z2lBuP%U;Pp%^LGjKeWB&UEEnU{R_Fym)>Oh^=z?_eslf}Ry!Easr=SR>T2gM|F;Qn zn;I}Ag1ZV2t~&B0>xXe{i0F?XKVH+HuyT`_J_K4i(b3Xcs7G9IT!J5>u~Hsx_sJfW z4V-JyB5}pK3K@eJK?%7J$li`TvC&)E5)l3o zjtLpNs)AY6PSEnKViJuskXxW`b5U8L1N;nylJhqHv3*C=jSrXB*HZXolbvA`cVc95 zeU|6x*b$-uf`r-rh5bKPnF|5tlk1SNUEnO5%>j6Q6iJ4H&4y{&7uT)ahg0sqAcFKQ z75g(x1wCvV^FkIL%SQWuT^`R#u`c5`Qr#|)LH?;@%4Tucmd|e_S~>? zv+7p0akgQ@0i>k-#nSkxB}}#qCfE0DtdVcoiij$9qH@|V_LL0zE+tEyQ3FZObaeRe zMp8vRkS({J%7TbOD|p<*wyTt7jb7<%yu8(DmggJ#IsR-Js~w^(KGM)0IfhFk=J(GYJPY_ znRdF~qxC$FEqM=B<-ks3@E+;Fc^CGMGD+iM9VgVD_V1WI73DWWF66W>&D5VZVo3chN1KV`rRdvqVI(d^3Um`|R z(;Q_a3v+DzgQ<#WhVkgKC))Tsh{jaiN~Zzo(X=paq-J$|!Q5MLBOoF&2C!aeM+mK1(Y#7P-GQFw*pmN8y>$th;(z>NFS+E%8TK)TAN2N#(1M zar;dwh1PJ5KL$FOrtS0M-o_9a>XcEkpZf^U%bI?5xu<_1pE1e#- zzDeR4JXceSw2Yn~DGSKU&0~>B)%gP#$p_-X8;-KKS|tC1A&=3RndZ}?Y=y4pg!|kPi$!|i(TKI9 zl4SA7{GOyA0*_LTX|66>H-^r+&9SIqsETSejTN)Cc=KdRQ-4>durm#q*^e*0*pYT0OB1QTc`KcH|)WpXast~uM$G0aU$+(XcN z@-u>Q&YRckEn4YChn(;wwBqcRY{NBRja-{~&`m^F(Vh>g(Ffvg$H81v$oGw2<6=Bb zdZ?ed%d7hs<5YT;)>71BnDq{-pz@9v3vTrxdCGDojT z@-ErR$Yyped6|cpJ^ROTX@YmWe+KUtn97A>%O5j3gi!SPT+o`Vl!1&KEc1<{CvulTtx6yw|n0x zHNMa+N_Q<6IUWD$imq?o?Vm@tVyg!#9)lehUuc!-&OPg0@n=#|$+$i$-7Of~AchNI zvIf+1W2{f4n~RpuA=qYGmvPkMt2khHm*+vsWwWpKFY|n3_b;@DxT`Fwheg-8(}G*b zs1Hh#xHNoqioc?w<-d}}i>RuFk-=$hDI>Eld?Mmr{%$~NdSiw2BA;;r3u1~`U-;E4 zY2micy0xt%x3xyK{8o?&>1<>BTau(6wK?4ZIFJXts?0SsonTixwkD;@% zqJ0zfFTARD8ME`ro7AYni;*7_iTw5_nPXd`7!lE?j><_D%@67ZxM{4a5?L^R*}yGJ zc_=}W$?;t9N0Og5I^M{X>A!vq8VmGD{}vLhoL=$nF>i2MmxWNPw%8Mmu25SV!_a5? z!EDdI_*w2D?7*eSlng9t#LQzJYlpB4E0=_5EW0-Oj2OqccvfqiYy$DEgXK!5^)2Hr z27Mml@7hVm`Vq4SjjVs$RkaP?;x`Z!(Zh@*ORQTYEFZ(;$r&qy<9)iJTn!41FJOv>mcB zeu|pj1v7k~)t>3OrdMY+OIMYpkUDZ(j7WwU znAO&xkp-y&Z*Knjc;p1LN;USSniqjpO&#qSxf=KMI*UE;7VP1&Ow(hz>fd9Zi$>cy zz%3_}M{^W3v-q*mkRcHdBf5kK(`q&O{{D(qjkGC?Rgg2t3Hiy(>tDgh#IHC5^d6#^`9Pg;9n3*GR*GJ=gD3 z<`WJ@Vo>j3P~BjW`Wm$cSH}nU+7=Fr1btQ7E<{v18Ed!|%GF3&m!^;IO?iE4{URzC zS>fdx#paT!sqdk=U_V_p{1%fLJNIFkG*NF)d0(BeUk|X<9kH~FL5Q5U&sD+&4UduL zr5lJBn&uU^_U^=1i_sXDL!2CU$7fRwTa-~2w^C+SVye6TZWnDa%8r6mySs1ZWnmK4 zI^jXaMz>#fz=>%-8V#ogO3YE>lk&7^$LH?YbXslglCJ`2Am4n9k7}p`fwuQPcY^2h zS>X1W;OLX)co!0mqq(LR4;l->xX8&YM!Y2AwHTV-ss_sQX-8mN1Y?vfqP}r&sc-l~gm*~4tdM!ZM!&%yY7mjCA|m7`XaVH%xyHa6bzi3Z)n5+wiCjOw%Y1D0FjmqOV8sb%e+Fy$LU57W_d zb|ukMkd>St%&SPEoYOE=dF#zyu5B`vi)NAQi+u87f~#V}9z1Vo6;DKE3+iWsD#sBL zdOr_+Hks7^7>5G8Y55CS$QQ>E04NG*kn@&vcZSMu+a67@M;RNdCHk?u2bPP^z-Hi} zM6I9728mUe==f44yqyJuhW$oHxp-X)+i|nrMQ>bbV=xml6*hGyFK@j3o?f4LwNu@w zF;Y%lAW3Tk^yFx$Ut$-0XTh|2^OqaxTp0wBXgMqUlF^s7i*aVC4=yG|Jj-u`RHu>i z2v-tptBP*345G|$E>6<)4(5tAT%F4_-1;(zG?$2Yj_R-!2;!+Wl^(?A^&k8qzkBYF1~PD$hDC}e zA5v?QEQDz=bV$l$1<0Mxt|Ffu3N!#jJKdxzMPXva}hhm=QcAQ1nOV3aR(U$ZVd!`3;gv*r_vaa@_ zJ*z-U7RWm1o(0K3Fyz@b!l5;-h?DQ7{eXo0?(q4DQoO8P(uZnLKCG%mRhJLCj8-4@ zsO{87a4S*YZj69W}xrs9^4(u%3QtHy-GvBK^$uqZCDC3zMW zdgd+&uPQ8aV5ipjz|Nebsa=o%TyVIWAiUikfm~E)-ymkUhHdCoXr-D#M3?KVvMc(X>6q#hwIoDSUTu(4bDIn&1a*y; zoy^r%YUq)ZSg;$RH@FRp-VJEd?Y|!lNou_V_e;F^3*V}~;qSSdV#__Fq6cn?O7Nm9 z9&%1w@62fqtP?4#O+DnE)7<(oiQx+W$Hz%bkCM9ZK$L1W47&T~oP+WtZlfl=p&=;N zpbYrdn6KUqm4220VM8_Z=@!~sg%J_fehH#6KB?tPIv$_86SI7# zNq#xy^Z;@6@{ z(9q?YKKQ(=p!kjQNms&qlg1a~*MM=NeKvC^J3=Kp5W;i3RHS%I^2MUQz9iI0&e$P2 zU?g{)MiO&QVJfF4<<3JIr)b3@=_!e*`4FQAa#~6s``xmuJHHpjYq}s03_>3HDs8xUO) zc(`}z=>gX1*pFL z5;oI*{gsVI*~VVwTS!Z>ldiVbXdm|*OMQauZ2!&>0v$~q2CshA*o|ByN+d3}bA+c5 zmwP6ub!Qice5*Mov?Tw}zLUc1d&yU68!52j#5;Woi3=f*N%&nI$aQQn^n7s4QdT8; zG(FVg6{CzTK-vYFP6`zwgSTCl3CT4HCipFK!`j!TH%Ua~$?jDqmoVYwVYs)p&RYp6 zgHo*!&JpStF-+xX2{mM@5!0J3Q-`H9C2~sy$=t`&^USI_jxLtfCpx@GoE*?`h!2Kw zwj?YO^ahPevs^1l-{FR<+Zp4;OgZDWQ}t{+h(&Nxc{JoLg566FdX)mJC4{$>8Xy-)K{coRSt z2yVUUrOQ5;AXM+GOidHSC3<=PHPr%bt8@WVi+jU!4E)gi*VlfHRBF*%^V(9?E93zi zJ0C>p1?@wNUwWxn?Dd1>%e@QnBzNENc7-fD))=zv2+lX7pc*a;U|d(7kG$WknoRyQ z=0_@Y1@L6fFF2~UTwp5(6ebkOC51>nsBHwivZm_Rq`e;YgOH}$6m4j~V ztc+WgG` zOCa_er|0@R!oYaWIfy8;P>-64vaiA)UG^*0^wtQoaqd2DH*Yzvtk0-@V;iT8@^%%% zl1N6AZH`h9LKO^#=h$1(L&ylQzCb1I=EFUuX)?cT54nhhKW+G^@d zClF__prDOU^Lu~Wmjsf`y2n%P`Wd{mC(5E4vO&IeS52%5`gqoeZ`k^!VS*FgUz_zp z3>>*xF_vAoXu38uDf@Vg_Fd60caz_XkXeHEs88?+6{!~iO(2M^o|a;ro$9Stx)o>-Xdzdj7E z={K{SU3{9FJX~ZCZ=q{5HVUD!Y-VG&@dPk|P!gjk*yBk~=X@cS%;4pzAM<%pRR!_h zf-SLUF*II+h?J{8>lITpnp8MZmrBfG{z}~ITV|0{PR-eD#Z^=VKBxS#r{Q(BT*P3b zoifHUu|oe)-lYVF8HQe@?IfrRLaCa~0o9F1q2%B7gmff+`qF)iJM$~CBoE{#j-!-sMdxl7%7;XOyFEmh~p+Ce2uq9HqkWt(jlS5kqq|7FP5R|^5l(E z%YN;>=bJ=Eg`=86s52UR=81ATLZ9}!kRMif?Zw5{8Y~SZyTJZ( zSI!gkGh|5U^iNQTBmXhaVruzw7Z;bgBsBDsTiBY`Upe^0K9Dm+t-me7MUS*SC-Am( zwF(16P$X(Ld!30PSrdc$t!%uzvRKK=Rw>ck@lzC76IJ~@)d8n3uFoy8;wn_l!@mpO zTmY|=p+PHn{<#gO=^!?{EjxaGNwb;NRGG&!lrq;WOpkWor$e(|6ctD()-g@#hd4f;w4lnoN&$~nOHtMK?yKqsAg&XFw1qZzdE=BL!7L=09 z2fW~7wLNy_TuX>ox|hcg1~|Q7M)wYLnHcmYkhyKmYX#5K#7=JaV{3M4G@vQn>3L~j zZXUTYs)-w8#|E>LbIb1|XLvph=+0ZO%&MwBD4&r>OmiPEqCUn#z_}^39Lq03x9AUp}p9Ig6Y3 zlEwwjj!9>Q43_9{w%EzAc~IBxT32Z6R8PN5Ra!nl1n4LQ>|fX?1__JWzp?87 zgq%7{Ocd1BOotWeh3N4v_E%-a4)QUR+jXi6e-S9V7>}h%qoZ~UDpMe5ul&xa+!Wn3 zv+V2wx)Sdv*+Gzw+cOeByn1E4ND?IK573 zJ#>h$Dzo4nK%iS9-l{i#a7how6sR^WaP?~$7ZTeFq&^R0rq=xSZs&%k^=>!+S;#A< z0PTLvsZne}JWb=0yi8lUMkc{Tv&ad=8{eRL&{=c|^_k9;Zgr|=) z7uqxTfxOO_w85a!K9x1K8{boPCd&~!tzArlT)b|DZq<9NQObR?kAKoTMs^NcJKWRA zOn-d!Zy)*JC;vb6&HnQg|Lex|5XZaEeALq|5X5fZ+oL$)ukSyk*2`_5!>f`MN zRa%8eBMN+o0GwQBEyg)&;tBAsT#%Ng?xZ!zxE3S3Dr^uTtfbC(nU10$cva;2v?aHX zSE-4y8O919|EMz1hp?U~OH9y?hs2-l9WfQjfR`PJV~qg%AH;7mw1Q>xMpK7G{e3%^ zSMz2mw)of-W#*ol*3qDS1&B%aB!HTA>llx=xo zH3bq1IJ^fRl;(T^1KUBVj@5*7GO=eLwRqwxo!n9DGI^!(b8$div`Pp_H1JUKTTKE{ zESk9^jggrU#H<8WMkxXrtvonfz;J6e&Q7&sn=lTJ3Q*8q`UJ9fz10gCd}qLk2mHQ^ z{;{bGG<+9(t~$B=5z;WM1>AlRDaen4;#K-7P~gJ2=@&0+A14x zI*{7Ih(idpcC6qSS7&sR;oxKgHC-8hm@ENW-QqaJ)v?`FII@pcUM`SRwEj3nSTwOa zN0i9tbD@(rml>N2&X}z9ezj|$^A1063csNK=NU<-Nw-VFUaC24$xlh@dsns}hM zeY&an?1smOvYcslK+2%AIT}MP9(-Z1XGS}CK2DUB(;Y!1Gu$JyBQt^}8Ras(bu3|b z0yO^mSnV~G^9NA`mE==#vo|H9@T(vR9!e;e3BWl@NisnZ5dk0e*Ptg%h{cF$9?qX; zJqNJxZ0%FkuO0kLKuxi)k;{21y_&<;xa6vV_80%VHY!J}AMqCJvSNyz4IHvAi z;!d$ntC({k@jfta{EWX?bvs?Sdx5o5x=ATeNM=Ii8MBGi=d~1{4Q`^-RQA1~P6JhT zn{9cAcXhd0-c&x$GR5aFvI=q@FW5v1wessGmbdA z0P(CbP9Wes%l(|A$d|mbiE|yupEZ3}lg=5v`$fEr;Wp7%CR%5zf^W=Zsq|V3 zCgJ@=hVCA^D=Y>c>M`L2FE~4P)^Ku|Z)-O@k`5O`{6!C|yplJE{dWGGVN%8ynqc5c zL(%S7noV1T2dJ4d*kPN4MUUUtk~Pr)FkEz3)e&Q zm1@oe^0T~AD&MU%g@RKf#PCXxaC6l;dzg9O_cvbal&xMv+qeZI({*a8x!F@$%!N5p z8kBb*rP3aI?X+sKli;V+@~J%S=+6mgdU9W*_^PeHWZ}TT_VRTt791g;!!37`@@PO^ zr1NDjO;u6M!_lMyP`}8G$JqmzLg#05?YEg6fbB(1Wc1;%M3PmK7YkR7vwZ}A5A+>| zqSU=ciGM9#0k@Flx5uL9xJrX%h*;}->uw{x6CO43x}&Z=qExTMJ<~LLU;TM6cli#9nzq<@duoxgr)^$z!(Lml50dDrkU8FkDsbg2 z*ye}iS)$qWt9Gi;ZaipPJ!~BaCZIIMOJk>3Ihnvgpugk?I-|uc?SBY!8I-8jgV$YAKr&Kl*n+xOBzBn!x5bY3} zOh>__$e+$X$EVjg;ntMy>$I9Ld;yzOUV!jufBHiOEFw!mi6y{Q_|gE zziP0If79+0Z!4{xgD*#CO?s_tTODFr=FZIaWQLfg9X7~WBmJs>EqX?+AbcWZQvh5) zp2Z+q{+2*JtfBr|e$pgp#b+F6-GS=?Am=0bui`Eg0*{rfjrb3jB5EKeEB$A*svO{n zLlsvNe+u_qef>cNs2|%iAf<72?~X%a^XGQk)zBx06@YIg0rra6+3_^0p!H?jdG51e zojW60rr5~l_bY5|r$yo(4_~o#f{;!>PpdiCdr5-!)sNtN*IJrE1lMs(GX>K40MA9t z)xoI{tO3biZK;;gg=j4(+oTb~nckJe2GzBDovFSZhvIhtC#}SQhQ!+!!`9g%_BkE1FL=Cv`=>`;x)rbtvJ-Z4l8*)FKTlA(nOZ;8asvB;w z&;D6Z$k{@%nS+OX%Ux(AG{LbBB!LAt;zF!6sutBhGB@fQEB8 zu-E0Fs?a_4CV(9l`6+u#{{?5!KIO&xq&@9{*_iQD(X`m;G4bZGE($~$O%2_<^3z2H z<^kS|J(}W~i`P_fEFJhIsS9F5nO2Lla*Ui_HV`U0`$a9npZ#y6@KeytF#4!sB(Sll z>1D!`%W@b7b^|CkSP3rjejHXT8tNqW)eZ~$0csm&kB2&nfvOFgCk}-s*PP6!}-1S55P|hBf-b>n|62_X_L8bo^yi^YBK@5q9krQ;B)~` zwme?4A1e+|Y)~7%n*R+1Z=sm9rtCh9M60HLi`V&Z74U8$4OgQiRmjhD>L-+6PhHaJ z_PZ-EvP}g9Y};WQD?9J7&(?x#c*?W79CJcWWo@zbFYB7teII8}HY8xzqh>)wO)VMB zR(($j{sT;_`iBXqYGVCgrFyckHE?)$`2PT#09OA;0I~&5`sp`+Z{2PD??&s5+|@Q> z?kBo3%u^zfQ5scMZPO5N$yTu+ass8vTSk+R-&1D$iR*8ukCE~q{$-iUxJ}Bp<~yny zm2HZ|qrfNQ*hHhTyoZkD*^TttHFzvGoN{A~y4qQkaAPehQjyZhAl@S&>`}x&5~qvi zP;0Tb@;h2vBS* zp(sHbQpxj5fOS#0yx0^NbSTEQB23thrY9*44 zzgr}c)}tbSan%{0B^VB#-#N75ZQ0th1*&Fc+&cYXg4E^bPji#lEpj1V$eCd(p1MzO zV6-=0>YHjZT2ZaTQ>#)7-iA#Ji%pM)<8boru~!xb6G4|-V)-^=dI|1>NJohM*`4KXcFbfTnQ1%G{@sm9Fjs;9``Db#I&-W(*pOazuLX%%m zT$5n<4hMBoxJ=gbtdpwv-W>H4)88Jzwgvq2ZC?@iHKeJrSNt4hMfykwBMq~p`S1b` z^%nHl78>73NyKj_!?ly5{H1?6Q{p>9)XREUu1$=oZ7yu)A-qkcX1;3o?I8d@S+-MV z-n`OwIqvakXk!b>tSPCoeB&+xNkW=_;*9kQ~C4Gc8fb`ch*(6q_v{34BE z-&;qgv75AtvpCPS?#SDAnnGOIk}``pSvxd&4Eh+BQi>~5T4kq6*B5^oZm`Ny(v3`@ zklFS|9q!VXjbfIY-yNH?^DJ|GYq>P_oFrh4W%q?Pd?Tr^RZFod*C(^QDYmMT+}C9{ zC%G#_%Ss#OLAn)ZpQnhIIBa(JB2)`p$~Ngm{{V%{Pho|%9V?SeiKORON3u4O*~jYX z@K<|2U7x)H*2QUEb_@^Pk~PeaY6)N>>0SrUtxc@L3*`hb)A{(b>jlX?Fy z0k*TL|0@9qlw_L+L2FvWmD~YoVbUS;N# zhZ=RK5|y^&YD&D4YZT-y%UqWEe(^c^)iz%3nG`u0|Ck9>iZ=C delta 35323 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�_Hg|9fKnQ-|P5?szsT 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 From f4ef47c2d271ef4e18387cf1bd3fdb35663c7f5e Mon Sep 17 00:00:00 2001 From: Paul Woitaschek Date: Thu, 2 Feb 2017 15:21:38 +0100 Subject: [PATCH 54/75] Added missing nullity annotations for the pager adapters (#219) --- .../conductor/support/ControllerPagerAdapter.java | 4 +++- .../bluelinelabs/conductor/support/RouterPagerAdapter.java | 5 +++-- .../conductor/demo/controllers/PagerController.java | 2 +- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/conductor-support/src/main/java/com/bluelinelabs/conductor/support/ControllerPagerAdapter.java b/conductor-support/src/main/java/com/bluelinelabs/conductor/support/ControllerPagerAdapter.java index 53ec15d7..98f35f3f 100644 --- a/conductor-support/src/main/java/com/bluelinelabs/conductor/support/ControllerPagerAdapter.java +++ b/conductor-support/src/main/java/com/bluelinelabs/conductor/support/ControllerPagerAdapter.java @@ -2,6 +2,7 @@ import android.os.Bundle; import android.os.Parcelable; +import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.view.PagerAdapter; import android.util.SparseArray; @@ -34,7 +35,7 @@ public abstract class ControllerPagerAdapter extends PagerAdapter { /** * Creates a new ControllerPagerAdapter using the passed host. */ - public ControllerPagerAdapter(Controller host, boolean saveControllerState) { + public ControllerPagerAdapter(@NonNull Controller host, boolean saveControllerState) { this.host = host; savesState = saveControllerState; } @@ -42,6 +43,7 @@ public ControllerPagerAdapter(Controller host, boolean saveControllerState) { /** * Return the Controller associated with a specified position. */ + @NonNull public abstract Controller getItem(int position); @Override diff --git a/conductor-support/src/main/java/com/bluelinelabs/conductor/support/RouterPagerAdapter.java b/conductor-support/src/main/java/com/bluelinelabs/conductor/support/RouterPagerAdapter.java index 96e9c74a..6020f061 100644 --- a/conductor-support/src/main/java/com/bluelinelabs/conductor/support/RouterPagerAdapter.java +++ b/conductor-support/src/main/java/com/bluelinelabs/conductor/support/RouterPagerAdapter.java @@ -2,6 +2,7 @@ import android.os.Bundle; import android.os.Parcelable; +import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.view.PagerAdapter; import android.util.SparseArray; @@ -28,7 +29,7 @@ public abstract class RouterPagerAdapter extends PagerAdapter { /** * Creates a new RouterPagerAdapter using the passed host. */ - public RouterPagerAdapter(Controller host) { + public RouterPagerAdapter(@NonNull Controller host) { this.host = host; } @@ -38,7 +39,7 @@ public RouterPagerAdapter(Controller host) { * @param router The router used for the page * @param position The page position to be instantiated. */ - public abstract void configureRouter(Router router, int position); + public abstract void configureRouter(@NonNull Router router, int position); @Override public Object instantiateItem(ViewGroup container, int position) { diff --git a/demo/src/main/java/com/bluelinelabs/conductor/demo/controllers/PagerController.java b/demo/src/main/java/com/bluelinelabs/conductor/demo/controllers/PagerController.java index f5def56b..2f34de4a 100644 --- a/demo/src/main/java/com/bluelinelabs/conductor/demo/controllers/PagerController.java +++ b/demo/src/main/java/com/bluelinelabs/conductor/demo/controllers/PagerController.java @@ -30,7 +30,7 @@ public class PagerController extends BaseController { public PagerController() { pagerAdapter = new RouterPagerAdapter(this) { @Override - public void configureRouter(Router router, int position) { + public void configureRouter(@NonNull Router router, int position) { if (!router.hasRootController()) { Controller page = new ChildController(String.format(Locale.getDefault(), "Child #%d (Swipe to see more)", position), PAGE_COLORS[position], true); router.setRoot(RouterTransaction.with(page)); From 314ee2b456ff11cf3bc4e411a04aaac39c56e91f Mon Sep 17 00:00:00 2001 From: Eric Kuck Date: Tue, 7 Feb 2017 10:22:33 -0600 Subject: [PATCH 55/75] Routers now properly remove views of all owned controllers on destroy (fixes #221) --- .../java/com/bluelinelabs/conductor/Router.java | 17 +++++++++++++++-- .../com/bluelinelabs/conductor/RouterTests.java | 17 +++++++++++++++++ 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/conductor/src/main/java/com/bluelinelabs/conductor/Router.java b/conductor/src/main/java/com/bluelinelabs/conductor/Router.java index b8028f1b..297b2371 100755 --- a/conductor/src/main/java/com/bluelinelabs/conductor/Router.java +++ b/conductor/src/main/java/com/bluelinelabs/conductor/Router.java @@ -188,11 +188,24 @@ public void replaceTopController(@NonNull RouterTransaction transaction) { void destroy(boolean popViews) { popsLastView = true; - List poppedControllers = backstack.popAll(); + final List poppedControllers = backstack.popAll(); trackDestroyingControllers(poppedControllers); if (popViews && poppedControllers.size() > 0) { - performControllerChange(null, poppedControllers.get(0), false, poppedControllers.get(0).popChangeHandler()); + RouterTransaction topTransaction = poppedControllers.get(0); + topTransaction.controller().addLifecycleListener(new LifecycleListener() { + @Override + public void onChangeEnd(@NonNull Controller controller, @NonNull ControllerChangeHandler changeHandler, @NonNull ControllerChangeType changeType) { + if (changeType == ControllerChangeType.POP_EXIT) { + for (int i = poppedControllers.size() - 1; i > 0; i--) { + RouterTransaction transaction = poppedControllers.get(i); + performControllerChange(null, transaction, true, new SimpleSwapChangeHandler()); + } + } + } + }); + + performControllerChange(null, topTransaction, false, topTransaction.popChangeHandler()); } } diff --git a/conductor/src/test/java/com/bluelinelabs/conductor/RouterTests.java b/conductor/src/test/java/com/bluelinelabs/conductor/RouterTests.java index fcbd441f..63f9f64e 100644 --- a/conductor/src/test/java/com/bluelinelabs/conductor/RouterTests.java +++ b/conductor/src/test/java/com/bluelinelabs/conductor/RouterTests.java @@ -2,6 +2,7 @@ import android.view.ViewGroup; +import com.bluelinelabs.conductor.changehandler.FadeChangeHandler; import com.bluelinelabs.conductor.util.ActivityProxy; import com.bluelinelabs.conductor.util.ListUtils; import com.bluelinelabs.conductor.util.MockChangeHandler; @@ -371,4 +372,20 @@ public void testChildRouterRearrangeTransactionBackstack() { assertEquals(0, childRouter.getBackstackSize()); } + @Test + public void testRemovesAllViewsOnDestroy() { + Controller controller1 = new TestController(); + Controller controller2 = new TestController(); + + router.setRoot(RouterTransaction.with(controller1)); + router.pushController(RouterTransaction.with(controller2) + .pushChangeHandler(new FadeChangeHandler(false))); + + assertEquals(2, router.container.getChildCount()); + + router.destroy(true); + + assertEquals(0, router.container.getChildCount()); + } + } From 97878b1ad62c825116a84ad2bd07df74f6e8e278 Mon Sep 17 00:00:00 2001 From: Paul Woitaschek Date: Wed, 8 Feb 2017 17:37:07 +0100 Subject: [PATCH 56/75] List simplification (#225) * Use a singleton list for transactions * Use built-in java list functions --- .../com/bluelinelabs/conductor/Router.java | 3 +- ...rollerLifecycleActivityReferenceTests.java | 99 ++++++++++--------- .../conductor/RouterChangeHandlerTests.java | 6 +- .../bluelinelabs/conductor/RouterTests.java | 20 ++-- .../conductor/util/ListUtils.java | 16 --- 5 files changed, 64 insertions(+), 80 deletions(-) delete mode 100644 conductor/src/test/java/com/bluelinelabs/conductor/util/ListUtils.java diff --git a/conductor/src/main/java/com/bluelinelabs/conductor/Router.java b/conductor/src/main/java/com/bluelinelabs/conductor/Router.java index 297b2371..bb8e0503 100755 --- a/conductor/src/main/java/com/bluelinelabs/conductor/Router.java +++ b/conductor/src/main/java/com/bluelinelabs/conductor/Router.java @@ -288,8 +288,7 @@ public boolean popToTag(@NonNull String tag, @Nullable ControllerChangeHandler c */ @UiThread public void setRoot(@NonNull RouterTransaction transaction) { - List transactions = new ArrayList<>(); - transactions.add(transaction); + List transactions = Collections.singletonList(transaction); setBackstack(transactions, transaction.pushChangeHandler()); } diff --git a/conductor/src/test/java/com/bluelinelabs/conductor/ControllerLifecycleActivityReferenceTests.java b/conductor/src/test/java/com/bluelinelabs/conductor/ControllerLifecycleActivityReferenceTests.java index 15a9d7db..cc97192f 100644 --- a/conductor/src/test/java/com/bluelinelabs/conductor/ControllerLifecycleActivityReferenceTests.java +++ b/conductor/src/test/java/com/bluelinelabs/conductor/ControllerLifecycleActivityReferenceTests.java @@ -6,7 +6,6 @@ import android.view.ViewGroup; import com.bluelinelabs.conductor.util.ActivityProxy; -import com.bluelinelabs.conductor.util.ListUtils; import com.bluelinelabs.conductor.util.MockChangeHandler; import com.bluelinelabs.conductor.util.TestController; @@ -17,6 +16,8 @@ import org.robolectric.annotation.Config; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; import java.util.List; import static org.junit.Assert.assertEquals; @@ -62,12 +63,12 @@ public void testSingleControllerActivityOnPush() { .pushChangeHandler(MockChangeHandler.defaultHandler()) .popChangeHandler(MockChangeHandler.defaultHandler())); - assertEquals(ListUtils.listOf(true), listener.changeEndReferences); - assertEquals(ListUtils.listOf(true), listener.postCreateViewReferences); - assertEquals(ListUtils.listOf(true), listener.postAttachReferences); - assertEquals(ListUtils.listOf(), listener.postDetachReferences); - assertEquals(ListUtils.listOf(), listener.postDestroyViewReferences); - assertEquals(ListUtils.listOf(), listener.postDestroyReferences); + assertEquals(Collections.singletonList(true), listener.changeEndReferences); + assertEquals(Collections.singletonList(true), listener.postCreateViewReferences); + assertEquals(Collections.singletonList(true), listener.postAttachReferences); + assertEquals(Collections.emptyList(), listener.postDetachReferences); + assertEquals(Collections.emptyList(), listener.postDestroyViewReferences); + assertEquals(Collections.emptyList(), listener.postDestroyReferences); } @Test @@ -89,12 +90,12 @@ public void testChildControllerActivityOnPush() { .pushChangeHandler(MockChangeHandler.defaultHandler()) .popChangeHandler(MockChangeHandler.defaultHandler())); - assertEquals(ListUtils.listOf(true), listener.changeEndReferences); - assertEquals(ListUtils.listOf(true), listener.postCreateViewReferences); - assertEquals(ListUtils.listOf(true), listener.postAttachReferences); - assertEquals(ListUtils.listOf(), listener.postDetachReferences); - assertEquals(ListUtils.listOf(), listener.postDestroyViewReferences); - assertEquals(ListUtils.listOf(), listener.postDestroyReferences); + assertEquals(Collections.singletonList(true), listener.changeEndReferences); + assertEquals(Collections.singletonList(true), listener.postCreateViewReferences); + assertEquals(Collections.singletonList(true), listener.postAttachReferences); + assertEquals(Collections.emptyList(), listener.postDetachReferences); + assertEquals(Collections.emptyList(), listener.postDestroyViewReferences); + assertEquals(Collections.emptyList(), listener.postDestroyReferences); } @Test @@ -110,12 +111,12 @@ public void testSingleControllerActivityOnPop() { router.popCurrentController(); - assertEquals(ListUtils.listOf(true, true), listener.changeEndReferences); - assertEquals(ListUtils.listOf(true), listener.postCreateViewReferences); - assertEquals(ListUtils.listOf(true), listener.postAttachReferences); - assertEquals(ListUtils.listOf(true), listener.postDetachReferences); - assertEquals(ListUtils.listOf(true), listener.postDestroyViewReferences); - assertEquals(ListUtils.listOf(true), listener.postDestroyReferences); + assertEquals(Arrays.asList(true, true), listener.changeEndReferences); + assertEquals(Collections.singletonList(true), listener.postCreateViewReferences); + assertEquals(Collections.singletonList(true), listener.postAttachReferences); + assertEquals(Collections.singletonList(true), listener.postDetachReferences); + assertEquals(Collections.singletonList(true), listener.postDestroyViewReferences); + assertEquals(Collections.singletonList(true), listener.postDestroyReferences); } @Test @@ -139,12 +140,12 @@ public void testChildControllerActivityOnPop() { childRouter.popCurrentController(); - assertEquals(ListUtils.listOf(true, true), listener.changeEndReferences); - assertEquals(ListUtils.listOf(true), listener.postCreateViewReferences); - assertEquals(ListUtils.listOf(true), listener.postAttachReferences); - assertEquals(ListUtils.listOf(true), listener.postDetachReferences); - assertEquals(ListUtils.listOf(true), listener.postDestroyViewReferences); - assertEquals(ListUtils.listOf(true), listener.postDestroyReferences); + assertEquals(Arrays.asList(true, true), listener.changeEndReferences); + assertEquals(Collections.singletonList(true), listener.postCreateViewReferences); + assertEquals(Collections.singletonList(true), listener.postAttachReferences); + assertEquals(Collections.singletonList(true), listener.postDetachReferences); + assertEquals(Collections.singletonList(true), listener.postDestroyViewReferences); + assertEquals(Collections.singletonList(true), listener.postDestroyReferences); } @Test @@ -168,12 +169,12 @@ public void testChildControllerActivityOnParentPop() { router.popCurrentController(); - assertEquals(ListUtils.listOf(true), listener.changeEndReferences); - assertEquals(ListUtils.listOf(true), listener.postCreateViewReferences); - assertEquals(ListUtils.listOf(true), listener.postAttachReferences); - assertEquals(ListUtils.listOf(true), listener.postDetachReferences); - assertEquals(ListUtils.listOf(true), listener.postDestroyViewReferences); - assertEquals(ListUtils.listOf(true), listener.postDestroyReferences); + assertEquals(Collections.singletonList(true), listener.changeEndReferences); + assertEquals(Collections.singletonList(true), listener.postCreateViewReferences); + assertEquals(Collections.singletonList(true), listener.postAttachReferences); + assertEquals(Collections.singletonList(true), listener.postDetachReferences); + assertEquals(Collections.singletonList(true), listener.postDestroyViewReferences); + assertEquals(Collections.singletonList(true), listener.postDestroyReferences); } @Test @@ -189,12 +190,12 @@ public void testSingleControllerActivityOnDestroy() { activityProxy.pause().stop(false).destroy(); - assertEquals(ListUtils.listOf(true), listener.changeEndReferences); - assertEquals(ListUtils.listOf(true), listener.postCreateViewReferences); - assertEquals(ListUtils.listOf(true), listener.postAttachReferences); - assertEquals(ListUtils.listOf(true), listener.postDetachReferences); - assertEquals(ListUtils.listOf(true), listener.postDestroyViewReferences); - assertEquals(ListUtils.listOf(true), listener.postDestroyReferences); + assertEquals(Collections.singletonList(true), listener.changeEndReferences); + assertEquals(Collections.singletonList(true), listener.postCreateViewReferences); + assertEquals(Collections.singletonList(true), listener.postAttachReferences); + assertEquals(Collections.singletonList(true), listener.postDetachReferences); + assertEquals(Collections.singletonList(true), listener.postDestroyViewReferences); + assertEquals(Collections.singletonList(true), listener.postDestroyReferences); } @Test @@ -218,21 +219,21 @@ public void testChildControllerActivityOnDestroy() { activityProxy.pause().stop(false).destroy(); - assertEquals(ListUtils.listOf(true), listener.changeEndReferences); - assertEquals(ListUtils.listOf(true), listener.postCreateViewReferences); - assertEquals(ListUtils.listOf(true), listener.postAttachReferences); - assertEquals(ListUtils.listOf(true), listener.postDetachReferences); - assertEquals(ListUtils.listOf(true), listener.postDestroyViewReferences); - assertEquals(ListUtils.listOf(true), listener.postDestroyReferences); + assertEquals(Collections.singletonList(true), listener.changeEndReferences); + assertEquals(Collections.singletonList(true), listener.postCreateViewReferences); + assertEquals(Collections.singletonList(true), listener.postAttachReferences); + assertEquals(Collections.singletonList(true), listener.postDetachReferences); + assertEquals(Collections.singletonList(true), listener.postDestroyViewReferences); + assertEquals(Collections.singletonList(true), listener.postDestroyReferences); } static class ActivityReferencingLifecycleListener extends Controller.LifecycleListener { - List changeEndReferences = new ArrayList<>(); - List postCreateViewReferences = new ArrayList<>(); - List postAttachReferences = new ArrayList<>(); - List postDetachReferences = new ArrayList<>(); - List postDestroyViewReferences = new ArrayList<>(); - List postDestroyReferences = new ArrayList<>(); + final List changeEndReferences = new ArrayList<>(); + final List postCreateViewReferences = new ArrayList<>(); + final List postAttachReferences = new ArrayList<>(); + final List postDetachReferences = new ArrayList<>(); + final List postDestroyViewReferences = new ArrayList<>(); + final List postDestroyReferences = new ArrayList<>(); @Override public void onChangeEnd(@NonNull Controller controller, @NonNull ControllerChangeHandler changeHandler, @NonNull ControllerChangeType changeType) { diff --git a/conductor/src/test/java/com/bluelinelabs/conductor/RouterChangeHandlerTests.java b/conductor/src/test/java/com/bluelinelabs/conductor/RouterChangeHandlerTests.java index 4f92fafd..b75d7240 100644 --- a/conductor/src/test/java/com/bluelinelabs/conductor/RouterChangeHandlerTests.java +++ b/conductor/src/test/java/com/bluelinelabs/conductor/RouterChangeHandlerTests.java @@ -3,7 +3,6 @@ import android.view.View; import com.bluelinelabs.conductor.util.ActivityProxy; -import com.bluelinelabs.conductor.util.ListUtils; import com.bluelinelabs.conductor.util.MockChangeHandler; import com.bluelinelabs.conductor.util.TestController; @@ -13,6 +12,7 @@ import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; +import java.util.Arrays; import java.util.List; import static org.junit.Assert.assertEquals; @@ -142,7 +142,7 @@ public void testSetBackstackHandlers() { TestController newController2 = new TestController(); MockChangeHandler setBackstackHandler = MockChangeHandler.taggedHandler("setBackstackHandler", true); - List newBackstack = ListUtils.listOf( + List newBackstack = Arrays.asList( RouterTransaction.with(newController1), RouterTransaction.with(newController2) ); @@ -194,7 +194,7 @@ public void testSetBackstackWithTwoVisibleHandlers() { TestController newController2 = new TestController(); MockChangeHandler setBackstackHandler = MockChangeHandler.taggedHandler("setBackstackHandler", true); - List newBackstack = ListUtils.listOf( + List newBackstack = Arrays.asList( RouterTransaction.with(newController1), RouterTransaction.with(newController2).pushChangeHandler(MockChangeHandler.noRemoveViewOnPushHandler()) ); diff --git a/conductor/src/test/java/com/bluelinelabs/conductor/RouterTests.java b/conductor/src/test/java/com/bluelinelabs/conductor/RouterTests.java index 63f9f64e..07792c97 100644 --- a/conductor/src/test/java/com/bluelinelabs/conductor/RouterTests.java +++ b/conductor/src/test/java/com/bluelinelabs/conductor/RouterTests.java @@ -4,7 +4,6 @@ import com.bluelinelabs.conductor.changehandler.FadeChangeHandler; import com.bluelinelabs.conductor.util.ActivityProxy; -import com.bluelinelabs.conductor.util.ListUtils; import com.bluelinelabs.conductor.util.MockChangeHandler; import com.bluelinelabs.conductor.util.TestController; @@ -14,6 +13,7 @@ import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; +import java.util.Arrays; import java.util.List; import static org.junit.Assert.assertEquals; @@ -190,7 +190,7 @@ public void testSetBackstack() { RouterTransaction middleTransaction = RouterTransaction.with(new TestController()); RouterTransaction topTransaction = RouterTransaction.with(new TestController()); - List backstack = ListUtils.listOf(rootTransaction, middleTransaction, topTransaction); + List backstack = Arrays.asList(rootTransaction, middleTransaction, topTransaction); router.setBackstack(backstack, null); assertEquals(3, router.getBackstackSize()); @@ -211,7 +211,7 @@ public void testNewSetBackstack() { RouterTransaction middleTransaction = RouterTransaction.with(new TestController()); RouterTransaction topTransaction = RouterTransaction.with(new TestController()); - List backstack = ListUtils.listOf(rootTransaction, middleTransaction, topTransaction); + List backstack = Arrays.asList(rootTransaction, middleTransaction, topTransaction); router.setBackstack(backstack, null); assertEquals(3, router.getBackstackSize()); @@ -242,7 +242,7 @@ public void testNewSetBackstackWithNoRemoveViewOnPush() { RouterTransaction middleTransaction = RouterTransaction.with(new TestController()).pushChangeHandler(MockChangeHandler.noRemoveViewOnPushHandler()); RouterTransaction topTransaction = RouterTransaction.with(new TestController()).pushChangeHandler(MockChangeHandler.noRemoveViewOnPushHandler()); - List backstack = ListUtils.listOf(rootTransaction, middleTransaction, topTransaction); + List backstack = Arrays.asList(rootTransaction, middleTransaction, topTransaction); router.setBackstack(backstack, null); assertEquals(3, router.getBackstackSize()); @@ -264,7 +264,7 @@ public void testReplaceTopController() { RouterTransaction rootTransaction = RouterTransaction.with(new TestController()); RouterTransaction topTransaction = RouterTransaction.with(new TestController()); - List backstack = ListUtils.listOf(rootTransaction, topTransaction); + List backstack = Arrays.asList(rootTransaction, topTransaction); router.setBackstack(backstack, null); assertEquals(2, router.getBackstackSize()); @@ -288,7 +288,7 @@ public void testReplaceTopControllerWithNoRemoveViewOnPush() { RouterTransaction rootTransaction = RouterTransaction.with(new TestController()); RouterTransaction topTransaction = RouterTransaction.with(new TestController()).pushChangeHandler(MockChangeHandler.noRemoveViewOnPushHandler()); - List backstack = ListUtils.listOf(rootTransaction, topTransaction); + List backstack = Arrays.asList(rootTransaction, topTransaction); router.setBackstack(backstack, null); assertEquals(2, router.getBackstackSize()); @@ -320,13 +320,13 @@ public void testRearrangeTransactionBackstack() { RouterTransaction transaction1 = RouterTransaction.with(new TestController()); RouterTransaction transaction2 = RouterTransaction.with(new TestController()); - List backstack = ListUtils.listOf(transaction1, transaction2); + List backstack = Arrays.asList(transaction1, transaction2); router.setBackstack(backstack, null); assertEquals(1, transaction1.transactionIndex); assertEquals(2, transaction2.transactionIndex); - backstack = ListUtils.listOf(transaction2, transaction1); + backstack = Arrays.asList(transaction2, transaction1); router.setBackstack(backstack, null); assertEquals(1, transaction2.transactionIndex); @@ -351,13 +351,13 @@ public void testChildRouterRearrangeTransactionBackstack() { RouterTransaction transaction1 = RouterTransaction.with(new TestController()); RouterTransaction transaction2 = RouterTransaction.with(new TestController()); - List backstack = ListUtils.listOf(transaction1, transaction2); + List backstack = Arrays.asList(transaction1, transaction2); childRouter.setBackstack(backstack, null); assertEquals(2, transaction1.transactionIndex); assertEquals(3, transaction2.transactionIndex); - backstack = ListUtils.listOf(transaction2, transaction1); + backstack = Arrays.asList(transaction2, transaction1); childRouter.setBackstack(backstack, null); assertEquals(2, transaction2.transactionIndex); diff --git a/conductor/src/test/java/com/bluelinelabs/conductor/util/ListUtils.java b/conductor/src/test/java/com/bluelinelabs/conductor/util/ListUtils.java deleted file mode 100644 index 67bf84e9..00000000 --- a/conductor/src/test/java/com/bluelinelabs/conductor/util/ListUtils.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.bluelinelabs.conductor.util; - -import java.util.ArrayList; -import java.util.List; - -public class ListUtils { - - public static List listOf(T... elements) { - List list = new ArrayList<>(); - for (T element : elements) { - list.add(element); - } - return list; - } - -} From 86227ae3b34d0dce02321caa9bec24fd9fe26b06 Mon Sep 17 00:00:00 2001 From: Eric Kuck Date: Tue, 14 Feb 2017 13:50:13 -0600 Subject: [PATCH 57/75] =?UTF-8?q?TransitionChangeHandler=20is=20now=20much?= =?UTF-8?q?=20more=20flexible=20(doesn=E2=80=99t=20force=20you=20to=20add/?= =?UTF-8?q?remove=20views)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../TransitionChangeHandler.java | 29 +++++++++++++------ 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/conductor/src/main/java/com/bluelinelabs/conductor/changehandler/TransitionChangeHandler.java b/conductor/src/main/java/com/bluelinelabs/conductor/changehandler/TransitionChangeHandler.java index aea4cfb1..f1f51fab 100644 --- a/conductor/src/main/java/com/bluelinelabs/conductor/changehandler/TransitionChangeHandler.java +++ b/conductor/src/main/java/com/bluelinelabs/conductor/changehandler/TransitionChangeHandler.java @@ -25,9 +25,9 @@ public abstract class TransitionChangeHandler extends ControllerChangeHandler { * Should be overridden to return the Transition to use while replacing Views. * * @param container The container these Views are hosted in - * @param from The previous View in the container or {@code null} if there was no Controller before this transition - * @param to The next View that should be put in the container or {@code null} if no Controller is being transitioned to - * @param isPush True if this is a push transaction, false if it's a pop + * @param from The previous View in the container or {@code null} if there was no Controller before this transition + * @param to The next View that should be put in the container or {@code null} if no Controller is being transitioned to + * @param isPush True if this is a push transaction, false if it's a pop */ @NonNull protected abstract Transition getTransition(@NonNull ViewGroup container, @Nullable View from, @Nullable View to, boolean isPush); @@ -68,8 +68,24 @@ public void onTransitionPause(Transition transition) { } public void onTransitionResume(Transition transition) { } }); + prepareForTransition(container, from, to, isPush); + TransitionManager.beginDelayedTransition(container, transition); - if (from != null) { + + executePropertyChanges(container, from, to, isPush); + } + + @Override + public boolean removesFromViewOnPush() { + return true; + } + + public void prepareForTransition(@NonNull ViewGroup container, @Nullable View from, @Nullable View to, boolean isPush) { + + } + + public void executePropertyChanges(@NonNull ViewGroup container, @Nullable View from, @Nullable View to, boolean isPush) { + if (from != null && (removesFromViewOnPush() || !isPush) && from.getParent() == container) { container.removeView(from); } if (to != null && to.getParent() == null) { @@ -77,9 +93,4 @@ public void onTransitionResume(Transition transition) { } } } - @Override - public final boolean removesFromViewOnPush() { - return true; - } - } From df68655b13e3b4a5234890b767a3b9a8e25c13cd Mon Sep 17 00:00:00 2001 From: Eric Kuck Date: Tue, 14 Feb 2017 13:52:20 -0600 Subject: [PATCH 58/75] Added missing comments to new methods --- .../changehandler/TransitionChangeHandler.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/conductor/src/main/java/com/bluelinelabs/conductor/changehandler/TransitionChangeHandler.java b/conductor/src/main/java/com/bluelinelabs/conductor/changehandler/TransitionChangeHandler.java index f1f51fab..c9138885 100644 --- a/conductor/src/main/java/com/bluelinelabs/conductor/changehandler/TransitionChangeHandler.java +++ b/conductor/src/main/java/com/bluelinelabs/conductor/changehandler/TransitionChangeHandler.java @@ -80,10 +80,27 @@ public boolean removesFromViewOnPush() { return true; } + /** + * Called directly before a transition occurs. This can be used to reorder views, set their transition names, etc. + * + * @param container The container these Views are hosted in + * @param from The previous View in the container or {@code null} if there was no Controller before this transition + * @param to The next View that should be put in the container or {@code null} if no Controller is being transitioned to + * @param isPush True if this is a push transaction, false if it's a pop + */ public void prepareForTransition(@NonNull ViewGroup container, @Nullable View from, @Nullable View to, boolean isPush) { } + /** + * This should set all view properties needed for the transition to work properly. By default it removes the "from" view + * and adds the "to" view. + * + * @param container The container these Views are hosted in + * @param from The previous View in the container or {@code null} if there was no Controller before this transition + * @param to The next View that should be put in the container or {@code null} if no Controller is being transitioned to + * @param isPush True if this is a push transaction, false if it's a pop + */ public void executePropertyChanges(@NonNull ViewGroup container, @Nullable View from, @Nullable View to, boolean isPush) { if (from != null && (removesFromViewOnPush() || !isPush) && from.getParent() == container) { container.removeView(from); From a888073e1be50ae0179a5276254c5fec3a8f7804 Mon Sep 17 00:00:00 2001 From: Simon Vergauwen Date: Thu, 16 Feb 2017 17:24:49 +0100 Subject: [PATCH 59/75] DialogToFabTransition (#229) --- .../CustomTransitionChangeHandler.java | 84 ++++ .../FabToDialogAnimatorChangeHandler.java | 48 +++ .../FabToDialogTransitionChangeHandler.java | 79 ++++ .../transitions/FabTransform.java | 295 ++++++++++++++ .../transitions/GravityArcMotion.java | 217 ++++++++++ .../CustomTransitionDemoController.java | 54 +++ .../demo/controllers/DialogController.java | 33 ++ .../demo/controllers/HomeController.java | 6 + .../conductor/demo/util/AnimUtils.java | 375 ++++++++++++++++++ .../demo/util/CustomTransitionCompatUtil.java | 21 + demo/src/main/res/animator/raise.xml | 17 + demo/src/main/res/drawable/dialog_bg.xml | 7 + demo/src/main/res/drawable/ic_add_dark.xml | 12 + .../layout/controller_custom_transition.xml | 23 ++ .../src/main/res/layout/controller_dialog.xml | 58 +++ .../main/res/layout/controller_overlay.xml | 2 +- demo/src/main/res/values-sw600dp/dimens.xml | 8 + .../main/res/values/attrs_fab_transform.xml | 22 + demo/src/main/res/values/colors.xml | 5 + demo/src/main/res/values/dimens.xml | 8 + demo/src/main/res/values/strings.xml | 3 + 21 files changed, 1376 insertions(+), 1 deletion(-) create mode 100644 demo/src/main/java/com/bluelinelabs/conductor/demo/changehandler/CustomTransitionChangeHandler.java create mode 100644 demo/src/main/java/com/bluelinelabs/conductor/demo/changehandler/FabToDialogAnimatorChangeHandler.java create mode 100644 demo/src/main/java/com/bluelinelabs/conductor/demo/changehandler/FabToDialogTransitionChangeHandler.java create mode 100644 demo/src/main/java/com/bluelinelabs/conductor/demo/changehandler/transitions/FabTransform.java create mode 100644 demo/src/main/java/com/bluelinelabs/conductor/demo/changehandler/transitions/GravityArcMotion.java create mode 100644 demo/src/main/java/com/bluelinelabs/conductor/demo/controllers/CustomTransitionDemoController.java create mode 100644 demo/src/main/java/com/bluelinelabs/conductor/demo/controllers/DialogController.java create mode 100644 demo/src/main/java/com/bluelinelabs/conductor/demo/util/AnimUtils.java create mode 100644 demo/src/main/java/com/bluelinelabs/conductor/demo/util/CustomTransitionCompatUtil.java create mode 100644 demo/src/main/res/animator/raise.xml create mode 100644 demo/src/main/res/drawable/dialog_bg.xml create mode 100644 demo/src/main/res/drawable/ic_add_dark.xml create mode 100644 demo/src/main/res/layout/controller_custom_transition.xml create mode 100644 demo/src/main/res/layout/controller_dialog.xml create mode 100644 demo/src/main/res/values-sw600dp/dimens.xml create mode 100644 demo/src/main/res/values/attrs_fab_transform.xml diff --git a/demo/src/main/java/com/bluelinelabs/conductor/demo/changehandler/CustomTransitionChangeHandler.java b/demo/src/main/java/com/bluelinelabs/conductor/demo/changehandler/CustomTransitionChangeHandler.java new file mode 100644 index 00000000..3bcff7ec --- /dev/null +++ b/demo/src/main/java/com/bluelinelabs/conductor/demo/changehandler/CustomTransitionChangeHandler.java @@ -0,0 +1,84 @@ +package com.bluelinelabs.conductor.demo.changehandler; + + +import android.annotation.TargetApi; +import android.os.Build; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.transition.Transition; +import android.view.View; +import android.view.ViewGroup; + +import com.bluelinelabs.conductor.Controller; +import com.bluelinelabs.conductor.ControllerChangeHandler; + +/** + * A base [ControllerChangeHandler] that facilitates using [android.transition.Transition]s to replace Controller Views. + */ +@TargetApi(Build.VERSION_CODES.LOLLIPOP) +abstract class CustomTransitionChangeHandler extends ControllerChangeHandler { + + private boolean canceled; + + /** + * Should be overridden to return the Transition to use while replacing Views. + * + * @param container The container these Views are hosted in + * @param from The previous View in the container or {@code null} if there was no Controller before this transition + * @param to The next View that should be put in the container or {@code null} if no Controller is being transitioned to + * @param isPush True if this is a push transaction, false if it's a pop + */ + @NonNull + protected abstract Transition getTransition(@NonNull ViewGroup container, @Nullable View from, @Nullable View to, boolean isPush); + + /** + * Should be overridden to implement the change in the view hierarchy + * + * @param container The container these Views are hosted in + * @param from The previous View in the container or {@code null} if there was no Controller before this transition + * @param to The next View that should be put in the container or {@code null} if no Controller is being transitioned to + * @param isPush True if this is a push transaction, false if it's a pop + */ + protected abstract void viewChange(@NonNull ViewGroup container, @Nullable View from, @Nullable View to, @NonNull Transition transition, boolean isPush); + + @Override + public void onAbortPush(@NonNull final ControllerChangeHandler newHandler, @Nullable final Controller newTop) { + super.onAbortPush(newHandler, newTop); + canceled = true; + } + + @Override + public void performChange(@NonNull final ViewGroup container, @Nullable final View from, @Nullable final View to, boolean isPush, @NonNull final ControllerChangeCompletedListener changeListener) { + if (canceled) { + changeListener.onChangeCompleted(); + return; + } + + Transition transition = getTransition(container, from, to, isPush); + transition.addListener(new Transition.TransitionListener() { + @Override + public void onTransitionStart(Transition transition) { + } + + @Override + public void onTransitionEnd(Transition transition) { + changeListener.onChangeCompleted(); + } + + @Override + public void onTransitionCancel(Transition transition) { + changeListener.onChangeCompleted(); + } + + @Override + public void onTransitionPause(Transition transition) { + } + + @Override + public void onTransitionResume(Transition transition) { + } + }); + + viewChange(container, from, to, transition, isPush); + } +} diff --git a/demo/src/main/java/com/bluelinelabs/conductor/demo/changehandler/FabToDialogAnimatorChangeHandler.java b/demo/src/main/java/com/bluelinelabs/conductor/demo/changehandler/FabToDialogAnimatorChangeHandler.java new file mode 100644 index 00000000..210fac93 --- /dev/null +++ b/demo/src/main/java/com/bluelinelabs/conductor/demo/changehandler/FabToDialogAnimatorChangeHandler.java @@ -0,0 +1,48 @@ +package com.bluelinelabs.conductor.demo.changehandler; + + +import android.animation.Animator; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.view.View; +import android.view.ViewGroup; + +import com.bluelinelabs.conductor.changehandler.AnimatorChangeHandler; +import com.bluelinelabs.conductor.demo.R; + +public class FabToDialogAnimatorChangeHandler extends AnimatorChangeHandler { + + public FabToDialogAnimatorChangeHandler() { + super(false); + } + + @NonNull @Override + protected Animator getAnimator(@NonNull final ViewGroup container, @Nullable final View from, @Nullable final View to, boolean isPush, boolean toAddedToContainer) { + final AnimatorSet animator = new AnimatorSet(); + final View fab = container.findViewById(R.id.fab); + + if (isPush) { + if (fab != null) { + animator.play(ObjectAnimator.ofFloat(fab, View.ALPHA, 0f)); + } + if (to != null && toAddedToContainer) { + animator.play(ObjectAnimator.ofFloat(to, View.ALPHA, 0f, 1f)); + } + } else { + if (from != null) { + animator.play(ObjectAnimator.ofFloat(from, View.ALPHA, 0f)); + } + if (fab != null) { + animator.play(ObjectAnimator.ofFloat(fab, View.ALPHA, 0f, 1f)); + } + } + return animator; + } + + @Override + protected void resetFromView(@NonNull View from) { + + } +} diff --git a/demo/src/main/java/com/bluelinelabs/conductor/demo/changehandler/FabToDialogTransitionChangeHandler.java b/demo/src/main/java/com/bluelinelabs/conductor/demo/changehandler/FabToDialogTransitionChangeHandler.java new file mode 100644 index 00000000..bd885a86 --- /dev/null +++ b/demo/src/main/java/com/bluelinelabs/conductor/demo/changehandler/FabToDialogTransitionChangeHandler.java @@ -0,0 +1,79 @@ +package com.bluelinelabs.conductor.demo.changehandler; + +import android.annotation.TargetApi; +import android.os.Build; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.content.ContextCompat; +import android.transition.Transition; +import android.transition.TransitionManager; +import android.view.View; +import android.view.ViewGroup; + +import com.bluelinelabs.conductor.demo.R; +import com.bluelinelabs.conductor.demo.changehandler.transitions.FabTransform; +import com.bluelinelabs.conductor.demo.util.AnimUtils; + +@TargetApi(Build.VERSION_CODES.LOLLIPOP) +public class FabToDialogTransitionChangeHandler extends CustomTransitionChangeHandler { + + @NonNull + @Override + protected Transition getTransition(@NonNull final ViewGroup container, @Nullable final View from, @Nullable final View to, boolean isPush) { + return new FabTransform(ContextCompat.getColor(container.getContext(), R.color.colorAccent), R.drawable.ic_add_dark); + } + + /* + * Container => ChangeHandlerFrameLayout of CustomTransitionDemoController + * if push (fab to dialog) => from == container and to == DialogController::getView (ChangeHandlerFrameLayout which contains dialog) + * if pop (dialog to fab) => from == DialogController::getView and to == container + */ + @Override + protected void viewChange(@NonNull final ViewGroup container, @Nullable final View from, @Nullable final View to, @NonNull final Transition transition, boolean isPush) { + final View fab; + if (isPush) { + fab = from.findViewById(R.id.fab); + } else { + fab = to.findViewById(R.id.fab); + } + final ViewGroup fabParent = (ViewGroup)fab.getParent(); + + if (isPush) { + TransitionManager.beginDelayedTransition(container, transition); + fabParent.removeView(fab); + container.addView(to); + + /* + * After the transition is finished we have to add the fab back to the original container. + * Because otherwise we will be lost when trying to transition back. + * Set it to invisible because we don't want it to jump back after the transition + */ + transition.addListener(new AnimUtils.TransitionListenerWrapper() { + @Override + public void onTransitionEnd(Transition transition) { + super.onTransitionEnd(transition); + fab.setVisibility(View.GONE); + fabParent.addView(fab); + } + + @Override + public void onTransitionCancel(Transition transition) { + super.onTransitionCancel(transition); + fab.setVisibility(View.GONE); + fabParent.addView(fab); + } + }); + } else { + /* + * Before we transition back we want to remove the fab + * in order to add it again for the TransitionManager to be able to detect the change + */ + fabParent.removeView(fab); + fab.setVisibility(View.VISIBLE); + + TransitionManager.beginDelayedTransition(container, transition); + fabParent.addView(fab); + container.removeView(from); + } + } +} diff --git a/demo/src/main/java/com/bluelinelabs/conductor/demo/changehandler/transitions/FabTransform.java b/demo/src/main/java/com/bluelinelabs/conductor/demo/changehandler/transitions/FabTransform.java new file mode 100644 index 00000000..cf3aee0a --- /dev/null +++ b/demo/src/main/java/com/bluelinelabs/conductor/demo/changehandler/transitions/FabTransform.java @@ -0,0 +1,295 @@ +/* + * Copyright 2016 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Example from https://github.com/nickbutcher/plaid + */ +package com.bluelinelabs.conductor.demo.changehandler.transitions; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.annotation.TargetApi; +import android.graphics.Bitmap; +import android.graphics.Outline; +import android.graphics.Rect; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.support.annotation.ColorInt; +import android.support.annotation.DrawableRes; +import android.support.v4.content.ContextCompat; +import android.transition.Transition; +import android.transition.TransitionValues; +import android.view.View; +import android.view.ViewAnimationUtils; +import android.view.ViewGroup; +import android.view.ViewOutlineProvider; +import android.view.ViewTreeObserver.OnPreDrawListener; +import android.view.animation.Interpolator; + +import com.bluelinelabs.conductor.demo.util.AnimUtils; + +import java.util.ArrayList; +import java.util.List; + +import static android.view.View.MeasureSpec.makeMeasureSpec; + +/** + * A transition between a FAB & another surface using a circular reveal moving along an arc. + *

+ * See: https://www.google.com/design/spec/motion/transforming-material.html#transforming-material-radial-transformation + */ +@TargetApi(Build.VERSION_CODES.LOLLIPOP) +public class FabTransform extends Transition { + + private static final long DEFAULT_DURATION = 240L; + private static final String PROP_BOUNDS = "plaid:fabTransform:bounds"; + private static final String[] TRANSITION_PROPERTIES = { + PROP_BOUNDS + }; + + private final int color; + private final int icon; + + public FabTransform(@ColorInt int fabColor, @DrawableRes int fabIconResId) { + color = fabColor; + icon = fabIconResId; + setPathMotion(new GravityArcMotion()); + setDuration(DEFAULT_DURATION); + } + + @Override + public String[] getTransitionProperties() { + return TRANSITION_PROPERTIES; + } + + @Override + public void captureStartValues(TransitionValues transitionValues) { + captureValues(transitionValues); + } + + @Override + public void captureEndValues(TransitionValues transitionValues) { + captureValues(transitionValues); + } + + @Override + public Animator createAnimator(final ViewGroup sceneRoot, + final TransitionValues startValues, + final TransitionValues endValues) { + if (startValues == null || endValues == null) return null; + + final Rect startBounds = (Rect) startValues.values.get(PROP_BOUNDS); + final Rect endBounds = (Rect) endValues.values.get(PROP_BOUNDS); + + final boolean fromFab = endBounds.width() > startBounds.width(); + final View view = endValues.view; + final Rect dialogBounds = fromFab ? endBounds : startBounds; + final Interpolator fastOutSlowInInterpolator = + AnimUtils.getFastOutSlowInInterpolator(); + final long duration = getDuration(); + final long halfDuration = duration / 2; + final long twoThirdsDuration = duration * 2 / 3; + + if (!fromFab) { + // Force measure / layout the dialog back to it's original bounds + view.measure( + makeMeasureSpec(startBounds.width(), View.MeasureSpec.EXACTLY), + makeMeasureSpec(startBounds.height(), View.MeasureSpec.EXACTLY)); + view.layout(startBounds.left, startBounds.top, startBounds.right, startBounds.bottom); + } + + final int translationX = startBounds.centerX() - endBounds.centerX(); + final int translationY = startBounds.centerY() - endBounds.centerY(); + if (fromFab) { + view.setTranslationX(translationX); + view.setTranslationY(translationY); + } + + // Add a color overlay to fake appearance of the FAB + final ColorDrawable fabColor = new ColorDrawable(color); + fabColor.setBounds(0, 0, dialogBounds.width(), dialogBounds.height()); + if (!fromFab) fabColor.setAlpha(0); + view.getOverlay().add(fabColor); + + // Add an icon overlay again to fake the appearance of the FAB + final Drawable fabIcon = + ContextCompat.getDrawable(sceneRoot.getContext(), icon).mutate(); + final int iconLeft = (dialogBounds.width() - fabIcon.getIntrinsicWidth()) / 2; + final int iconTop = (dialogBounds.height() - fabIcon.getIntrinsicHeight()) / 2; + fabIcon.setBounds(iconLeft, iconTop, + iconLeft + fabIcon.getIntrinsicWidth(), + iconTop + fabIcon.getIntrinsicHeight()); + if (!fromFab) fabIcon.setAlpha(0); + view.getOverlay().add(fabIcon); + + // Since the view that's being transition to always seems to be on the top (z-order), we have + // to make a copy of the "from" view and put it in the "to" view's overlay, then fade it out. + // There has to be another way to do this, right? + Drawable dialogView = null; + if (!fromFab) { + startValues.view.setDrawingCacheEnabled(true); + startValues.view.buildDrawingCache(); + Bitmap viewBitmap = startValues.view.getDrawingCache(); + dialogView = new BitmapDrawable(view.getResources(), viewBitmap); + dialogView.setBounds(0, 0, dialogBounds.width(), dialogBounds.height()); + view.getOverlay().add(dialogView); + } + + // Circular clip from/to the FAB size + final Animator circularReveal; + if (fromFab) { + circularReveal = ViewAnimationUtils.createCircularReveal(view, + view.getWidth() / 2, + view.getHeight() / 2, + startBounds.width() / 2, + (float) Math.hypot(endBounds.width() / 2, endBounds.height() / 2)); + circularReveal.setInterpolator( + AnimUtils.getFastOutLinearInInterpolator()); + } else { + circularReveal = ViewAnimationUtils.createCircularReveal(view, + view.getWidth() / 2, + view.getHeight() / 2, + (float) Math.hypot(startBounds.width() / 2, startBounds.height() / 2), + endBounds.width() / 2); + circularReveal.setInterpolator( + AnimUtils.getLinearOutSlowInInterpolator()); + + // Persist the end clip i.e. stay at FAB size after the reveal has run + circularReveal.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + final ViewOutlineProvider fabOutlineProvider = view.getOutlineProvider(); + + view.setOutlineProvider(new ViewOutlineProvider() { + boolean hasRun = false; + + @Override + public void getOutline(final View view, Outline outline) { + final int left = (view.getWidth() - endBounds.width()) / 2; + final int top = (view.getHeight() - endBounds.height()) / 2; + + outline.setOval( + left, top, left + endBounds.width(), top + endBounds.height()); + + if (!hasRun) { + hasRun = true; + view.setClipToOutline(true); + + // We have to remove this as soon as it's laid out so we can get the shadow back + view.getViewTreeObserver().addOnPreDrawListener(new OnPreDrawListener() { + @Override + public boolean onPreDraw() { + if (view.getWidth() == endBounds.width() && view.getHeight() == endBounds.height()) { + view.setOutlineProvider(fabOutlineProvider); + view.setClipToOutline(false); + view.getViewTreeObserver().removeOnPreDrawListener(this); + return true; + } + + return true; + } + }); + } + } + }); + } + }); + } + circularReveal.setDuration(duration); + + // Translate to end position along an arc + final Animator translate = ObjectAnimator.ofFloat( + view, + View.TRANSLATION_X, + View.TRANSLATION_Y, + fromFab ? getPathMotion().getPath(translationX, translationY, 0, 0) + : getPathMotion().getPath(0, 0, -translationX, -translationY)); + translate.setDuration(duration); + translate.setInterpolator(fastOutSlowInInterpolator); + + // Fade contents of non-FAB view in/out + List fadeContents = null; + if (view instanceof ViewGroup) { + final ViewGroup vg = ((ViewGroup) view); + fadeContents = new ArrayList<>(vg.getChildCount()); + for (int i = vg.getChildCount() - 1; i >= 0; i--) { + final View child = vg.getChildAt(i); + final Animator fade = + ObjectAnimator.ofFloat(child, View.ALPHA, fromFab ? 1f : 0f); + if (fromFab) { + child.setAlpha(0f); + } + fade.setDuration(twoThirdsDuration); + fade.setInterpolator(fastOutSlowInInterpolator); + fadeContents.add(fade); + } + } + + // Fade in/out the fab color & icon overlays + final Animator colorFade = ObjectAnimator.ofInt(fabColor, "alpha", fromFab ? 0 : 255); + final Animator iconFade = ObjectAnimator.ofInt(fabIcon, "alpha", fromFab ? 0 : 255); + if (!fromFab) { + colorFade.setStartDelay(halfDuration); + iconFade.setStartDelay(halfDuration); + } + colorFade.setDuration(halfDuration); + iconFade.setDuration(halfDuration); + colorFade.setInterpolator(fastOutSlowInInterpolator); + iconFade.setInterpolator(fastOutSlowInInterpolator); + + // Run all animations together + final AnimatorSet transition = new AnimatorSet(); + transition.playTogether(circularReveal, translate, colorFade, iconFade); + transition.playTogether(fadeContents); + if (dialogView != null) { + final Animator dialogViewFade = ObjectAnimator.ofInt(dialogView, "alpha", 0).setDuration(twoThirdsDuration); + dialogViewFade.setInterpolator(fastOutSlowInInterpolator); + transition.playTogether(dialogViewFade); + } + transition.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + // Clean up + view.getOverlay().clear(); + + if (!fromFab) { + view.setTranslationX(0); + view.setTranslationY(0); + view.setTranslationZ(0); + + view.measure( + makeMeasureSpec(endBounds.width(), View.MeasureSpec.EXACTLY), + makeMeasureSpec(endBounds.height(), View.MeasureSpec.EXACTLY)); + view.layout(endBounds.left, endBounds.top, endBounds.right, endBounds.bottom); + } + + } + }); + return new AnimUtils.NoPauseAnimator(transition); + } + + private void captureValues(TransitionValues transitionValues) { + final View view = transitionValues.view; + if (view == null || view.getWidth() <= 0 || view.getHeight() <= 0) return; + + transitionValues.values.put(PROP_BOUNDS, new Rect(view.getLeft(), view.getTop(), + view.getRight(), view.getBottom())); + } +} \ No newline at end of file diff --git a/demo/src/main/java/com/bluelinelabs/conductor/demo/changehandler/transitions/GravityArcMotion.java b/demo/src/main/java/com/bluelinelabs/conductor/demo/changehandler/transitions/GravityArcMotion.java new file mode 100644 index 00000000..48bd611b --- /dev/null +++ b/demo/src/main/java/com/bluelinelabs/conductor/demo/changehandler/transitions/GravityArcMotion.java @@ -0,0 +1,217 @@ +/* + * Copyright 2016 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.bluelinelabs.conductor.demo.changehandler.transitions; + +import android.annotation.TargetApi; +import android.content.Context; +import android.graphics.Path; +import android.os.Build; +import android.transition.ArcMotion; +import android.util.AttributeSet; + +/** + * A tweak to {@link ArcMotion} which slightly alters the path calculation. In the real world + * gravity slows upward motion and accelerates downward motion. This class emulates this behavior + * to make motion paths appear more natural. + *

+ * See https://www.google.com/design/spec/motion/movement.html#movement-movement-within-screen-bounds + */ +@TargetApi(Build.VERSION_CODES.LOLLIPOP) +public class GravityArcMotion extends ArcMotion { + + private static final float DEFAULT_MIN_ANGLE_DEGREES = 0; + private static final float DEFAULT_MAX_ANGLE_DEGREES = 70; + private static final float DEFAULT_MAX_TANGENT = (float) + Math.tan(Math.toRadians(DEFAULT_MAX_ANGLE_DEGREES/2)); + + private float mMinimumHorizontalAngle = 0; + private float mMinimumVerticalAngle = 0; + private float mMaximumAngle = DEFAULT_MAX_ANGLE_DEGREES; + private float mMinimumHorizontalTangent = 0; + private float mMinimumVerticalTangent = 0; + private float mMaximumTangent = DEFAULT_MAX_TANGENT; + + public GravityArcMotion() {} + + public GravityArcMotion(Context context, AttributeSet attrs) { + super(context, attrs); + } + + /** + * @inheritDoc + */ + @Override + public void setMinimumHorizontalAngle(float angleInDegrees) { + mMinimumHorizontalAngle = angleInDegrees; + mMinimumHorizontalTangent = toTangent(angleInDegrees); + } + + /** + * @inheritDoc + */ + @Override + public float getMinimumHorizontalAngle() { + return mMinimumHorizontalAngle; + } + + /** + * @inheritDoc + */ + @Override + public void setMinimumVerticalAngle(float angleInDegrees) { + mMinimumVerticalAngle = angleInDegrees; + mMinimumVerticalTangent = toTangent(angleInDegrees); + } + + /** + * @inheritDoc + */ + @Override + public float getMinimumVerticalAngle() { + return mMinimumVerticalAngle; + } + + /** + * @inheritDoc + */ + @Override + public void setMaximumAngle(float angleInDegrees) { + mMaximumAngle = angleInDegrees; + mMaximumTangent = toTangent(angleInDegrees); + } + + /** + * @inheritDoc + */ + @Override + public float getMaximumAngle() { + return mMaximumAngle; + } + + private static float toTangent(float arcInDegrees) { + if (arcInDegrees < 0 || arcInDegrees > 90) { + throw new IllegalArgumentException("Arc must be between 0 and 90 degrees"); + } + return (float) Math.tan(Math.toRadians(arcInDegrees / 2)); + } + + @Override + public Path getPath(float startX, float startY, float endX, float endY) { + // Here's a little ascii art to show how this is calculated: + // c---------- b + // \ / | + // \ d | + // \ / e + // a----f + // This diagram assumes that the horizontal distance is less than the vertical + // distance between The start point (a) and end point (b). + // d is the midpoint between a and b. c is the center point of the circle with + // This path is formed by assuming that start and end points are in + // an arc on a circle. The end point is centered in the circle vertically + // and start is a point on the circle. + + // Triangles bfa and bde form similar right triangles. The control points + // for the cubic Bezier arc path are the midpoints between a and e and e and b. + + Path path = new Path(); + path.moveTo(startX, startY); + + float ex; + float ey; + if (startY == endY) { + ex = (startX + endX) / 2; + ey = startY + mMinimumHorizontalTangent * Math.abs(endX - startX) / 2; + } else if (startX == endX) { + ex = startX + mMinimumVerticalTangent * Math.abs(endY - startY) / 2; + ey = (startY + endY) / 2; + } else { + float deltaX = endX - startX; + + /** + * This is the only change to ArcMotion + */ + float deltaY; + if (endY < startY) { + deltaY = startY - endY; // Y is inverted compared to diagram above. + } else { + deltaY = endY - startY; + } + /** + * End changes + */ + + // hypotenuse squared. + float h2 = deltaX * deltaX + deltaY * deltaY; + + // Midpoint between start and end + float dx = (startX + endX) / 2; + float dy = (startY + endY) / 2; + + // Distance squared between end point and mid point is (1/2 hypotenuse)^2 + float midDist2 = h2 * 0.25f; + + float minimumArcDist2 = 0; + + if (Math.abs(deltaX) < Math.abs(deltaY)) { + // Similar triangles bfa and bde mean that (ab/fb = eb/bd) + // Therefore, eb = ab * bd / fb + // ab = hypotenuse + // bd = hypotenuse/2 + // fb = deltaY + float eDistY = h2 / (2 * deltaY); + ey = endY + eDistY; + ex = endX; + + minimumArcDist2 = midDist2 * mMinimumVerticalTangent + * mMinimumVerticalTangent; + } else { + // Same as above, but flip X & Y + float eDistX = h2 / (2 * deltaX); + ex = endX + eDistX; + ey = endY; + + minimumArcDist2 = midDist2 * mMinimumHorizontalTangent + * mMinimumHorizontalTangent; + } + float arcDistX = dx - ex; + float arcDistY = dy - ey; + float arcDist2 = arcDistX * arcDistX + arcDistY * arcDistY; + + float maximumArcDist2 = midDist2 * mMaximumTangent * mMaximumTangent; + + float newArcDistance2 = 0; + if (arcDist2 < minimumArcDist2) { + newArcDistance2 = minimumArcDist2; + } else if (arcDist2 > maximumArcDist2) { + newArcDistance2 = maximumArcDist2; + } + if (newArcDistance2 != 0) { + float ratio2 = newArcDistance2 / arcDist2; + float ratio = (float) Math.sqrt(ratio2); + ex = dx + (ratio * (ex - dx)); + ey = dy + (ratio * (ey - dy)); + } + } + float controlX1 = (startX + ex) / 2; + float controlY1 = (startY + ey) / 2; + float controlX2 = (ex + endX) / 2; + float controlY2 = (ey + endY) / 2; + path.cubicTo(controlX1, controlY1, controlX2, controlY2, endX, endY); + return path; + } + +} \ No newline at end of file diff --git a/demo/src/main/java/com/bluelinelabs/conductor/demo/controllers/CustomTransitionDemoController.java b/demo/src/main/java/com/bluelinelabs/conductor/demo/controllers/CustomTransitionDemoController.java new file mode 100644 index 00000000..3efe4d62 --- /dev/null +++ b/demo/src/main/java/com/bluelinelabs/conductor/demo/controllers/CustomTransitionDemoController.java @@ -0,0 +1,54 @@ +package com.bluelinelabs.conductor.demo.controllers; + +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import com.bluelinelabs.conductor.RouterTransaction; +import com.bluelinelabs.conductor.demo.R; +import com.bluelinelabs.conductor.demo.changehandler.FabToDialogAnimatorChangeHandler; +import com.bluelinelabs.conductor.demo.changehandler.FabToDialogTransitionChangeHandler; +import com.bluelinelabs.conductor.demo.controllers.base.BaseController; + +import butterknife.BindView; +import butterknife.OnClick; + +import static com.bluelinelabs.conductor.demo.util.CustomTransitionCompatUtil.getTransitionCompat; + +public class CustomTransitionDemoController extends BaseController { + + private static final String KEY_FAB_VISIBILITY = "CustomTransitionDemoController.fabVisibility"; + + @BindView(R.id.fab) View fab; + + @Override + protected View inflateView(@NonNull final LayoutInflater inflater, @NonNull final ViewGroup container) { + return inflater.inflate(R.layout.controller_custom_transition, container, false); + } + + @Override + protected void onSaveViewState(@NonNull View view, @NonNull Bundle outState) { + super.onSaveViewState(view, outState); + outState.putInt(KEY_FAB_VISIBILITY, fab.getVisibility()); + } + + @Override + protected void onRestoreViewState(@NonNull View view, @NonNull Bundle savedViewState) { + super.onRestoreViewState(view, savedViewState); + + //noinspection WrongConstant + fab.setVisibility(savedViewState.getInt(KEY_FAB_VISIBILITY)); + } + + @OnClick(R.id.fab) + public void showDialog() { + getRouter() + .pushController(RouterTransaction.with(new DialogController()) + .pushChangeHandler(getTransitionCompat(new FabToDialogTransitionChangeHandler(), new FabToDialogAnimatorChangeHandler())) + .popChangeHandler(getTransitionCompat(new FabToDialogTransitionChangeHandler(), new FabToDialogAnimatorChangeHandler())) + ); + } + +} diff --git a/demo/src/main/java/com/bluelinelabs/conductor/demo/controllers/DialogController.java b/demo/src/main/java/com/bluelinelabs/conductor/demo/controllers/DialogController.java new file mode 100644 index 00000000..9e6a3144 --- /dev/null +++ b/demo/src/main/java/com/bluelinelabs/conductor/demo/controllers/DialogController.java @@ -0,0 +1,33 @@ +package com.bluelinelabs.conductor.demo.controllers; + + +import android.app.Activity; +import android.support.annotation.NonNull; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import com.bluelinelabs.conductor.demo.R; +import com.bluelinelabs.conductor.demo.controllers.base.BaseController; + +import butterknife.OnClick; + +public class DialogController extends BaseController { + + + @Override + protected View inflateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) { + return inflater.inflate(R.layout.controller_dialog, container, false); + } + + @OnClick({R.id.dismiss, R.id.dialog_window}) + public void dimissDialog() { + final Activity activity = getActivity(); + if (activity != null) getActivity().onBackPressed(); + } + + @Override + public boolean handleBack() { + return super.handleBack(); + } +} diff --git a/demo/src/main/java/com/bluelinelabs/conductor/demo/controllers/HomeController.java b/demo/src/main/java/com/bluelinelabs/conductor/demo/controllers/HomeController.java index 326849d2..ac3cbb1a 100644 --- a/demo/src/main/java/com/bluelinelabs/conductor/demo/controllers/HomeController.java +++ b/demo/src/main/java/com/bluelinelabs/conductor/demo/controllers/HomeController.java @@ -47,6 +47,7 @@ public enum HomeDemoModel { DRAG_DISMISS("Drag Dismiss", R.color.lime_300), RX_LIFECYCLE("Rx Lifecycle", R.color.teal_300), RX_LIFECYCLE_2("Rx Lifecycle 2", R.color.brown_300), + CUSTOM_TRANSITIONS("Custom Transitions", R.color.cyan_300), OVERLAY("Overlay Controller", R.color.purple_300); String title; @@ -182,6 +183,11 @@ void onModelRowClick(HomeDemoModel model) { .pushChangeHandler(new FadeChangeHandler()) .popChangeHandler(new FadeChangeHandler())); break; + case CUSTOM_TRANSITIONS: + getRouter().pushController(RouterTransaction.with(new CustomTransitionDemoController()) + .pushChangeHandler(new FadeChangeHandler()) + .popChangeHandler(new FadeChangeHandler())); + break; case MULTIPLE_CHILD_ROUTERS: getRouter().pushController(RouterTransaction.with(new MultipleChildRouterController()) .pushChangeHandler(new FadeChangeHandler()) diff --git a/demo/src/main/java/com/bluelinelabs/conductor/demo/util/AnimUtils.java b/demo/src/main/java/com/bluelinelabs/conductor/demo/util/AnimUtils.java new file mode 100644 index 00000000..2daa07f4 --- /dev/null +++ b/demo/src/main/java/com/bluelinelabs/conductor/demo/util/AnimUtils.java @@ -0,0 +1,375 @@ +/* + * Copyright 2015 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.bluelinelabs.conductor.demo.util; + +import android.animation.Animator; +import android.animation.TimeInterpolator; +import android.annotation.TargetApi; +import android.os.Build; +import android.support.annotation.NonNull; +import android.support.v4.view.animation.FastOutLinearInInterpolator; +import android.support.v4.view.animation.FastOutSlowInInterpolator; +import android.support.v4.view.animation.LinearOutSlowInInterpolator; +import android.transition.Transition; +import android.util.ArrayMap; +import android.util.FloatProperty; +import android.util.IntProperty; +import android.util.Property; +import android.view.animation.Interpolator; +import android.view.animation.LinearInterpolator; + +import java.util.ArrayList; + +/** + * Utility methods for working with animations. + */ +@TargetApi(Build.VERSION_CODES.LOLLIPOP) +public class AnimUtils { + + private AnimUtils() { } + + private static Interpolator fastOutSlowIn; + private static Interpolator fastOutLinearIn; + private static Interpolator linearOutSlowIn; + private static Interpolator linear; + + @NonNull + public static Interpolator getFastOutSlowInInterpolator() { + if (fastOutSlowIn == null) { + fastOutSlowIn = new FastOutSlowInInterpolator(); + } + return fastOutSlowIn; + } + + @NonNull + public static Interpolator getFastOutLinearInInterpolator() { + if (fastOutLinearIn == null) { + fastOutLinearIn = new FastOutLinearInInterpolator(); + } + return fastOutLinearIn; + } + + @NonNull + public static Interpolator getLinearOutSlowInInterpolator() { + if (linearOutSlowIn == null) { + linearOutSlowIn = new LinearOutSlowInInterpolator(); + } + return linearOutSlowIn; + } + + @NonNull + public static Interpolator getLinearInterpolator() { + if (linear == null) { + linear = new LinearInterpolator(); + } + return linear; + } + + /** + * Linear interpolate between a and b with parameter t. + */ + public static float lerp(float a, float b, float t) { + return a + (b - a) * t; + } + + /** + * A delegate for creating a {@link Property} of int type. + */ + public static abstract class IntProp { + + public final String name; + + public IntProp(String name) { + this.name = name; + } + + public abstract void set(T object, int value); + public abstract int get(T object); + } + + /** + * The animation framework has an optimization for Properties of type + * int but it was only made public in API24, so wrap the impl in our own type + * and conditionally create the appropriate type, delegating the implementation. + */ + public static Property createIntProperty(final IntProp impl) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + return new IntProperty(impl.name) { + @Override + public Integer get(T object) { + return impl.get(object); + } + + @Override + public void setValue(T object, int value) { + impl.set(object, value); + } + }; + } else { + return new Property(Integer.class, impl.name) { + @Override + public Integer get(T object) { + return impl.get(object); + } + + @Override + public void set(T object, Integer value) { + impl.set(object, value); + } + }; + } + } + + /** + * A delegate for creating a {@link Property} of float type. + */ + public static abstract class FloatProp { + + public final String name; + + protected FloatProp(String name) { + this.name = name; + } + + public abstract void set(T object, float value); + public abstract float get(T object); + } + + /** + * The animation framework has an optimization for Properties of type + * float but it was only made public in API24, so wrap the impl in our own type + * and conditionally create the appropriate type, delegating the implementation. + */ + public static Property createFloatProperty(final FloatProp impl) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + return new FloatProperty(impl.name) { + @Override + public Float get(T object) { + return impl.get(object); + } + + @Override + public void setValue(T object, float value) { + impl.set(object, value); + } + }; + } else { + return new Property(Float.class, impl.name) { + @Override + public Float get(T object) { + return impl.get(object); + } + + @Override + public void set(T object, Float value) { + impl.set(object, value); + } + }; + } + } + + /** + * https://halfthought.wordpress.com/2014/11/07/reveal-transition/ + *

+ * Interrupting Activity transitions can yield an OperationNotSupportedException when the + * transition tries to pause the animator. Yikes! We can fix this by wrapping the Animator: + */ + @TargetApi(Build.VERSION_CODES.KITKAT) + public static class NoPauseAnimator extends Animator { + private final Animator mAnimator; + private final ArrayMap mListeners = new ArrayMap<>(); + + public NoPauseAnimator(Animator animator) { + mAnimator = animator; + } + + @Override + public void addListener(AnimatorListener listener) { + AnimatorListener wrapper = new AnimatorListenerWrapper(this, listener); + if (!mListeners.containsKey(listener)) { + mListeners.put(listener, wrapper); + mAnimator.addListener(wrapper); + } + } + + @Override + public void cancel() { + mAnimator.cancel(); + } + + @Override + public void end() { + mAnimator.end(); + } + + @Override + public long getDuration() { + return mAnimator.getDuration(); + } + + @Override + public TimeInterpolator getInterpolator() { + return mAnimator.getInterpolator(); + } + + @Override + public void setInterpolator(TimeInterpolator timeInterpolator) { + mAnimator.setInterpolator(timeInterpolator); + } + + @Override + public ArrayList getListeners() { + return new ArrayList<>(mListeners.keySet()); + } + + @Override + public long getStartDelay() { + return mAnimator.getStartDelay(); + } + + @Override + public void setStartDelay(long delayMS) { + mAnimator.setStartDelay(delayMS); + } + + @Override + public boolean isPaused() { + return mAnimator.isPaused(); + } + + @Override + public boolean isRunning() { + return mAnimator.isRunning(); + } + + @Override + public boolean isStarted() { + return mAnimator.isStarted(); + } + + /* We don't want to override pause or resume methods because we don't want them + * to affect mAnimator. + public void pause(); + + public void resume(); + + public void addPauseListener(AnimatorPauseListener listener); + + public void removePauseListener(AnimatorPauseListener listener); + */ + + @Override + public void removeAllListeners() { + mListeners.clear(); + mAnimator.removeAllListeners(); + } + + @Override + public void removeListener(AnimatorListener listener) { + AnimatorListener wrapper = mListeners.get(listener); + if (wrapper != null) { + mListeners.remove(listener); + mAnimator.removeListener(wrapper); + } + } + + @Override + public Animator setDuration(long durationMS) { + mAnimator.setDuration(durationMS); + return this; + } + + @Override + public void setTarget(Object target) { + mAnimator.setTarget(target); + } + + @Override + public void setupEndValues() { + mAnimator.setupEndValues(); + } + + @Override + public void setupStartValues() { + mAnimator.setupStartValues(); + } + + @Override + public void start() { + mAnimator.start(); + } + } + + public static class AnimatorListenerWrapper implements Animator.AnimatorListener { + private final Animator mAnimator; + private final Animator.AnimatorListener mListener; + + AnimatorListenerWrapper(Animator animator, Animator.AnimatorListener listener) { + mAnimator = animator; + mListener = listener; + } + + @Override + public void onAnimationStart(Animator animator) { + mListener.onAnimationStart(mAnimator); + } + + @Override + public void onAnimationEnd(Animator animator) { + mListener.onAnimationEnd(mAnimator); + } + + @Override + public void onAnimationCancel(Animator animator) { + mListener.onAnimationCancel(mAnimator); + } + + @Override + public void onAnimationRepeat(Animator animator) { + mListener.onAnimationRepeat(mAnimator); + } + } + + @TargetApi(Build.VERSION_CODES.KITKAT) + public static class TransitionListenerWrapper implements Transition.TransitionListener { + @Override + public void onTransitionStart(Transition transition) { + + } + + @Override + public void onTransitionEnd(Transition transition) { + + } + + @Override + public void onTransitionCancel(Transition transition) { + + } + + @Override + public void onTransitionPause(Transition transition) { + + } + + @Override + public void onTransitionResume(Transition transition) { + + } + } + +} \ No newline at end of file diff --git a/demo/src/main/java/com/bluelinelabs/conductor/demo/util/CustomTransitionCompatUtil.java b/demo/src/main/java/com/bluelinelabs/conductor/demo/util/CustomTransitionCompatUtil.java new file mode 100644 index 00000000..c104ab1a --- /dev/null +++ b/demo/src/main/java/com/bluelinelabs/conductor/demo/util/CustomTransitionCompatUtil.java @@ -0,0 +1,21 @@ +package com.bluelinelabs.conductor.demo.util; + + +import android.os.Build; +import android.support.annotation.NonNull; + +import com.bluelinelabs.conductor.ControllerChangeHandler; + +public final class CustomTransitionCompatUtil { + + private CustomTransitionCompatUtil() { + } + + public static ControllerChangeHandler getTransitionCompat(@NonNull final ControllerChangeHandler transitionChangeHandler, @NonNull final ControllerChangeHandler animatorChangeHandler) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + return transitionChangeHandler; + } else { + return animatorChangeHandler; + } + } +} diff --git a/demo/src/main/res/animator/raise.xml b/demo/src/main/res/animator/raise.xml new file mode 100644 index 00000000..265f8431 --- /dev/null +++ b/demo/src/main/res/animator/raise.xml @@ -0,0 +1,17 @@ + + + + + + + + + \ No newline at end of file diff --git a/demo/src/main/res/drawable/dialog_bg.xml b/demo/src/main/res/drawable/dialog_bg.xml new file mode 100644 index 00000000..00c9b0c1 --- /dev/null +++ b/demo/src/main/res/drawable/dialog_bg.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/demo/src/main/res/drawable/ic_add_dark.xml b/demo/src/main/res/drawable/ic_add_dark.xml new file mode 100644 index 00000000..4cde97f2 --- /dev/null +++ b/demo/src/main/res/drawable/ic_add_dark.xml @@ -0,0 +1,12 @@ + + + + + \ No newline at end of file diff --git a/demo/src/main/res/layout/controller_custom_transition.xml b/demo/src/main/res/layout/controller_custom_transition.xml new file mode 100644 index 00000000..5de6ec4a --- /dev/null +++ b/demo/src/main/res/layout/controller_custom_transition.xml @@ -0,0 +1,23 @@ + + + + + + \ No newline at end of file diff --git a/demo/src/main/res/layout/controller_dialog.xml b/demo/src/main/res/layout/controller_dialog.xml new file mode 100644 index 00000000..d4a7fdd8 --- /dev/null +++ b/demo/src/main/res/layout/controller_dialog.xml @@ -0,0 +1,58 @@ + + + + + + + + + +