Compare commits

..

13 Commits

Author SHA1 Message Date
Eric Kuck 48dc4abcbe Version bump 2016-12-13 17:09:19 -06:00
Eric Kuck 4a814afb5f Fixes #166 2016-12-13 16:56:22 -06:00
Eric Kuck 9cd225e704 Controllers now throw an exception when the user forgets to pass false for LayoutInflater.inflate's attachToRoot parameter. 2016-12-12 14:31:22 -06:00
Eric Kuck c8640af1ac Remove saveState option for RouterPagerAdapters, as users can configure the page before display anyway. 2016-12-12 13:25:57 -06:00
Eric Kuck 43c825f7c2 - Child backstack is now properly restored when Android kills the process
- Added a RouterPagerAdapter, which allows the use of Routers as pages
2016-12-12 13:09:29 -06:00
Eric Kuck 7334ed5300 ControllerPagerAdapter updates to enable using a per-page router if needed. 2016-12-12 12:24:26 -06:00
Eric Kuck 7ea4872ff8 Updated ordering of calls in backstack to be in line with other backstack-affecting calls 2016-12-08 13:25:11 -06:00
Eric Kuck 9655170bd2 Fixes tests 2016-12-07 16:34:38 -06:00
Eric Kuck acce9b1702 Simplified setRoot implementation 2016-12-07 16:17:32 -06:00
Eric Kuck 95baa8baa3 Fixes #172 2016-12-01 18:00:06 -06:00
Eric Kuck 2388fa2d06 Added a demo for the new RxLifecycle2Controller 2016-12-01 17:56:43 -06:00
Vishnu Rajeevan 285eb59da0 add rxlifecycle2 support, fixes #148 (#171) 2016-12-01 17:31:52 -06:00
Eric Kuck ae42ee1674 Attempted fix for #165 2016-12-01 17:28:15 -06:00
23 changed files with 705 additions and 93 deletions
+6 -6
View File
@@ -20,22 +20,22 @@ Conductor is architecture-agnostic and does not try to force any design decision
## Installation
```gradle
compile 'com.bluelinelabs:conductor:2.0.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:
+1 -1
View File
@@ -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'
}
}
+35
View File
@@ -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'
+3
View File
@@ -0,0 +1,3 @@
POM_NAME=Conductor RxLifecycle2 Extensions
POM_ARTIFACT_ID=conductor-rxlifecycle2
POM_PACKAGING=aar
@@ -0,0 +1,4 @@
<manifest package="com.bluelinelabs.conductor.rxlifecycle2">
<application />
</manifest>
@@ -0,0 +1,12 @@
package com.bluelinelabs.conductor.rxlifecycle2;
public enum ControllerEvent {
CREATE,
CREATE_VIEW,
ATTACH,
DETACH,
DESTROY_VIEW,
DESTROY
}
@@ -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<ControllerEvent> create(Controller controller){
final BehaviorSubject<ControllerEvent> 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;
}
}
@@ -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<ControllerEvent> {
private final BehaviorSubject<ControllerEvent> lifecycleSubject;
public RxController(){
this(null);
}
public RxController(@Nullable Bundle args) {
super(args);
lifecycleSubject = ControllerLifecycleSubjectHelper.create(this);
}
@Override
@NonNull
@CheckResult
public final Observable<ControllerEvent> lifecycle() {
return lifecycleSubject.hide();
}
@Override
@NonNull
@CheckResult
public final <T> LifecycleTransformer<T> bindUntilEvent(@NonNull ControllerEvent event) {
return RxLifecycle.bindUntilEvent(lifecycleSubject, event);
}
@Override
@NonNull
@CheckResult
public final <T> LifecycleTransformer<T> bindToLifecycle() {
return RxControllerLifecycle.bindController(lifecycleSubject);
}
}
@@ -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 <T> LifecycleTransformer<T> bindController(@NonNull final Observable<ControllerEvent> lifecycle) {
return RxLifecycle.bind(lifecycle, CONTROLLER_LIFECYCLE);
}
private static final Function<ControllerEvent, ControllerEvent> CONTROLLER_LIFECYCLE =
new Function<ControllerEvent, ControllerEvent>() {
@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.");
}
}
};
}
@@ -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<ControllerEvent> {
private final BehaviorSubject<ControllerEvent> lifecycleSubject;
public RxRestoreViewOnCreateController() {
this(null);
}
public RxRestoreViewOnCreateController(Bundle args) {
super(args);
lifecycleSubject = ControllerLifecycleSubjectHelper.create(this);
}
@Override
@NonNull
@CheckResult
public final Observable<ControllerEvent> lifecycle() {
return lifecycleSubject.hide();
}
@Override
@NonNull
@CheckResult
public final <T> LifecycleTransformer<T> bindUntilEvent(@NonNull ControllerEvent event) {
return RxLifecycle.bindUntilEvent(lifecycleSubject, event);
}
@Override
@NonNull
@CheckResult
public final <T> LifecycleTransformer<T> bindToLifecycle() {
return RxControllerLifecycle.bindController(lifecycleSubject);
}
}
@@ -2,6 +2,7 @@ package com.bluelinelabs.conductor.support;
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<Bundle> savedPages = new SparseArray<>();
private SparseArray<String> visiblePageIds = new SparseArray<>();
/**
* Creates a new ControllerPagerAdapter using the passed host.
@@ -50,8 +54,9 @@ public abstract class ControllerPagerAdapter extends PagerAdapter {
}
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();
}
@@ -69,6 +74,8 @@ public abstract class ControllerPagerAdapter extends PagerAdapter {
savedPages.put(position, savedState);
}
visiblePageIds.remove(position);
host.removeChildRouter(router);
}
@@ -82,6 +89,16 @@ public abstract class ControllerPagerAdapter extends PagerAdapter {
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;
}
@@ -91,6 +108,26 @@ public abstract class ControllerPagerAdapter extends PagerAdapter {
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;
}
}
@@ -0,0 +1,119 @@
package com.bluelinelabs.conductor.support;
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;
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 final Controller host;
private SparseArray<Bundle> savedPages = new SparseArray<>();
private SparseArray<Router> visibleRouters = new SparseArray<>();
/**
* Creates a new RouterPagerAdapter using the passed host.
*/
public RouterPagerAdapter(Controller host) {
this.host = host;
}
/**
* 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 (!router.hasRootController()) {
Bundle routerSavedState = savedPages.get(position);
if (routerSavedState != null) {
router.restoreInstanceState(routerSavedState);
}
}
router.rebindIfNeeded();
configureRouter(router, position);
visibleRouters.put(position, router);
return router;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
Router router = (Router)object;
Bundle savedState = new Bundle();
router.saveInstanceState(savedState);
savedPages.put(position, savedState);
host.removeChildRouter(router);
visibleRouters.remove(position);
}
@Override
public boolean isViewFromObject(View view, Object object) {
Router router = (Router)object;
final List<RouterTransaction> 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.putSparseParcelableArray(KEY_SAVED_PAGES, savedPages);
return bundle;
}
@Override
public void restoreState(Parcelable state, ClassLoader loader) {
Bundle bundle = (Bundle)state;
if (state != null) {
savedPages = bundle.getSparseParcelableArray(KEY_SAVED_PAGES);
}
}
/**
* 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;
}
private static String makeRouterName(int viewId, long id) {
return viewId + ":" + id;
}
}
@@ -15,37 +15,37 @@ class Backstack implements Iterable<RouterTransaction> {
private static final String KEY_ENTRIES = "Backstack.entries";
private final Deque<RouterTransaction> backStack = new ArrayDeque<>();
private final Deque<RouterTransaction> 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<RouterTransaction> iterator() {
return backStack.iterator();
return backstack.iterator();
}
@NonNull
public Iterator<RouterTransaction> reverseIterator() {
return backStack.descendingIterator();
return backstack.descendingIterator();
}
@NonNull
public List<RouterTransaction> popTo(@NonNull RouterTransaction transaction) {
List<RouterTransaction> 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 @@ class Backstack implements Iterable<RouterTransaction> {
@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 @@ class Backstack implements Iterable<RouterTransaction> {
}
public void setBackstack(@NonNull List<RouterTransaction> 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 @@ class Backstack implements Iterable<RouterTransaction> {
}
}
backStack.clear();
this.backstack.clear();
for (RouterTransaction transaction : backstack) {
backStack.push(transaction);
this.backstack.push(transaction);
}
}
public void saveInstanceState(@NonNull Bundle outState) {
ArrayList<Bundle> entryBundles = new ArrayList<>(backStack.size());
for (RouterTransaction entry : backStack) {
ArrayList<Bundle> entryBundles = new ArrayList<>(backstack.size());
for (RouterTransaction entry : backstack) {
entryBundles.add(entry.saveInstanceState());
}
@@ -119,7 +119,7 @@ class Backstack implements Iterable<RouterTransaction> {
if (entryBundles != null) {
Collections.reverse(entryBundles);
for (Bundle transactionBundle : entryBundles) {
backStack.push(new RouterTransaction(transactionBundle));
backstack.push(new RouterTransaction(transactionBundle));
}
}
}
@@ -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";
@@ -130,7 +131,7 @@ public abstract class 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 +159,7 @@ public abstract class Controller {
/**
* Returns any arguments that were set in this Controller's constructor
*/
@Nullable
@NonNull
public Bundle getArgs() {
return args;
}
@@ -171,7 +172,6 @@ public abstract class Controller {
*/
@NonNull
public final Router getChildRouter(@NonNull ViewGroup container) {
//noinspection deprecation
return getChildRouter(container, null);
}
@@ -186,6 +186,21 @@ public abstract class Controller {
*/
@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;
@@ -197,10 +212,12 @@ public abstract class Controller {
}
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);
@@ -865,6 +882,9 @@ public abstract class Controller {
}
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) {
@@ -895,11 +915,27 @@ public abstract class Controller {
};
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<LifecycleListener> listeners = new ArrayList<>(lifecycleListeners);
@@ -966,17 +1002,7 @@ public abstract class Controller {
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<LifecycleListener> listeners = new ArrayList<>(lifecycleListeners);
for (LifecycleListener lifecycleListener : listeners) {
@@ -1015,6 +1041,12 @@ public abstract class Controller {
}
outState.putParcelableArrayList(KEY_CHILD_ROUTERS, childBundles);
ArrayList<String> childBackstack = new ArrayList<>();
for (Controller controller : this.childBackstack) {
childBackstack.add(controller.instanceId);
}
outState.putStringArrayList(KEY_CHILD_BACKSTACK, childBackstack);
Bundle savedState = new Bundle();
onSaveInstanceState(savedState);
@@ -1046,6 +1078,14 @@ public abstract class Controller {
childRouters.add(childRouter);
}
List<String> 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();
}
@@ -275,29 +275,9 @@ public abstract class Router {
*/
@UiThread
public void setRoot(@NonNull RouterTransaction transaction) {
ControllerChangeHandler newHandler = transaction.pushChangeHandler() != null ? transaction.pushChangeHandler() : new SimpleSwapChangeHandler();
List<RouterTransaction> 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<RouterTransaction> transactions = new ArrayList<>();
transactions.add(transaction);
setBackstack(transactions, transaction.pushChangeHandler());
}
/**
@@ -364,6 +344,12 @@ public abstract class Router {
public void setBackstack(@NonNull List<RouterTransaction> newBackstack, @Nullable ControllerChangeHandler changeHandler) {
List<RouterTransaction> oldVisibleTransactions = getVisibleTransactions(backstack.iterator());
backstack.setBackstack(newBackstack);
for (RouterTransaction transaction : backstack) {
transaction.onAttachedToRouter();
}
removeAllExceptVisibleAndUnowned();
if (newBackstack.size() > 0) {
@@ -385,7 +371,7 @@ public abstract class Router {
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);
@@ -402,12 +388,6 @@ public abstract class Router {
}
}
for (RouterTransaction transaction : backstack) {
transaction.onAttachedToRouter();
}
backstack.setBackstack(newBackstack);
if (onControllerPushedListener != null) {
for (RouterTransaction transaction : newBackstack) {
onControllerPushedListener.onControllerPushed(transaction.controller);
+5
View File
@@ -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
@@ -46,6 +46,7 @@ public class HomeController extends BaseController {
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 @@ public class HomeController extends BaseController {
.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())
@@ -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.<Long>bindUntilEvent(ControllerEvent.DESTROY))
.subscribe(new Consumer<Long>() {
@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.<Long>bindUntilEvent(ControllerEvent.DESTROY_VIEW))
.subscribe(new Consumer<Long>() {
@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.<Long>bindUntilEvent(ControllerEvent.DETACH))
.subscribe(new Consumer<Long>() {
@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()));
}
}
@@ -7,30 +7,38 @@ 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.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 class RxLifecycleController extends BaseController {
});
}
@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 class RxLifecycleController extends BaseController {
Log.i(TAG, "Started in onCreateView(), running until onDestroyView(): " + num);
}
});
return view;
}
@Override
@@ -75,6 +89,8 @@ public class RxLifecycleController extends BaseController {
Log.i(TAG, "onAttach() called");
(((ActionBarProvider)getActivity()).getSupportActionBar()).setTitle("RxLifecycle Demo");
Observable.interval(1, TimeUnit.SECONDS)
.doOnUnsubscribe(new Action0() {
@Override
@@ -96,6 +112,9 @@ public class RxLifecycleController extends BaseController {
super.onDestroyView(view);
Log.i(TAG, "onDestroyView() called");
unbinder.unbind();
unbinder = null;
}
@Override
@@ -110,17 +129,20 @@ public class RxLifecycleController extends BaseController {
super.onDestroy();
Log.i(TAG, "onDestroy() called");
if (hasExited) {
DemoApplication.refWatcher.watch(this);
}
}
@Override
protected String getTitle() {
return "RxLifecycle Demo";
}
protected void onChangeEnded(@NonNull ControllerChangeHandler changeHandler, @NonNull ControllerChangeType changeType) {
super.onChangeEnded(changeHandler, changeType);
@NonNull
@Override
protected View inflateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) {
return inflater.inflate(R.layout.controller_rxlifecycle, container, false);
hasExited = !changeType.isEnter;
if (isDestroyed()) {
DemoApplication.refWatcher.watch(this);
}
}
@OnClick(R.id.btn_next_release_view) void onNextWithReleaseClicked() {
@@ -6,12 +6,12 @@ import android.view.LayoutInflater;
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;
+6
View File
@@ -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'
+1 -1
View File
@@ -1,4 +1,4 @@
VERSION_NAME=2.0.5-SNAPSHOT
VERSION_NAME=2.0.6-SNAPSHOT
VERSION_CODE=2
GROUP=com.bluelinelabs
+5 -4
View File
@@ -1,5 +1,6 @@
include ':conductor'
include':conductor-support'
include':conductor-rxlifecycle'
include':conductor-lint'
include':demo'
include ':conductor-support'
include ':conductor-rxlifecycle'
include ':conductor-rxlifecycle2'
include ':conductor-lint'
include ':demo'