Compare commits

..

15 Commits

Author SHA1 Message Date
Eric Kuck 2ce8c0a45d Version bump 2017-01-18 13:00:24 -06:00
Eric Kuck 77ad6b4512 Now sets ClassLoader for view state Bundle - potential fix for #198 2017-01-18 12:18:06 -06:00
Eric Kuck efbdf913bd Fixes #201 2017-01-18 11:54:50 -06:00
Eric Kuck dfb01389f7 Fixes #199 2017-01-17 16:45:56 -06:00
Eric Kuck e390261b53 Fixes #197 2017-01-15 17:33:40 -06:00
Eric Kuck 27f5275172 Now throws an exception when a destroyed controller is pushed in order to make it more obvious that controllers can not be reused. 2016-12-26 15:25:16 -06:00
Eric Kuck d15f2b68ab Added missing files from e7c195d910 2016-12-16 11:35:45 -06:00
Eric Kuck 44ed19858b Fixes #185 2016-12-15 15:22:26 -06:00
Eric Kuck e7c195d910 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
2016-12-15 15:11:25 -06:00
Eric Kuck 23a4dbbb60 Travis yml fix 2016-12-14 20:20:04 -06:00
Eric Kuck 0ac81767dd Travis.yml licenses fix 2016-12-14 20:05:38 -06:00
Eric Kuck 54cdc51557 Added license to travis.yml 2016-12-14 19:48:55 -06:00
Eric Kuck 09ce640d9c Fixes #183 2016-12-14 19:42:10 -06:00
Eric Kuck 01df673a34 Switched lint checks to the new PSI based api (closes #184) 2016-12-14 19:41:41 -06:00
Eric Kuck 553bae0be5 Updated readme to include mention of RxLifecycle2 2016-12-14 17:53:45 -06:00
42 changed files with 802 additions and 271 deletions
+4 -2
View File
@@ -3,9 +3,11 @@ language: android
android:
components:
- tools
- build-tools-23.0.2
- android-23
- build-tools-24.0.3
- android-25
- extra-android-m2repository
licenses:
- '.+'
script:
- ./gradlew test
+11 -7
View File
@@ -20,22 +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/RxAndroid lifecycle support:
compile 'com.bluelinelabs:conductor-rxlifecycle:2.0.5'
// If you want RxJava lifecycle support:
compile 'com.bluelinelabs:conductor-rxlifecycle:2.0.6'
// If you want RxJava2 lifecycle support:
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: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:
+1 -5
View File
@@ -10,12 +10,8 @@ buildscript {
allprojects {
repositories {
jcenter()
mavenLocal()
mavenCentral()
}
}
task wrapper(type: Wrapper) {
gradleVersion = '2.10'
}
apply from: rootProject.file('dependencies.gradle')
+6
View File
@@ -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)
}
@@ -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.Issue;
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<String> 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<VariableDefinition, ConstructorDeclaration> 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);
}
}
}
@@ -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.Issue;
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<String> 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;
PsiMethod[] constructors = declaration.getConstructors();
for (PsiMethod constructor : constructors) {
if (evaluator.isPublic(constructor)) {
PsiParameter[] parameters = constructor.getParameterList().getParameters();
if (constructor.astModifiers().isPublic()) {
StrictListAccessor<VariableDefinition, ConstructorDeclaration> 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;
}
}
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);
}
}
}
@@ -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<Issue> getIssues() {
return Collections.singletonList(ControllerChangeHandlerIssueDetector.ISSUE);
}
@Override
protected boolean allowCompilationErrors() {
return true;
}
}
@@ -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<Issue> getIssues() {
return Collections.singletonList(ControllerIssueDetector.ISSUE);
}
@Override
protected boolean allowCompilationErrors() {
return true;
}
}
-5
View File
@@ -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
-4
View File
@@ -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
-4
View File
@@ -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
-4
View File
@@ -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
@@ -18,12 +18,13 @@ import android.view.Menu;
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<ControllerHostedRouter> childRouters = new ArrayList<>();
private final List<LifecycleListener> lifecycleListeners = new ArrayList<>();
private final ArrayList<String> requestedPermissions = new ArrayList<>();
@@ -229,7 +230,7 @@ public abstract class Controller {
public final void removeChildRouter(@NonNull Router childRouter) {
if ((childRouter instanceof ControllerHostedRouter) && childRouters.remove(childRouter)) {
childRouter.destroy();
childRouter.destroy(true);
}
}
@@ -751,6 +752,7 @@ public abstract class Controller {
attach(view);
} else if (attached) {
needsAttach = false;
hasSavedViewState = false;
}
onActivityResumed(activity);
@@ -846,7 +848,7 @@ public abstract class Controller {
onDestroyView(view);
view.removeOnAttachStateChangeListener(onAttachStateChangeListener);
viewAttachHandler.unregisterAttachListener(view);
viewIsAttached = false;
if (isBeingDestroyed) {
@@ -893,9 +895,9 @@ public abstract class Controller {
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;
@@ -904,7 +906,7 @@ public abstract class Controller {
}
@Override
public void onViewDetachedFromWindow(View v) {
public void onDetached(View v) {
viewIsAttached = false;
viewWasDetached = true;
@@ -912,9 +914,8 @@ public abstract class Controller {
detach(v, false);
}
}
};
view.addOnAttachStateChangeListener(onAttachStateChangeListener);
});
viewAttachHandler.listenForAttach(view);
} else if (retainViewMode == RetainViewMode.RETAIN_DETACH) {
restoreChildControllerHosts();
}
@@ -968,7 +969,7 @@ public abstract class Controller {
}
for (ControllerHostedRouter childRouter : childRouters) {
childRouter.destroy();
childRouter.destroy(false);
}
if (!attached) {
@@ -981,7 +982,7 @@ public abstract class Controller {
private void saveViewState(@NonNull View view) {
hasSavedViewState = true;
viewState = new Bundle();
viewState = new Bundle(getClass().getClassLoader());
SparseArray<Parcelable> hierarchyState = new SparseArray<>();
view.saveHierarchyState(hierarchyState);
@@ -1062,6 +1063,10 @@ public abstract class Controller {
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));
@@ -28,6 +28,7 @@ public abstract class ControllerChangeHandler {
private static final Map<String, ControllerChangeHandler> inProgressPushHandlers = new HashMap<>();
private boolean forceRemoveViewOnPush;
private boolean hasBeenUsed;
/**
* Responsible for swapping Views from one Controller to another.
@@ -73,6 +74,26 @@ public abstract class ControllerChangeHandler {
*/
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());
}
/**
* 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();
@@ -85,11 +106,6 @@ public abstract class ControllerChangeHandler {
return bundle;
}
@NonNull
final ControllerChangeHandler copy() {
return fromBundle(toBundle());
}
private void ensureDefaultConstructor() {
try {
getClass().getConstructor();
@@ -134,8 +150,20 @@ public abstract class ControllerChangeHandler {
}
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<ControllerChangeListener> 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 = inHandler != null ? inHandler : new SimpleSwapChangeHandler();
final ControllerChangeHandler handler;
if (inHandler == null) {
handler = new SimpleSwapChangeHandler();
} else if (inHandler.hasBeenUsed && !inHandler.isReusable()) {
handler = inHandler.copy();
} else {
handler = inHandler;
}
handler.hasBeenUsed = true;
if (isPush && to != null) {
inProgressPushHandlers.put(to.getInstanceId(), handler);
@@ -72,9 +72,9 @@ class ControllerHostedRouter extends Router {
}
@Override
void destroy() {
void destroy(boolean popViews) {
setDetachFrozen(false);
super.destroy();
super.destroy(popViews);
}
@Override @Nullable
@@ -172,7 +172,7 @@ public abstract class Router {
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);
}
}
}
@@ -185,11 +185,11 @@ public abstract class Router {
performControllerChange(transaction.pushChangeHandler(handler), topTransaction, true);
}
void destroy() {
void destroy(boolean popViews) {
popsLastView = true;
List<RouterTransaction> 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());
@@ -368,22 +368,19 @@ public abstract class Router {
}
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 @@ public abstract class Router {
} else if (from != null) {
changeHandler = from.popChangeHandler();
} else {
changeHandler = new SimpleSwapChangeHandler();
changeHandler = null;
}
Controller toController = to != null ? to.controller : null;
@@ -9,6 +9,8 @@ import android.transition.Transition;
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 @@ public class AutoTransitionChangeHandler extends TransitionChangeHandler {
return new AutoTransition();
}
@Override @NonNull
public ControllerChangeHandler copy() {
return new AutoTransitionChangeHandler();
}
}
@@ -8,6 +8,8 @@ import android.support.annotation.Nullable;
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 @@ public class FadeChangeHandler extends AnimatorChangeHandler {
protected void resetFromView(@NonNull View from) {
from.setAlpha(1);
}
@Override @NonNull
public ControllerChangeHandler copy() {
return new FadeChangeHandler(getAnimationDuration(), removesFromViewOnPush());
}
}
@@ -8,6 +8,8 @@ import android.support.annotation.Nullable;
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 @@ public class HorizontalChangeHandler extends AnimatorChangeHandler {
protected void resetFromView(@NonNull View from) {
from.setTranslationX(0);
}
@Override @NonNull
public ControllerChangeHandler copy() {
return new HorizontalChangeHandler(getAnimationDuration(), removesFromViewOnPush());
}
}
@@ -102,4 +102,14 @@ public class SimpleSwapChangeHandler extends ControllerChangeHandler implements
@Override
public void onViewDetachedFromWindow(@NonNull View v) { }
@Override @NonNull
public ControllerChangeHandler copy() {
return new SimpleSwapChangeHandler(removesFromViewOnPush());
}
@Override
public boolean isReusable() {
return true;
}
}
@@ -81,4 +81,5 @@ public abstract class TransitionChangeHandler extends ControllerChangeHandler {
public final boolean removesFromViewOnPush() {
return true;
}
}
@@ -69,4 +69,13 @@ public class TransitionChangeHandlerCompat extends ControllerChangeHandler {
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());
}
}
}
@@ -8,6 +8,8 @@ import android.support.annotation.Nullable;
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 @@ public class VerticalChangeHandler extends AnimatorChangeHandler {
@Override
protected void resetFromView(@NonNull View from) { }
@Override @NonNull
public ControllerChangeHandler copy() {
return new VerticalChangeHandler(getAnimationDuration(), removesFromViewOnPush());
}
}
@@ -14,4 +14,14 @@ public class NoOpControllerChangeHandler extends ControllerChangeHandler {
changeListener.onChangeCompleted();
}
@NonNull
@Override
public ControllerChangeHandler copy() {
return new NoOpControllerChangeHandler();
}
@Override
public boolean isReusable() {
return true;
}
}
@@ -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;
}
}
}
@@ -383,8 +383,8 @@ public class ControllerLifecycleTests {
});
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 class ControllerLifecycleTests {
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 class ControllerLifecycleTests {
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 class ControllerLifecycleTests {
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 @@ public class ControllerLifecycleTests {
}
private MockChangeHandler getPopHandler(final CallState expectedCallState, final TestController controller) {
return new MockChangeHandler(new ChangeHandlerListener() {
return MockChangeHandler.listeningChangeHandler(new ChangeHandlerListener() {
@Override
void willStartChange() {
expectedCallState.changeStartCalls++;
@@ -1,6 +1,5 @@
package com.bluelinelabs.conductor;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.view.View;
@@ -19,19 +18,23 @@ public class MockChangeHandler extends ControllerChangeHandler {
final ChangeHandlerListener listener;
boolean removesFromViewOnPush;
public static MockChangeHandler defaultHandler() {
return new MockChangeHandler(true, null);
}
public static MockChangeHandler noRemoveViewOnPushHandler() {
return new MockChangeHandler(false, null);
}
public static MockChangeHandler listeningChangeHandler(@NonNull ChangeHandlerListener listener) {
return new MockChangeHandler(true , listener);
}
public MockChangeHandler() {
this(true, null);
listener = null;
}
public MockChangeHandler(boolean removesViewOnPush) {
this(removesViewOnPush, null);
}
public MockChangeHandler(@NonNull ChangeHandlerListener listener) {
this(true, listener);
}
public MockChangeHandler(boolean removesFromViewOnPush, ChangeHandlerListener listener) {
private MockChangeHandler(boolean removesFromViewOnPush, ChangeHandlerListener listener) {
this.removesFromViewOnPush = removesFromViewOnPush;
if (listener == null) {
@@ -82,4 +85,15 @@ public class MockChangeHandler extends ControllerChangeHandler {
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;
}
}
@@ -36,8 +36,8 @@ public class ReattachCaseTests {
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 class ReattachCaseTests {
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 class ReattachCaseTests {
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 class ReattachCaseTests {
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 class ReattachCaseTests {
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 class ReattachCaseTests {
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 class ReattachCaseTests {
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 class ReattachCaseTests {
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());
@@ -222,7 +222,7 @@ public class RouterTests {
@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 class RouterTests {
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<RouterTransaction> backstack = new ArrayList<>();
backstack.add(rootTransaction);
@@ -286,7 +286,7 @@ public class RouterTests {
@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<RouterTransaction> backstack = new ArrayList<>();
backstack.add(rootTransaction);
@@ -303,7 +303,7 @@ public class RouterTests {
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();
@@ -38,14 +38,14 @@ public class TargetControllerTests {
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 class TargetControllerTests {
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 class TargetControllerTests {
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());
@@ -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++;
}
}
}
@@ -2,6 +2,7 @@ package com.bluelinelabs.conductor;
import android.view.View;
import android.view.View.OnAttachStateChangeListener;
import android.view.ViewGroup;
import org.robolectric.util.ReflectionHelpers;
@@ -10,6 +11,10 @@ import java.util.List;
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 class ViewUtils {
}
}
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<OnAttachStateChangeListener> getAttachStateListeners(View view) {
+5 -4
View File
@@ -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"
}
@@ -50,17 +50,19 @@ 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 && !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);
}
}
}
}
@@ -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 @@ import java.util.List;
* 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 {
@@ -1,9 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_height="match_parent"
android:orientation="horizontal"
tools:ignore="InconsistentLayout">
<android.support.v7.widget.RecyclerView
android:id="@+id/recycler_view"
@@ -17,6 +17,7 @@
android:textSize="22sp"
android:padding="16dp"
android:text="@string/drag_to_dismiss"
android:clickable="true"
/>
<android.support.v4.widget.NestedScrollView
@@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
@@ -13,6 +14,7 @@
android:elevation="6dp"
android:minHeight="?attr/actionBarSize"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
tools:targetApi="lollipop"
/>
<android.support.v4.view.ViewPager
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/transition_root"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
@@ -19,6 +20,7 @@
android:layout_marginTop="96dp"
android:gravity="center_horizontal"
android:transitionName="@string/transition_tag_title"
tools:targetApi="lollipop"
/>
<android.support.design.widget.FloatingActionButton
@@ -31,6 +33,7 @@
app:elevation="0dp"
app:pressedTranslationZ="0dp"
android:transitionName="@string/transition_tag_dot"
tools:targetApi="lollipop"
/>
</FrameLayout>
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/transition_root"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
@@ -13,6 +14,7 @@
android:layout_marginTop="24dp"
android:gravity="center_horizontal"
android:transitionName="@string/transition_tag_title"
tools:targetApi="lollipop"
/>
<android.support.design.widget.FloatingActionButton
@@ -24,6 +26,7 @@
app:elevation="0dp"
app:pressedTranslationZ="0dp"
android:transitionName="@string/transition_tag_dot"
tools:targetApi="lollipop"
/>
</FrameLayout>
+36 -23
View File
@@ -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'
}
+1 -1
View File
@@ -1,4 +1,4 @@
VERSION_NAME=2.0.6-SNAPSHOT
VERSION_NAME=2.0.7-SNAPSHOT
VERSION_CODE=2
GROUP=com.bluelinelabs