Compare commits

...

12 Commits

27 changed files with 644 additions and 200 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.2'
compile 'com.bluelinelabs:conductor:2.0.3'
// If you want the components that go along with
// Android's support libraries (currently just a PagerAdapter):
compile 'com.bluelinelabs:conductor-support:2.0.2'
compile 'com.bluelinelabs:conductor-support:2.0.3'
// If you want RxJava/RxAndroid lifecycle support:
compile 'com.bluelinelabs:conductor-rxlifecycle:2.0.2'
compile 'com.bluelinelabs:conductor-rxlifecycle:2.0.3'
```
SNAPSHOT:
```gradle
compile 'com.bluelinelabs:conductor:2.0.3-SNAPSHOT'
compile 'com.bluelinelabs:conductor-support:2.0.3-SNAPSHOT'
compile 'com.bluelinelabs:conductor-rxlifecycle:2.0.3-SNAPSHOT'
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'
```
You also have to add the url to the snapshot repository:
+1
View File
@@ -28,6 +28,7 @@ dependencies {
compile rootProject.ext.rxJava
compile rootProject.ext.rxAndroid
compile rootProject.ext.rxLifecycle
compile rootProject.ext.rxLifecycleAndroid
compile project(':conductor')
}
@@ -1,38 +0,0 @@
package com.bluelinelabs.conductor.rxlifecycle;
import android.support.annotation.CheckResult;
import android.support.annotation.NonNull;
import rx.Observable;
/**
* Interface used for RxController. Can also be used if writing your own Controller component without subclassing RxController.
*/
public interface ControllerLifecycleProvider {
/**
* @return An observable that will have all {@link com.bluelinelabs.conductor.Controller} lifecycle events
*/
@NonNull
@CheckResult
Observable<ControllerEvent> lifecycle();
/**
* Will bind the source until a specific {@link ControllerEvent} occurs.
*
* @param event The {@link ControllerEvent} that should cause onComplete to be called
* @return A {@link rx.Observable.Transformer} that will call onComplete when the event occurs.
*/
@NonNull
@CheckResult
<T> Observable.Transformer<T, T> bindUntilEvent(@NonNull ControllerEvent event);
/**
* Will bind the source until the next reasonable {@link ControllerEvent} occurs.
* @return A {@link rx.Observable.Transformer} that will call onComplete when the event occurs.
*/
@NonNull
@CheckResult
<T> Observable.Transformer<T, T> bindToLifecycle();
}
@@ -5,6 +5,8 @@ import android.support.annotation.CheckResult;
import android.support.annotation.NonNull;
import com.bluelinelabs.conductor.Controller;
import com.trello.rxlifecycle.LifecycleProvider;
import com.trello.rxlifecycle.LifecycleTransformer;
import com.trello.rxlifecycle.RxLifecycle;
import rx.Observable;
@@ -13,7 +15,7 @@ import rx.subjects.BehaviorSubject;
/**
* A base {@link Controller} that can be used to expose lifecycle events using RxJava
*/
public abstract class RxController extends Controller implements ControllerLifecycleProvider {
public abstract class RxController extends Controller implements LifecycleProvider<ControllerEvent> {
private final BehaviorSubject<ControllerEvent> lifecycleSubject;
@@ -36,14 +38,14 @@ public abstract class RxController extends Controller implements ControllerLifec
@Override
@NonNull
@CheckResult
public final <T> Observable.Transformer<T, T> bindUntilEvent(@NonNull ControllerEvent event) {
public final <T> LifecycleTransformer<T> bindUntilEvent(@NonNull ControllerEvent event) {
return RxLifecycle.bindUntilEvent(lifecycleSubject, event);
}
@Override
@NonNull
@CheckResult
public final <T> Observable.Transformer<T, T> bindToLifecycle() {
public final <T> LifecycleTransformer<T> bindToLifecycle() {
return RxControllerLifecycle.bindController(lifecycleSubject);
}
@@ -14,7 +14,7 @@ public class RxControllerLifecycle {
/**
* Binds the given source to a Controller lifecycle. This is the Controller version of
* {@link com.trello.rxlifecycle.RxLifecycle#bindFragment(Observable)}.
* {@link com.trello.rxlifecycle.android.RxLifecycleAndroid#bindFragment(Observable)}.
*
* @param lifecycle the lifecycle sequence of a Controller
* @return a reusable {@link Observable.Transformer} that unsubscribes the source during the Controller lifecycle
@@ -59,7 +59,7 @@ public abstract class Controller {
private final Bundle args;
private Bundle viewState;
Bundle viewState;
private Bundle savedInstanceState;
private boolean isBeingDestroyed;
private boolean destroyed;
@@ -647,6 +647,10 @@ public abstract class Controller {
return false;
}
final void setNeedsAttach() {
needsAttach = true;
}
final void prepareForHostDetach() {
needsAttach = needsAttach || attached;
@@ -4,6 +4,7 @@ import android.os.Bundle;
import android.support.annotation.NonNull;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import com.bluelinelabs.conductor.changehandler.SimpleSwapChangeHandler;
import com.bluelinelabs.conductor.internal.ClassUtils;
@@ -25,6 +26,8 @@ public abstract class ControllerChangeHandler {
private static final Map<String, ControllerChangeHandler> inProgressPushHandlers = new HashMap<>();
private boolean forceRemoveViewOnPush;
/**
* Responsible for swapping Views from one Controller to another.
*
@@ -80,6 +83,10 @@ public abstract class ControllerChangeHandler {
return bundle;
}
final ControllerChangeHandler copy() {
return fromBundle(toBundle());
}
private void ensureDefaultConstructor() {
try {
getClass().getConstructor();
@@ -100,11 +107,14 @@ public abstract class ControllerChangeHandler {
}
}
public static void completePushImmediately(String controllerInstanceId) {
static boolean completePushImmediately(String controllerInstanceId) {
ControllerChangeHandler changeHandler = inProgressPushHandlers.get(controllerInstanceId);
if (changeHandler != null) {
changeHandler.completeImmediately();
inProgressPushHandlers.remove(controllerInstanceId);
return true;
}
return false;
}
public static void abortPush(Controller toAbort, Controller newController, ControllerChangeHandler newChangeHandler) {
@@ -167,6 +177,13 @@ public abstract class ControllerChangeHandler {
for (ControllerChangeListener listener : listeners) {
listener.onChangeCompleted(to, from, isPush, container, inHandler);
}
if (handler.forceRemoveViewOnPush && fromView != null) {
ViewParent fromParent = fromView.getParent();
if (fromParent != null && fromParent instanceof ViewGroup) {
((ViewGroup)fromParent).removeView(fromView);
}
}
}
});
}
@@ -176,6 +193,10 @@ public abstract class ControllerChangeHandler {
return true;
}
public void setForceRemoveViewOnPush(boolean force) {
forceRemoveViewOnPush = force;
}
/**
* A listener interface useful for allowing external classes to be notified of change events.
*/
@@ -47,7 +47,8 @@ class ControllerHostedRouter extends Router {
removeChangeListener((ControllerChangeListener)container);
}
for (Controller controller : destroyingControllers) {
final List<Controller> controllersToDestroy = new ArrayList<>(destroyingControllers);
for (Controller controller : controllersToDestroy) {
if (controller.getView() != null) {
controller.detach(controller.getView(), true);
}
@@ -0,0 +1,38 @@
package com.bluelinelabs.conductor;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
/**
* A simple controller subclass that changes the onCreateView signature to include a saved view state parameter.
* This is necessary for some third party libraries like Google Maps, which require passing in a saved state
* bundle at the time of creation.
*/
abstract public class RestoreViewOnCreateController extends Controller {
@NonNull
@Override
protected final View onCreateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) {
return onCreateView(inflater, container, viewState);
}
/**
* Called when the controller is ready to display its view. A valid view must be returned. The standard body
* for this method will be {@code return inflater.inflate(R.layout.my_layout, container, false);}, plus
* any binding and state restoration code.
*
* @param inflater The LayoutInflater that should be used to inflate views
* @param container The parent view that this Controller's view will eventually be attached to.
* 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.
*/
@NonNull
protected abstract View onCreateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container, @Nullable Bundle savedViewState);
}
@@ -152,8 +152,21 @@ public abstract class Router {
trackDestroyingController(backstack.pop());
}
final ControllerChangeHandler handler = transaction.pushChangeHandler();
final boolean oldHandlerRemovedViews = topTransaction.pushChangeHandler() == null || topTransaction.pushChangeHandler().removesFromViewOnPush();
final boolean newHandlerRemovesViews = handler == null || handler.removesFromViewOnPush();
if (!oldHandlerRemovedViews && newHandlerRemovesViews) {
for (RouterTransaction visibleTransaction : getVisibleTransactions(backstack.iterator())) {
performControllerChange(null, visibleTransaction.controller, true, handler != null ? handler.copy() : new SimpleSwapChangeHandler());
}
}
pushToBackstack(transaction);
performControllerChange(transaction, topTransaction, true);
if (handler != null) {
handler.setForceRemoveViewOnPush(true);
}
performControllerChange(transaction.pushChangeHandler(handler), topTransaction, true);
}
void destroy() {
@@ -319,7 +332,7 @@ public abstract class Router {
* using the passed {@link ControllerChangeHandler}
*
* @param newBackstack The new backstack
* @param changeHandler An optional change handler to be used to handle the transition
* @param changeHandler An optional change handler to be used to handle the root view of transition
*/
public void setBackstack(@NonNull List<RouterTransaction> newBackstack, ControllerChangeHandler changeHandler) {
List<RouterTransaction> oldVisibleTransactions = getVisibleTransactions(backstack.iterator());
@@ -345,17 +358,19 @@ 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);
performControllerChange(newVisibleTransactions.get(0).controller, rootController, true, handler.copy());
for (int i = oldVisibleTransactions.size() - 1; i > 0; i--) {
RouterTransaction transaction = oldVisibleTransactions.get(i);
performControllerChange(null, transaction.controller, true, handler);
ControllerChangeHandler localHandler = handler.copy();
localHandler.setForceRemoveViewOnPush(true);
performControllerChange(null, transaction.controller, true, localHandler);
}
for (int i = 1; i < newVisibleTransactions.size(); i++) {
RouterTransaction transaction = newVisibleTransactions.get(i);
handler = transaction.pushChangeHandler() != null ? transaction.pushChangeHandler(): new SimpleSwapChangeHandler();
performControllerChange(transaction.controller, newVisibleTransactions.get(i - 1).controller, true, handler);
handler = transaction.pushChangeHandler() != null ? transaction.pushChangeHandler().copy() : new SimpleSwapChangeHandler();
performControllerChange(transaction.controller, newVisibleTransactions.get(i - 1).controller, true, handler.copy());
}
}
}
@@ -481,7 +496,9 @@ public abstract class Router {
public void prepareForHostDetach() {
for (RouterTransaction transaction : backstack) {
ControllerChangeHandler.completePushImmediately(transaction.controller.getInstanceId());
if (ControllerChangeHandler.completePushImmediately(transaction.controller.getInstanceId())) {
transaction.controller.setNeedsAttach();
}
transaction.controller.prepareForHostDetach();
}
}
@@ -615,7 +632,7 @@ public abstract class Router {
ControllerChangeHandler.executeChange(to, from, isPush, container, changeHandler, changeListeners);
}
void pushToBackstack(@NonNull RouterTransaction entry) {
private void pushToBackstack(@NonNull RouterTransaction entry) {
backstack.push(entry);
}
@@ -162,18 +162,20 @@ public abstract class AnimatorChangeHandler extends ControllerChangeHandler {
@Override
public void onAnimationEnd(Animator animation) {
if (from != null && (!isPush || removesFromViewOnPush) && !canceled) {
container.removeView(from);
if (!canceled && animator != null) {
if (from != null && (!isPush || removesFromViewOnPush)) {
container.removeView(from);
}
changeListener.onChangeCompleted();
animator.removeListener(this);
if (isPush && from != null) {
resetFromView(from);
}
animator = null;
}
changeListener.onChangeCompleted();
animator.removeListener(this);
if (isPush && from != null) {
resetFromView(from);
}
animator = null;
}
});
@@ -3,6 +3,7 @@ package com.bluelinelabs.conductor.changehandler;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.view.View;
import android.view.View.OnAttachStateChangeListener;
import android.view.ViewGroup;
import com.bluelinelabs.conductor.Controller;
@@ -11,7 +12,7 @@ import com.bluelinelabs.conductor.ControllerChangeHandler;
/**
* A {@link ControllerChangeHandler} that will instantly swap Views with no animations or transitions.
*/
public class SimpleSwapChangeHandler extends ControllerChangeHandler {
public class SimpleSwapChangeHandler extends ControllerChangeHandler implements OnAttachStateChangeListener {
private static final String KEY_REMOVES_FROM_ON_PUSH = "SimpleSwapChangeHandler.removesFromViewOnPush";
@@ -19,6 +20,9 @@ public class SimpleSwapChangeHandler extends ControllerChangeHandler {
private boolean canceled;
private ViewGroup container;
private ControllerChangeCompletedListener changeListener;
public SimpleSwapChangeHandler() {
this(true);
}
@@ -47,7 +51,18 @@ public class SimpleSwapChangeHandler extends ControllerChangeHandler {
}
@Override
public void performChange(@NonNull ViewGroup container, View from, View to, boolean isPush, @NonNull final ControllerChangeCompletedListener changeListener) {
public void completeImmediately() {
if (changeListener != null) {
changeListener.onChangeCompleted();
changeListener = null;
container.removeOnAttachStateChangeListener(this);
container = null;
}
}
@Override
public void performChange(@NonNull ViewGroup container, View from, View to, boolean isPush, @NonNull ControllerChangeCompletedListener changeListener) {
if (!canceled) {
if (from != null && (!isPush || removesFromViewOnPush)) {
container.removeView(from);
@@ -58,11 +73,32 @@ public class SimpleSwapChangeHandler extends ControllerChangeHandler {
}
}
changeListener.onChangeCompleted();
if (container.getWindowToken() != null) {
changeListener.onChangeCompleted();
} else {
this.changeListener = changeListener;
this.container = container;
container.addOnAttachStateChangeListener(this);
}
}
@Override
public boolean removesFromViewOnPush() {
return removesFromViewOnPush;
}
@Override
public void onViewAttachedToWindow(View v) {
v.removeOnAttachStateChangeListener(this);
if (changeListener != null) {
changeListener.onChangeCompleted();
changeListener = null;
container = null;
}
}
@Override
public void onViewDetachedFromWindow(View v) { }
}
@@ -0,0 +1,72 @@
package com.bluelinelabs.conductor;
import android.os.Bundle;
import android.support.annotation.IdRes;
import org.robolectric.Robolectric;
import org.robolectric.util.ActivityController;
public class ActivityProxy {
private ActivityController<TestActivity> activityController;
private AttachFakingFrameLayout view;
public ActivityProxy() {
activityController = Robolectric.buildActivity(TestActivity.class);
@IdRes int containerId = 4;
view = new AttachFakingFrameLayout(activityController.get());
view.setId(containerId);
}
public ActivityProxy create(Bundle savedInstanceState) {
activityController.create(savedInstanceState);
return this;
}
public ActivityProxy start() {
activityController.start();
view.setAttached(true);
return this;
}
public ActivityProxy resume() {
activityController.resume();
return this;
}
public ActivityProxy pause() {
activityController.pause();
return this;
}
public ActivityProxy saveInstanceState(Bundle outState) {
activityController.saveInstanceState(outState);
return this;
}
public ActivityProxy stop() {
activityController.stop();
view.setAttached(false);
return this;
}
public ActivityProxy destroy() {
activityController.destroy();
return this;
}
public ActivityProxy rotate() {
getActivity().isChangingConfigurations = true;
getActivity().recreate();
return this;
}
public TestActivity getActivity() {
return activityController.get();
}
public AttachFakingFrameLayout getView() {
return view;
}
}
@@ -0,0 +1,113 @@
package com.bluelinelabs.conductor;
import android.content.Context;
import android.os.IBinder;
import android.os.IInterface;
import android.os.Parcel;
import android.os.RemoteException;
import android.util.AttributeSet;
import android.view.View;
import android.widget.FrameLayout;
import java.io.FileDescriptor;
public class AttachFakingFrameLayout extends FrameLayout {
final IBinder fakeWindowToken = new IBinder() {
@Override
public String getInterfaceDescriptor() throws RemoteException {
return null;
}
@Override
public boolean pingBinder() {
return false;
}
@Override
public boolean isBinderAlive() {
return false;
}
@Override
public IInterface queryLocalInterface(String descriptor) {
return null;
}
@Override
public void dump(FileDescriptor fd, String[] args) throws RemoteException {
}
@Override
public void dumpAsync(FileDescriptor fd, String[] args) throws RemoteException {
}
@Override
public boolean transact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
return false;
}
@Override
public void linkToDeath(DeathRecipient recipient, int flags) throws RemoteException {
}
@Override
public boolean unlinkToDeath(DeathRecipient recipient, int flags) {
return false;
}
};
private boolean reportAttached;
public AttachFakingFrameLayout(Context context) {
super(context);
}
public AttachFakingFrameLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public AttachFakingFrameLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public final IBinder getWindowToken() {
return reportAttached ? fakeWindowToken : null;
}
public void setAttached(boolean attached) {
setAttached(attached, true);
}
public void setAttached(boolean attached, boolean reportToViewUtils) {
if (reportAttached != attached) {
reportAttached = attached;
if (reportToViewUtils) {
ViewUtils.reportAttached(this, attached);
}
for (int i = 0; i < getChildCount(); i++) {
ViewUtils.reportAttached(getChildAt(i), attached);
}
}
}
@Override
public void onViewAdded(View child) {
if (reportAttached) {
ViewUtils.reportAttached(child, true);
}
super.onViewAdded(child);
}
@Override
public void onViewRemoved(View child) {
ViewUtils.reportAttached(child, false);
super.onViewRemoved(child);
}
}
@@ -119,9 +119,9 @@ public class CallState implements Parcelable {
"\n restoreInstanceStateCalls=" + restoreInstanceStateCalls +
"\n saveViewStateCalls=" + saveViewStateCalls +
"\n restoreViewStateCalls=" + restoreViewStateCalls +
"\n onActivityResultCalls= " + onActivityResultCalls +
"\n onRequestPermissionsResultCalls= " + onRequestPermissionsResultCalls +
"\n createOptionsMenuCalls= " + createOptionsMenuCalls +
"\n onActivityResultCalls=" + onActivityResultCalls +
"\n onRequestPermissionsResultCalls=" + onRequestPermissionsResultCalls +
"\n createOptionsMenuCalls=" + createOptionsMenuCalls +
"}\n";
}
@@ -1,11 +1,9 @@
package com.bluelinelabs.conductor;
import android.os.Bundle;
import android.support.annotation.IdRes;
import android.support.annotation.NonNull;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import com.bluelinelabs.conductor.Controller.LifecycleListener;
import com.bluelinelabs.conductor.MockChangeHandler.ChangeHandlerListener;
@@ -14,28 +12,26 @@ import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.Robolectric;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import org.robolectric.util.ActivityController;
@RunWith(RobolectricTestRunner.class)
@Config(manifest = Config.NONE)
public class ControllerLifecycleTests {
private ActivityController<TestActivity> activityController;
private Router router;
private ActivityProxy activityProxy;
private CallState currentCallState;
public void createActivityController(Bundle savedInstanceState) {
activityController = Robolectric.buildActivity(TestActivity.class).create(savedInstanceState).start();
public void createActivityController(Bundle savedInstanceState, boolean includeStartAndResume) {
activityProxy = new ActivityProxy().create(savedInstanceState);
@IdRes int containerId = 4;
FrameLayout routerContainer = new FrameLayout(activityController.get());
routerContainer.setId(containerId);
if (includeStartAndResume) {
activityProxy.start().resume();
}
router = Conductor.attachRouter(activityController.get(), routerContainer, savedInstanceState);
router = Conductor.attachRouter(activityProxy.getActivity(), activityProxy.getView(), savedInstanceState);
if (!router.hasRootController()) {
router.setRoot(RouterTransaction.with(new TestController()));
}
@@ -43,7 +39,7 @@ public class ControllerLifecycleTests {
@Before
public void setup() {
createActivityController(null);
createActivityController(null, true);
currentCallState = new CallState();
}
@@ -82,18 +78,20 @@ public class ControllerLifecycleTests {
assertCalls(expectedCallState, controller);
activityController.pause();
activityProxy.getActivity().isDestroying = true;
activityProxy.pause();
assertCalls(expectedCallState, controller);
activityController.stop();
assertCalls(expectedCallState, controller);
activityController.destroy();
activityProxy.stop();
expectedCallState.saveViewStateCalls++;
expectedCallState.detachCalls++;
expectedCallState.destroyViewCalls++;
assertCalls(expectedCallState, controller);
activityProxy.destroy();
expectedCallState.destroyCalls++;
assertCalls(expectedCallState, controller);
}
@@ -112,33 +110,32 @@ public class ControllerLifecycleTests {
assertCalls(expectedCallState, controller);
activityController.get().isChangingConfigurations = true;
activityProxy.getActivity().isChangingConfigurations = true;
Bundle bundle = new Bundle();
activityController.saveInstanceState(bundle);
activityProxy.saveInstanceState(bundle);
expectedCallState.saveViewStateCalls++;
expectedCallState.saveInstanceStateCalls++;
assertCalls(expectedCallState, controller);
activityController.pause();
activityProxy.pause();
assertCalls(expectedCallState, controller);
activityController.stop();
assertCalls(expectedCallState, controller);
activityController.destroy();
activityProxy.stop();
expectedCallState.detachCalls++;
expectedCallState.destroyViewCalls++;
assertCalls(expectedCallState, controller);
createActivityController(bundle);
activityProxy.destroy();
assertCalls(expectedCallState, controller);
createActivityController(bundle, false);
controller = (TestController)router.getControllerWithTag("root");
expectedCallState.restoreInstanceStateCalls++;
expectedCallState.restoreViewStateCalls++;
expectedCallState.changeStartCalls++;
expectedCallState.changeEndCalls++;
expectedCallState.createViewCalls++;
// Lifecycle listener isn't attached during restore, grab the current views from the controller for this stuff...
@@ -147,10 +144,19 @@ public class ControllerLifecycleTests {
currentCallState.changeStartCalls = controller.currentCallState.changeStartCalls;
currentCallState.changeEndCalls = controller.currentCallState.changeEndCalls;
currentCallState.createViewCalls = controller.currentCallState.createViewCalls;
currentCallState.attachCalls = controller.currentCallState.attachCalls;
assertCalls(expectedCallState, controller);
activityController.resume();
activityProxy.start().resume();
currentCallState.changeEndCalls = controller.currentCallState.changeEndCalls;
currentCallState.attachCalls = controller.currentCallState.attachCalls;
expectedCallState.changeEndCalls++;
expectedCallState.attachCalls++;
assertCalls(expectedCallState, controller);
activityProxy.resume();
assertCalls(expectedCallState, controller);
}
@@ -167,16 +173,16 @@ public class ControllerLifecycleTests {
assertCalls(expectedCallState, controller);
activityController.pause();
activityProxy.pause();
Bundle bundle = new Bundle();
activityController.saveInstanceState(bundle);
activityProxy.saveInstanceState(bundle);
expectedCallState.saveInstanceStateCalls++;
expectedCallState.saveViewStateCalls++;
assertCalls(expectedCallState, controller);
activityController.resume();
activityProxy.resume();
}
@Test
@@ -3,10 +3,8 @@ package com.bluelinelabs.conductor;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.IdRes;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import com.bluelinelabs.conductor.Controller.RetainViewMode;
@@ -14,26 +12,19 @@ import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.Robolectric;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import org.robolectric.util.ActivityController;
@RunWith(RobolectricTestRunner.class)
@Config(manifest = Config.NONE)
public class ControllerTests {
private ActivityController<TestActivity> activityController;
private ActivityProxy activityProxy;
private Router router;
public void createActivityController(Bundle savedInstanceState) {
activityController = Robolectric.buildActivity(TestActivity.class).create(savedInstanceState).start();
@IdRes int containerId = 4;
FrameLayout routerContainer = new FrameLayout(activityController.get());
routerContainer.setId(containerId);
router = Conductor.attachRouter(activityController.get(), routerContainer, savedInstanceState);
activityProxy = new ActivityProxy().create(savedInstanceState).start().resume();
router = Conductor.attachRouter(activityProxy.getActivity(), activityProxy.getView(), savedInstanceState);
if (!router.hasRootController()) {
router.setRoot(RouterTransaction.with(new TestController()));
}
@@ -51,20 +42,20 @@ public class ControllerTests {
// Test View getting released w/ RELEASE_DETACH
controller.setRetainViewMode(RetainViewMode.RELEASE_DETACH);
Assert.assertNull(controller.getView());
View view = controller.inflate(new FrameLayout(router.getActivity()));
View view = controller.inflate(new AttachFakingFrameLayout(router.getActivity()));
Assert.assertNotNull(controller.getView());
ViewUtils.setAttached(view, true);
ViewUtils.reportAttached(view, true);
Assert.assertNotNull(controller.getView());
ViewUtils.setAttached(view, false);
ViewUtils.reportAttached(view, false);
Assert.assertNull(controller.getView());
// Test View getting retained w/ RETAIN_DETACH
controller.setRetainViewMode(RetainViewMode.RETAIN_DETACH);
view = controller.inflate(new FrameLayout(router.getActivity()));
view = controller.inflate(new AttachFakingFrameLayout(router.getActivity()));
Assert.assertNotNull(controller.getView());
ViewUtils.setAttached(view, true);
ViewUtils.reportAttached(view, true);
Assert.assertNotNull(controller.getView());
ViewUtils.setAttached(view, false);
ViewUtils.reportAttached(view, false);
Assert.assertNotNull(controller.getView());
// Ensure re-setting RELEASE_DETACH releases
@@ -78,7 +69,6 @@ public class ControllerTests {
CallState expectedCallState = new CallState(true);
router.pushController(RouterTransaction.with(controller));
ViewUtils.setAttached(controller.getView(), true);
// Ensure that calling onActivityResult w/o requesting a result doesn't do anything
router.onActivityResult(1, Activity.RESULT_OK, null);
@@ -103,10 +93,8 @@ public class ControllerTests {
TestController child = new TestController();
router.pushController(RouterTransaction.with(parent));
ViewUtils.setAttached(parent.getView(), true);
parent.getChildRouter((ViewGroup)parent.getView().findViewById(TestController.VIEW_ID), null)
.setRoot(RouterTransaction.with(child));
ViewUtils.setAttached(child.getView(), true);
CallState childExpectedCallState = new CallState(true);
CallState parentExpectedCallState = new CallState(true);
@@ -139,7 +127,6 @@ public class ControllerTests {
CallState expectedCallState = new CallState(true);
router.pushController(RouterTransaction.with(controller));
ViewUtils.setAttached(controller.getView(), true);
// Ensure that calling handleRequestedPermission w/o requesting a result doesn't do anything
router.onRequestPermissionsResult("anotherId", 1, requestedPermissions, new int[] {1});
@@ -163,10 +150,8 @@ public class ControllerTests {
TestController child = new TestController();
router.pushController(RouterTransaction.with(parent));
ViewUtils.setAttached(parent.getView(), true);
parent.getChildRouter((ViewGroup)parent.getView().findViewById(TestController.VIEW_ID), null)
.setRoot(RouterTransaction.with(child));
ViewUtils.setAttached(child.getView(), true);
CallState childExpectedCallState = new CallState(true);
CallState parentExpectedCallState = new CallState(true);
@@ -193,7 +178,6 @@ public class ControllerTests {
CallState expectedCallState = new CallState(true);
router.pushController(RouterTransaction.with(controller));
ViewUtils.setAttached(controller.getView(), true);
// Ensure that calling onCreateOptionsMenu w/o declaring that we have one doesn't do anything
router.onCreateOptionsMenu(null, null);
@@ -230,10 +214,8 @@ public class ControllerTests {
TestController child = new TestController();
router.pushController(RouterTransaction.with(parent));
ViewUtils.setAttached(parent.getView(), true);
parent.getChildRouter((ViewGroup)parent.getView().findViewById(TestController.VIEW_ID), null)
.setRoot(RouterTransaction.with(child));
ViewUtils.setAttached(child.getView(), true);
CallState childExpectedCallState = new CallState(true);
CallState parentExpectedCallState = new CallState(true);
@@ -285,6 +267,7 @@ public class ControllerTests {
Assert.assertNull(child2.getParentController());
Router childRouter = parent.getChildRouter((ViewGroup)parent.getView().findViewById(TestController.VIEW_ID), null);
childRouter.setPopsLastView(true);
childRouter.setRoot(RouterTransaction.with(child1));
Assert.assertEquals(1, parent.getChildRouters().size());
@@ -1,12 +1,15 @@
package com.bluelinelabs.conductor;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.view.View;
import android.view.ViewGroup;
public class MockChangeHandler extends ControllerChangeHandler {
private static final String KEY_REMOVES_FROM_VIEW_ON_PUSH = "MockChangeHandler.removesFromViewOnPush";
static class ChangeHandlerListener {
void willStartChange() { }
void didAttachOrDetach() { }
@@ -14,13 +17,28 @@ public class MockChangeHandler extends ControllerChangeHandler {
}
final ChangeHandlerListener listener;
boolean removesFromViewOnPush;
public MockChangeHandler() {
this(new ChangeHandlerListener() { });
this(true, null);
}
public MockChangeHandler(boolean removesViewOnPush) {
this(removesViewOnPush, null);
}
public MockChangeHandler(@NonNull ChangeHandlerListener listener) {
this.listener = listener;
this(true, listener);
}
public MockChangeHandler(boolean removesFromViewOnPush, ChangeHandlerListener listener) {
this.removesFromViewOnPush = removesFromViewOnPush;
if (listener == null) {
this.listener = new ChangeHandlerListener() { };
} else {
this.listener = listener;
}
}
@Override
@@ -29,23 +47,17 @@ public class MockChangeHandler extends ControllerChangeHandler {
if (isPush) {
container.addView(to);
ViewUtils.setAttached(to, true);
listener.didAttachOrDetach();
if (from != null) {
if (removesFromViewOnPush && from != null) {
container.removeView(from);
ViewUtils.setAttached(from, false);
}
} else {
container.removeView(from);
ViewUtils.setAttached(from, false);
listener.didAttachOrDetach();
if (to != null) {
container.addView(to);
ViewUtils.setAttached(to, true);
}
}
@@ -54,4 +66,20 @@ public class MockChangeHandler extends ControllerChangeHandler {
listener.didEndChange();
}
@Override
public boolean removesFromViewOnPush() {
return removesFromViewOnPush;
}
@Override
public void saveToBundle(@NonNull Bundle bundle) {
super.saveToBundle(bundle);
bundle.putBoolean(KEY_REMOVES_FROM_VIEW_ON_PUSH, removesFromViewOnPush);
}
@Override
public void restoreFromBundle(@NonNull Bundle bundle) {
super.restoreFromBundle(bundle);
removesFromViewOnPush = bundle.getBoolean(KEY_REMOVES_FROM_VIEW_ON_PUSH);
}
}
@@ -1,34 +1,25 @@
package com.bluelinelabs.conductor;
import android.os.Bundle;
import android.support.annotation.IdRes;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.Robolectric;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import org.robolectric.util.ActivityController;
@RunWith(RobolectricTestRunner.class)
@Config(manifest = Config.NONE)
public class ReattachCaseTests {
private ActivityController<TestActivity> activityController;
private ActivityProxy activityProxy;
private Router router;
public void createActivityController(Bundle savedInstanceState) {
activityController = Robolectric.buildActivity(TestActivity.class).create(savedInstanceState).start();
@IdRes int containerId = 4;
FrameLayout routerContainer = new FrameLayout(activityController.get());
routerContainer.setId(containerId);
router = Conductor.attachRouter(activityController.get(), routerContainer, savedInstanceState);
activityProxy = new ActivityProxy().create(savedInstanceState).start().resume();
router = Conductor.attachRouter(activityProxy.getActivity(), activityProxy.getView(), savedInstanceState);
if (!router.hasRootController()) {
router.setRoot(RouterTransaction.with(new TestController()));
}
@@ -63,7 +54,8 @@ public class ReattachCaseTests {
Assert.assertFalse(controllerA.isAttached());
Assert.assertTrue(controllerB.isAttached());
rotateDevice();
activityProxy.rotate();
router.rebindIfNeeded();
Assert.assertFalse(controllerA.isAttached());
Assert.assertTrue(controllerB.isAttached());
@@ -102,7 +94,8 @@ public class ReattachCaseTests {
Assert.assertFalse(childController.isAttached());
Assert.assertTrue(controllerB.isAttached());
rotateDevice();
activityProxy.rotate();
router.rebindIfNeeded();
Assert.assertFalse(controllerA.isAttached());
Assert.assertFalse(childController.isAttached());
@@ -138,7 +131,8 @@ public class ReattachCaseTests {
Assert.assertTrue(controllerB.isAttached());
Assert.assertTrue(childController.isAttached());
rotateDevice();
activityProxy.rotate();
router.rebindIfNeeded();
Assert.assertFalse(controllerA.isAttached());
Assert.assertTrue(controllerB.isAttached());
@@ -201,7 +195,8 @@ public class ReattachCaseTests {
Assert.assertTrue(controllerB.isAttached());
Assert.assertTrue(childController.isAttached());
rotateDevice();
activityProxy.rotate();
router.rebindIfNeeded();
Assert.assertFalse(controllerA.isAttached());
Assert.assertTrue(controllerB.isAttached());
@@ -232,18 +227,8 @@ public class ReattachCaseTests {
}
private void sleepWakeDevice() {
activityController.saveInstanceState(new Bundle()).pause();
activityController.resume();
}
private void rotateDevice() {
@IdRes int containerId = 4;
FrameLayout routerContainer = new FrameLayout(activityController.get());
routerContainer.setId(containerId);
activityController.get().isChangingConfigurations = true;
activityController.get().recreate();
router.rebindIfNeeded();
activityProxy.saveInstanceState(new Bundle()).pause();
activityProxy.resume();
}
}
@@ -1,16 +1,15 @@
package com.bluelinelabs.conductor;
import android.app.Activity;
import android.widget.FrameLayout;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.Robolectric;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import java.util.ArrayList;
import java.util.List;
@RunWith(RobolectricTestRunner.class)
@Config(manifest = Config.NONE)
public class RouterTests {
@@ -19,8 +18,8 @@ public class RouterTests {
@Before
public void setup() {
Activity activity = Robolectric.buildActivity(TestActivity.class).create().get();
router = Conductor.attachRouter(activity, new FrameLayout(activity), null);
ActivityProxy activityProxy = new ActivityProxy().create(null).start().resume();
router = Conductor.attachRouter(activityProxy.getActivity(), activityProxy.getView(), null);
}
@Test
@@ -174,4 +173,149 @@ public class RouterTests {
Assert.assertEquals(controller3, router.getControllerWithTag(controller3Tag));
}
@Test
public void testSetBackstack() {
RouterTransaction rootTransaction = RouterTransaction.with(new TestController());
RouterTransaction middleTransaction = RouterTransaction.with(new TestController());
RouterTransaction topTransaction = RouterTransaction.with(new TestController());
List<RouterTransaction> backstack = new ArrayList<>();
backstack.add(rootTransaction);
backstack.add(middleTransaction);
backstack.add(topTransaction);
router.setBackstack(backstack, null);
Assert.assertEquals(3, router.getBackstackSize());
List<RouterTransaction> fetchedBackstack = router.getBackstack();
Assert.assertEquals(rootTransaction, fetchedBackstack.get(0));
Assert.assertEquals(middleTransaction, fetchedBackstack.get(1));
Assert.assertEquals(topTransaction, fetchedBackstack.get(2));
}
@Test
public void testNewSetBackstack() {
router.setRoot(RouterTransaction.with(new TestController()));
Assert.assertEquals(1, router.getBackstackSize());
RouterTransaction rootTransaction = RouterTransaction.with(new TestController());
RouterTransaction middleTransaction = RouterTransaction.with(new TestController());
RouterTransaction topTransaction = RouterTransaction.with(new TestController());
List<RouterTransaction> backstack = new ArrayList<>();
backstack.add(rootTransaction);
backstack.add(middleTransaction);
backstack.add(topTransaction);
router.setBackstack(backstack, null);
Assert.assertEquals(3, router.getBackstackSize());
List<RouterTransaction> fetchedBackstack = router.getBackstack();
Assert.assertEquals(rootTransaction, fetchedBackstack.get(0));
Assert.assertEquals(middleTransaction, fetchedBackstack.get(1));
Assert.assertEquals(topTransaction, fetchedBackstack.get(2));
}
@Test
public void testNewSetBackstackWithNoRemoveViewOnPush() {
RouterTransaction oldRootTransaction = RouterTransaction.with(new TestController());
RouterTransaction oldTopTransaction = RouterTransaction.with(new TestController()).pushChangeHandler(new MockChangeHandler(false));
router.setRoot(oldRootTransaction);
router.pushController(oldTopTransaction);
Assert.assertEquals(2, router.getBackstackSize());
Assert.assertTrue(oldRootTransaction.controller.isAttached());
Assert.assertTrue(oldTopTransaction.controller.isAttached());
RouterTransaction rootTransaction = RouterTransaction.with(new TestController());
RouterTransaction middleTransaction = RouterTransaction.with(new TestController()).pushChangeHandler(new MockChangeHandler(false));
RouterTransaction topTransaction = RouterTransaction.with(new TestController()).pushChangeHandler(new MockChangeHandler(false));
List<RouterTransaction> backstack = new ArrayList<>();
backstack.add(rootTransaction);
backstack.add(middleTransaction);
backstack.add(topTransaction);
router.setBackstack(backstack, null);
Assert.assertEquals(3, router.getBackstackSize());
List<RouterTransaction> fetchedBackstack = router.getBackstack();
Assert.assertEquals(rootTransaction, fetchedBackstack.get(0));
Assert.assertEquals(middleTransaction, fetchedBackstack.get(1));
Assert.assertEquals(topTransaction, fetchedBackstack.get(2));
Assert.assertFalse(oldRootTransaction.controller.isAttached());
Assert.assertFalse(oldTopTransaction.controller.isAttached());
Assert.assertTrue(rootTransaction.controller.isAttached());
Assert.assertTrue(middleTransaction.controller.isAttached());
Assert.assertTrue(topTransaction.controller.isAttached());
}
@Test
public void testReplaceTopController() {
RouterTransaction rootTransaction = RouterTransaction.with(new TestController());
RouterTransaction topTransaction = RouterTransaction.with(new TestController());
List<RouterTransaction> backstack = new ArrayList<>();
backstack.add(rootTransaction);
backstack.add(topTransaction);
router.setBackstack(backstack, null);
Assert.assertEquals(2, router.getBackstackSize());
List<RouterTransaction> fetchedBackstack = router.getBackstack();
Assert.assertEquals(rootTransaction, fetchedBackstack.get(0));
Assert.assertEquals(topTransaction, fetchedBackstack.get(1));
RouterTransaction newTopTransaction = RouterTransaction.with(new TestController());
router.replaceTopController(newTopTransaction);
Assert.assertEquals(2, router.getBackstackSize());
fetchedBackstack = router.getBackstack();
Assert.assertEquals(rootTransaction, fetchedBackstack.get(0));
Assert.assertEquals(newTopTransaction, fetchedBackstack.get(1));
}
@Test
public void testReplaceTopControllerWithNoRemoveViewOnPush() {
RouterTransaction rootTransaction = RouterTransaction.with(new TestController());
RouterTransaction topTransaction = RouterTransaction.with(new TestController()).pushChangeHandler(new MockChangeHandler(false));
List<RouterTransaction> backstack = new ArrayList<>();
backstack.add(rootTransaction);
backstack.add(topTransaction);
router.setBackstack(backstack, null);
Assert.assertEquals(2, router.getBackstackSize());
Assert.assertTrue(rootTransaction.controller.isAttached());
Assert.assertTrue(topTransaction.controller.isAttached());
List<RouterTransaction> fetchedBackstack = router.getBackstack();
Assert.assertEquals(rootTransaction, fetchedBackstack.get(0));
Assert.assertEquals(topTransaction, fetchedBackstack.get(1));
RouterTransaction newTopTransaction = RouterTransaction.with(new TestController()).pushChangeHandler(new MockChangeHandler(false));
router.replaceTopController(newTopTransaction);
newTopTransaction.pushChangeHandler().completeImmediately();
Assert.assertEquals(2, router.getBackstackSize());
fetchedBackstack = router.getBackstack();
Assert.assertEquals(rootTransaction, fetchedBackstack.get(0));
Assert.assertEquals(newTopTransaction, fetchedBackstack.get(1));
Assert.assertTrue(rootTransaction.controller.isAttached());
Assert.assertFalse(topTransaction.controller.isAttached());
Assert.assertTrue(newTopTransaction.controller.isAttached());
}
}
@@ -1,34 +1,24 @@
package com.bluelinelabs.conductor;
import android.os.Bundle;
import android.support.annotation.IdRes;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.Robolectric;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import org.robolectric.util.ActivityController;
@RunWith(RobolectricTestRunner.class)
@Config(manifest = Config.NONE)
public class TargetControllerTests {
private ActivityController<TestActivity> activityController;
private Router router;
public void createActivityController(Bundle savedInstanceState) {
activityController = Robolectric.buildActivity(TestActivity.class).create(savedInstanceState).start();
@IdRes int containerId = 4;
FrameLayout routerContainer = new FrameLayout(activityController.get());
routerContainer.setId(containerId);
router = Conductor.attachRouter(activityController.get(), routerContainer, savedInstanceState);
ActivityProxy activityProxy = new ActivityProxy().create(savedInstanceState).start().resume();
router = Conductor.attachRouter(activityProxy.getActivity(), activityProxy.getView(), savedInstanceState);
if (!router.hasRootController()) {
router.setRoot(RouterTransaction.with(new TestController()));
}
@@ -5,10 +5,15 @@ import android.app.Activity;
public class TestActivity extends Activity {
public boolean isChangingConfigurations = false;
public boolean isDestroying = false;
@Override
public boolean isChangingConfigurations() {
return isChangingConfigurations;
}
@Override
public boolean isDestroyed() {
return isDestroying || super.isDestroyed();
}
}
@@ -29,7 +29,7 @@ public class TestController extends Controller {
@Override
protected View onCreateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) {
currentCallState.createViewCalls++;
FrameLayout view = new FrameLayout(inflater.getContext());
FrameLayout view = new AttachFakingFrameLayout(inflater.getContext());
view.setId(VIEW_ID);
FrameLayout childContainer1 = new FrameLayout(inflater.getContext());
@@ -9,9 +9,26 @@ import java.util.List;
public class ViewUtils {
public static void setAttached(View view, boolean attached) {
Object listenerInfo = ReflectionHelpers.callInstanceMethod(view, "getListenerInfo");
List<OnAttachStateChangeListener> listeners = ReflectionHelpers.getField(listenerInfo, "mOnAttachStateChangeListeners");
public static void reportAttached(View view, boolean attached) {
if (view instanceof AttachFakingFrameLayout) {
((AttachFakingFrameLayout)view).setAttached(attached, false);
}
List<OnAttachStateChangeListener> listeners = getAttachStateListeners(view);
// Add, then remove an OnAttachStateChangeListener to initialize the attachStateListeners variable inside a view
if (listeners == null) {
OnAttachStateChangeListener tmpListener = new OnAttachStateChangeListener() {
@Override
public void onViewAttachedToWindow(View v) { }
@Override
public void onViewDetachedFromWindow(View v) { }
};
view.addOnAttachStateChangeListener(tmpListener);
view.removeOnAttachStateChangeListener(tmpListener);
listeners = getAttachStateListeners(view);
}
for (OnAttachStateChangeListener listener : listeners) {
if (attached) {
@@ -20,6 +37,12 @@ public class ViewUtils {
listener.onViewDetachedFromWindow(view);
}
}
}
private static List<OnAttachStateChangeListener> getAttachStateListeners(View view) {
Object listenerInfo = ReflectionHelpers.callInstanceMethod(view, "getListenerInfo");
return ReflectionHelpers.getField(listenerInfo, "mOnAttachStateChangeListeners");
}
}
@@ -16,10 +16,13 @@ import com.bluelinelabs.conductor.demo.R;
import com.bluelinelabs.conductor.demo.controllers.base.BaseController;
import com.bluelinelabs.conductor.demo.util.ColorUtil;
import java.util.List;
public class ParentController extends BaseController {
private static final int NUMBER_OF_CHILDREN = 5;
private boolean finishing;
private boolean hasShownAll;
@NonNull
@Override
@@ -47,8 +50,12 @@ public class ParentController extends BaseController {
childController.addLifecycleListener(new LifecycleListener() {
@Override
public void onChangeEnd(@NonNull Controller controller, @NonNull ControllerChangeHandler changeHandler, @NonNull ControllerChangeType changeType) {
if (changeType == ControllerChangeType.PUSH_ENTER && index < NUMBER_OF_CHILDREN - 1) {
addChild(index + 1);
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);
@@ -66,7 +73,10 @@ public class ParentController extends BaseController {
}
private void removeChild(int index) {
removeChildRouter(getChildRouters().get(index));
List<Router> childRouters = getChildRouters();
if (index < childRouters.size()) {
removeChildRouter(childRouters.get(index));
}
}
@Override
+4 -3
View File
@@ -17,9 +17,10 @@ ext {
leakCanary = 'com.squareup.leakcanary:leakcanary-android:1.4'
leakCanaryNoOp = 'com.squareup.leakcanary:leakcanary-android-no-op:1.4'
rxJava = 'io.reactivex:rxjava:1.1.0'
rxAndroid = 'io.reactivex:rxandroid:1.1.0'
rxLifecycle = 'com.trello:rxlifecycle:0.6.0'
rxJava = 'io.reactivex:rxjava:1.2.0'
rxAndroid = 'io.reactivex:rxandroid:1.2.1'
rxLifecycle = 'com.trello:rxlifecycle:0.8.0'
rxLifecycleAndroid = 'com.trello:rxlifecycle-android:0.8.0'
junit = 'junit:junit:4.11'
roboelectric = 'org.robolectric:robolectric:3.0'
+1 -1
View File
@@ -1,4 +1,4 @@
VERSION_NAME=2.0.3-SNAPSHOT
VERSION_NAME=2.0.4-SNAPSHOT
VERSION_CODE=2
GROUP=com.bluelinelabs