Compare commits

...

51 Commits

Author SHA1 Message Date
Eric Kuck 10a1c8af3e Version bump 2017-01-19 14:21:48 -06:00
Eric Kuck 6834df73e6 Fixes #203 2017-01-19 14:13:26 -06:00
Eric Kuck 04d40a5b90 Minor ViewAttachHandler optimizations 2017-01-19 13:21:02 -06:00
Allan Hasegawa be40900e1e #Fixes 206 (#207) 2017-01-19 13:20:20 -06:00
Eric Kuck f16f7b6d2c Fixes #205 2017-01-19 12:02:31 -06:00
Eric Kuck f74f8391b6 Fixes #204 (also mentioned in #199) 2017-01-19 11:50:07 -06:00
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
Eric Kuck 48dc4abcbe Version bump 2016-12-13 17:09:19 -06:00
Eric Kuck 4a814afb5f Fixes #166 2016-12-13 16:56:22 -06:00
Eric Kuck 9cd225e704 Controllers now throw an exception when the user forgets to pass false for LayoutInflater.inflate's attachToRoot parameter. 2016-12-12 14:31:22 -06:00
Eric Kuck c8640af1ac Remove saveState option for RouterPagerAdapters, as users can configure the page before display anyway. 2016-12-12 13:25:57 -06:00
Eric Kuck 43c825f7c2 - Child backstack is now properly restored when Android kills the process
- Added a RouterPagerAdapter, which allows the use of Routers as pages
2016-12-12 13:09:29 -06:00
Eric Kuck 7334ed5300 ControllerPagerAdapter updates to enable using a per-page router if needed. 2016-12-12 12:24:26 -06:00
Eric Kuck 7ea4872ff8 Updated ordering of calls in backstack to be in line with other backstack-affecting calls 2016-12-08 13:25:11 -06:00
Eric Kuck 9655170bd2 Fixes tests 2016-12-07 16:34:38 -06:00
Eric Kuck acce9b1702 Simplified setRoot implementation 2016-12-07 16:17:32 -06:00
Eric Kuck 95baa8baa3 Fixes #172 2016-12-01 18:00:06 -06:00
Eric Kuck 2388fa2d06 Added a demo for the new RxLifecycle2Controller 2016-12-01 17:56:43 -06:00
Vishnu Rajeevan 285eb59da0 add rxlifecycle2 support, fixes #148 (#171) 2016-12-01 17:31:52 -06:00
Eric Kuck ae42ee1674 Attempted fix for #165 2016-12-01 17:28:15 -06:00
Eric Kuck 638b2ad311 Version bump 2016-11-11 11:10:35 -06:00
Eric Kuck 96e068d348 Fixes a controller's internal backstack when setBackstack is used on a child router 2016-11-11 10:44:37 -06:00
Eric Kuck db359d906b Un-deprecated getChildRouter with a tag. Fixes #160. 2016-11-10 13:34:13 -06:00
Eric Kuck 11185458b3 Fixed nullable annotations for menu callbacks 2016-11-09 16:03:20 -06:00
Eric Kuck 977db6b5bf Merge branch 'develop' of github.com:bluelinelabs/Conductor into develop 2016-11-09 15:32:01 -06:00
Eric Kuck 803c20e093 Added UiThread annotations - closes #145 2016-11-09 15:25:28 -06:00
TMTron 9948cb4652 Navigation Demos: "GO UP" is hidden in "Controller #0" - closes #158 (#159)
* Navigation Demos: "GO UP" is hidden in "Controller #0" - this closes #158

* Multiple Child Routers: "GO UP" is hidden for all - #158
2016-11-09 14:54:27 -06:00
Eric Kuck 07a579b939 Fixed incorrect child router backstack handling if controllers were VERY rapidly added 2016-11-09 11:07:41 -06:00
Eric Kuck 104d96e6e2 Filled out @Nullable and @NonNull annotations throughout the library 2016-11-09 10:09:00 -06:00
Eric Kuck e0f40a9fce Fixes tests 2016-11-02 12:29:58 -05:00
Eric Kuck bc8e0c5b2c Fixes #113 2016-11-02 12:04:53 -05:00
Eric Kuck 2b6e41f895 Fixes #140 2016-10-17 17:32:10 -05:00
Eric Kuck b633523d0e Fixes #138 2016-10-12 12:22:22 -05:00
Eric Kuck c5eb7fc89e Fixes #136 2016-10-12 09:44:26 -05:00
Eric Kuck cf6837a41a Fixes #137 2016-10-12 09:24:29 -05:00
Eric Kuck e297242264 Now handles requesting permissions while now yet fully attached to the host activity 2016-10-10 15:14:06 -05:00
Eric Kuck 550e7e0aa1 Added missing constructor to RestoreViewOnCreateController 2016-10-05 15:16:04 -05:00
91 changed files with 2993 additions and 1127 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.3'
compile 'com.bluelinelabs:conductor:2.0.7'
// If you want the components that go along with
// Android's support libraries (currently just a PagerAdapter):
compile 'com.bluelinelabs:conductor-support:2.0.3'
compile 'com.bluelinelabs:conductor-support:2.0.7'
// If you want RxJava/RxAndroid lifecycle support:
compile 'com.bluelinelabs:conductor-rxlifecycle:2.0.3'
// If you want RxJava lifecycle support:
compile 'com.bluelinelabs:conductor-rxlifecycle:2.0.7'
// If you want RxJava2 lifecycle support:
compile 'com.bluelinelabs:conductor-rxlifecycle2:2.0.7'
```
SNAPSHOT:
```gradle
compile 'com.bluelinelabs:conductor:2.0.4-SNAPSHOT'
compile 'com.bluelinelabs:conductor-support:2.0.4-SNAPSHOT'
compile 'com.bluelinelabs:conductor-rxlifecycle:2.0.4-SNAPSHOT'
compile 'com.bluelinelabs:conductor:2.0.6-SNAPSHOT'
compile 'com.bluelinelabs:conductor-support:2.0.6-SNAPSHOT'
compile 'com.bluelinelabs:conductor-rxlifecycle:2.0.6-SNAPSHOT'
compile 'com.bluelinelabs:conductor-rxlifecycle2:2.0.6-SNAPSHOT'
```
You also have to add the url to the snapshot repository:
+2 -6
View File
@@ -3,19 +3,15 @@ buildscript {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.2.0'
classpath 'com.android.tools.build:gradle:2.2.3'
}
}
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
@@ -0,0 +1,52 @@
package com.bluelinelabs.conductor.rxlifecycle;
import android.os.Bundle;
import android.support.annotation.CheckResult;
import android.support.annotation.NonNull;
import com.bluelinelabs.conductor.RestoreViewOnCreateController;
import com.trello.rxlifecycle.LifecycleProvider;
import com.trello.rxlifecycle.LifecycleTransformer;
import com.trello.rxlifecycle.RxLifecycle;
import rx.Observable;
import rx.subjects.BehaviorSubject;
/**
* A base {@link RestoreViewOnCreateController} that can be used to expose lifecycle events using RxJava
*/
public abstract class RxRestoreViewOnCreateController extends RestoreViewOnCreateController implements LifecycleProvider<ControllerEvent> {
private final BehaviorSubject<ControllerEvent> lifecycleSubject;
public RxRestoreViewOnCreateController() {
this(null);
}
public RxRestoreViewOnCreateController(Bundle args) {
super(args);
lifecycleSubject = ControllerLifecycleSubjectHelper.create(this);
}
@Override
@NonNull
@CheckResult
public final Observable<ControllerEvent> lifecycle() {
return lifecycleSubject.asObservable();
}
@Override
@NonNull
@CheckResult
public final <T> LifecycleTransformer<T> bindUntilEvent(@NonNull ControllerEvent event) {
return RxLifecycle.bindUntilEvent(lifecycleSubject, event);
}
@Override
@NonNull
@CheckResult
public final <T> LifecycleTransformer<T> bindToLifecycle() {
return RxControllerLifecycle.bindController(lifecycleSubject);
}
}
+31
View File
@@ -0,0 +1,31 @@
apply from: rootProject.file('dependencies.gradle')
apply from: rootProject.file('gradle-mvn-push.gradle')
apply plugin: 'com.android.library'
android {
compileSdkVersion rootProject.ext.compileSdkVersion
buildToolsVersion rootProject.ext.buildToolsVersion
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_7
targetCompatibility JavaVersion.VERSION_1_7
}
defaultConfig {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode Integer.parseInt(project.VERSION_CODE)
versionName project.VERSION_NAME
}
}
dependencies {
compile rootProject.ext.rxJava2
compile rootProject.ext.rxLifecycle2
compile rootProject.ext.rxLifecycleAndroid2
compile project(':conductor')
}
ext.artifactId = 'conductor-rxlifecycle2'
+3
View File
@@ -0,0 +1,3 @@
POM_NAME=Conductor RxLifecycle2 Extensions
POM_ARTIFACT_ID=conductor-rxlifecycle2
POM_PACKAGING=aar
@@ -0,0 +1,4 @@
<manifest package="com.bluelinelabs.conductor.rxlifecycle2">
<application />
</manifest>
@@ -0,0 +1,12 @@
package com.bluelinelabs.conductor.rxlifecycle2;
public enum ControllerEvent {
CREATE,
CREATE_VIEW,
ATTACH,
DETACH,
DESTROY_VIEW,
DESTROY
}
@@ -0,0 +1,44 @@
package com.bluelinelabs.conductor.rxlifecycle2;
import android.support.annotation.NonNull;
import android.view.View;
import com.bluelinelabs.conductor.Controller;
import io.reactivex.subjects.BehaviorSubject;
public class ControllerLifecycleSubjectHelper {
private ControllerLifecycleSubjectHelper() {
}
public static BehaviorSubject<ControllerEvent> create(Controller controller){
final BehaviorSubject<ControllerEvent> subject = BehaviorSubject.createDefault(ControllerEvent.CREATE);
controller.addLifecycleListener(new Controller.LifecycleListener() {
@Override
public void preCreateView(@NonNull Controller controller) {
subject.onNext(ControllerEvent.CREATE_VIEW);
}
@Override
public void preAttach(@NonNull Controller controller, @NonNull View view) {
subject.onNext(ControllerEvent.ATTACH);
}
@Override
public void preDetach(@NonNull Controller controller, @NonNull View view) {
subject.onNext(ControllerEvent.DETACH);
}
@Override
public void preDestroyView(@NonNull Controller controller, @NonNull View view) {
subject.onNext(ControllerEvent.DESTROY_VIEW);
}
@Override
public void preDestroy(@NonNull Controller controller) {
subject.onNext(ControllerEvent.DESTROY);
}
});
return subject;
}
}
@@ -0,0 +1,49 @@
package com.bluelinelabs.conductor.rxlifecycle2;
import android.os.Bundle;
import android.support.annotation.CheckResult;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import com.bluelinelabs.conductor.Controller;
import com.trello.rxlifecycle2.LifecycleProvider;
import com.trello.rxlifecycle2.LifecycleTransformer;
import com.trello.rxlifecycle2.RxLifecycle;
import io.reactivex.Observable;
import io.reactivex.subjects.BehaviorSubject;
/**
* A base {@link Controller} that can be used to expose lifecycle events using RxJava
*/
public abstract class RxController extends Controller implements LifecycleProvider<ControllerEvent> {
private final BehaviorSubject<ControllerEvent> lifecycleSubject;
public RxController(){
this(null);
}
public RxController(@Nullable Bundle args) {
super(args);
lifecycleSubject = ControllerLifecycleSubjectHelper.create(this);
}
@Override
@NonNull
@CheckResult
public final Observable<ControllerEvent> lifecycle() {
return lifecycleSubject.hide();
}
@Override
@NonNull
@CheckResult
public final <T> LifecycleTransformer<T> bindUntilEvent(@NonNull ControllerEvent event) {
return RxLifecycle.bindUntilEvent(lifecycleSubject, event);
}
@Override
@NonNull
@CheckResult
public final <T> LifecycleTransformer<T> bindToLifecycle() {
return RxControllerLifecycle.bindController(lifecycleSubject);
}
}
@@ -0,0 +1,41 @@
package com.bluelinelabs.conductor.rxlifecycle2;
import android.support.annotation.NonNull;
import com.trello.rxlifecycle2.LifecycleTransformer;
import com.trello.rxlifecycle2.OutsideLifecycleException;
import com.trello.rxlifecycle2.RxLifecycle;
import io.reactivex.Observable;
import io.reactivex.functions.Function;
public class RxControllerLifecycle {
/**
* Binds the given source to a Controller lifecycle. This is the Controller version of
* {@link com.trello.rxlifecycle2.android.RxLifecycleAndroid#bindFragment(Observable)}.
*
* @param lifecycle the lifecycle sequence of a Controller
* @return a reusable {@link io.reactivex.ObservableTransformer} that unsubscribes the source during the Controller lifecycle
*/
public static <T> LifecycleTransformer<T> bindController(@NonNull final Observable<ControllerEvent> lifecycle) {
return RxLifecycle.bind(lifecycle, CONTROLLER_LIFECYCLE);
}
private static final Function<ControllerEvent, ControllerEvent> CONTROLLER_LIFECYCLE =
new Function<ControllerEvent, ControllerEvent>() {
@Override
public ControllerEvent apply(ControllerEvent lastEvent) {
switch (lastEvent) {
case CREATE:
return ControllerEvent.DESTROY;
case ATTACH:
return ControllerEvent.DETACH;
case CREATE_VIEW:
return ControllerEvent.DESTROY_VIEW;
case DETACH:
return ControllerEvent.DESTROY;
default:
throw new OutsideLifecycleException("Cannot bind to Controller lifecycle when outside of it.");
}
}
};
}
@@ -0,0 +1,45 @@
package com.bluelinelabs.conductor.rxlifecycle2;
import android.os.Bundle;
import android.support.annotation.CheckResult;
import android.support.annotation.NonNull;
import com.bluelinelabs.conductor.RestoreViewOnCreateController;
import com.trello.rxlifecycle2.LifecycleProvider;
import com.trello.rxlifecycle2.LifecycleTransformer;
import com.trello.rxlifecycle2.RxLifecycle;
import io.reactivex.Observable;
import io.reactivex.subjects.BehaviorSubject;
public abstract class RxRestoreViewOnCreateController extends RestoreViewOnCreateController implements LifecycleProvider<ControllerEvent> {
private final BehaviorSubject<ControllerEvent> lifecycleSubject;
public RxRestoreViewOnCreateController() {
this(null);
}
public RxRestoreViewOnCreateController(Bundle args) {
super(args);
lifecycleSubject = ControllerLifecycleSubjectHelper.create(this);
}
@Override
@NonNull
@CheckResult
public final Observable<ControllerEvent> lifecycle() {
return lifecycleSubject.hide();
}
@Override
@NonNull
@CheckResult
public final <T> LifecycleTransformer<T> bindUntilEvent(@NonNull ControllerEvent event) {
return RxLifecycle.bindUntilEvent(lifecycleSubject, event);
}
@Override
@NonNull
@CheckResult
public final <T> LifecycleTransformer<T> bindToLifecycle() {
return RxControllerLifecycle.bindController(lifecycleSubject);
}
}
-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
@@ -2,6 +2,7 @@ package com.bluelinelabs.conductor.support;
import android.os.Bundle;
import android.os.Parcelable;
import android.support.annotation.Nullable;
import android.support.v4.view.PagerAdapter;
import android.util.SparseArray;
import android.view.View;
@@ -18,10 +19,13 @@ public abstract class ControllerPagerAdapter extends PagerAdapter {
private static final String KEY_SAVED_PAGES = "ControllerPagerAdapter.savedStates";
private static final String KEY_SAVES_STATE = "ControllerPagerAdapter.savesState";
private static final String KEY_VISIBLE_PAGE_IDS_KEYS = "ControllerPagerAdapter.visiblePageIds.keys";
private static final String KEY_VISIBLE_PAGE_IDS_VALUES = "ControllerPagerAdapter.visiblePageIds.values";
private final Controller host;
private boolean savesState;
private SparseArray<Bundle> savedPages = new SparseArray<>();
private SparseArray<String> visiblePageIds = new SparseArray<>();
/**
* Creates a new ControllerPagerAdapter using the passed host.
@@ -50,8 +54,9 @@ public abstract class ControllerPagerAdapter extends PagerAdapter {
}
if (!router.hasRootController()) {
router.setRoot(RouterTransaction.with(getItem(position))
.tag(name));
Controller controller = getItem(position);
router.setRoot(RouterTransaction.with(controller).tag(name));
visiblePageIds.put(position, controller.getInstanceId());
} else {
router.rebindIfNeeded();
}
@@ -69,6 +74,8 @@ public abstract class ControllerPagerAdapter extends PagerAdapter {
savedPages.put(position, savedState);
}
visiblePageIds.remove(position);
host.removeChildRouter(router);
}
@@ -82,6 +89,16 @@ public abstract class ControllerPagerAdapter extends PagerAdapter {
Bundle bundle = new Bundle();
bundle.putBoolean(KEY_SAVES_STATE, savesState);
bundle.putSparseParcelableArray(KEY_SAVED_PAGES, savedPages);
int[] visiblePageIdsKeys = new int[visiblePageIds.size()];
String[] visiblePageIdsValues = new String[visiblePageIds.size()];
for (int i = 0; i < visiblePageIds.size(); i++) {
visiblePageIdsKeys[i] = visiblePageIds.keyAt(i);
visiblePageIdsValues[i] = visiblePageIds.valueAt(i);
}
bundle.putIntArray(KEY_VISIBLE_PAGE_IDS_KEYS, visiblePageIdsKeys);
bundle.putStringArray(KEY_VISIBLE_PAGE_IDS_VALUES, visiblePageIdsValues);
return bundle;
}
@@ -91,6 +108,26 @@ public abstract class ControllerPagerAdapter extends PagerAdapter {
if (state != null) {
savesState = bundle.getBoolean(KEY_SAVES_STATE, false);
savedPages = bundle.getSparseParcelableArray(KEY_SAVED_PAGES);
int[] visiblePageIdsKeys = bundle.getIntArray(KEY_VISIBLE_PAGE_IDS_KEYS);
String[] visiblePageIdsValues = bundle.getStringArray(KEY_VISIBLE_PAGE_IDS_VALUES);
visiblePageIds = new SparseArray<>(visiblePageIdsKeys.length);
for (int i = 0; i < visiblePageIdsKeys.length; i++) {
visiblePageIds.put(visiblePageIdsKeys[i], visiblePageIdsValues[i]);
}
}
}
/**
* Returns the already instantiated Controller in the specified position, if available.
*/
@Nullable
public Controller getController(int position) {
String instanceId = visiblePageIds.get(position);
if (instanceId != null) {
return host.getRouter().getControllerWithInstanceId(instanceId);
} else {
return null;
}
}
@@ -0,0 +1,119 @@
package com.bluelinelabs.conductor.support;
import android.os.Bundle;
import android.os.Parcelable;
import android.support.annotation.Nullable;
import android.support.v4.view.PagerAdapter;
import android.util.SparseArray;
import android.view.View;
import android.view.ViewGroup;
import com.bluelinelabs.conductor.Controller;
import com.bluelinelabs.conductor.Router;
import com.bluelinelabs.conductor.RouterTransaction;
import java.util.List;
/**
* An adapter for ViewPagers that uses Routers as pages
*/
public abstract class RouterPagerAdapter extends PagerAdapter {
private static final String KEY_SAVED_PAGES = "RouterPagerAdapter.savedStates";
private final Controller host;
private SparseArray<Bundle> savedPages = new SparseArray<>();
private SparseArray<Router> visibleRouters = new SparseArray<>();
/**
* Creates a new RouterPagerAdapter using the passed host.
*/
public RouterPagerAdapter(Controller host) {
this.host = host;
}
/**
* Called when a router is instantiated. Here the router's root should be set if needed.
*
* @param router The router used for the page
* @param position The page position to be instantiated.
*/
public abstract void configureRouter(Router router, int position);
@Override
public Object instantiateItem(ViewGroup container, int position) {
final String name = makeRouterName(container.getId(), getItemId(position));
Router router = host.getChildRouter(container, name);
if (!router.hasRootController()) {
Bundle routerSavedState = savedPages.get(position);
if (routerSavedState != null) {
router.restoreInstanceState(routerSavedState);
}
}
router.rebindIfNeeded();
configureRouter(router, position);
visibleRouters.put(position, router);
return router;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
Router router = (Router)object;
Bundle savedState = new Bundle();
router.saveInstanceState(savedState);
savedPages.put(position, savedState);
host.removeChildRouter(router);
visibleRouters.remove(position);
}
@Override
public boolean isViewFromObject(View view, Object object) {
Router router = (Router)object;
final List<RouterTransaction> backstack = router.getBackstack();
for (RouterTransaction transaction : backstack) {
if (transaction.controller().getView() == view) {
return true;
}
}
return false;
}
@Override
public Parcelable saveState() {
Bundle bundle = new Bundle();
bundle.putSparseParcelableArray(KEY_SAVED_PAGES, savedPages);
return bundle;
}
@Override
public void restoreState(Parcelable state, ClassLoader loader) {
Bundle bundle = (Bundle)state;
if (state != null) {
savedPages = bundle.getSparseParcelableArray(KEY_SAVED_PAGES);
}
}
/**
* Returns the already instantiated Router in the specified position, if available.
*/
@Nullable
public Router getRouter(int position) {
return visibleRouters.get(position);
}
public long getItemId(int position) {
return position;
}
private static String makeRouterName(int viewId, long id) {
return viewId + ":" + id;
}
}
-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
@@ -4,6 +4,7 @@ import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.view.ViewGroup;
import com.bluelinelabs.conductor.ControllerChangeHandler.ControllerChangeListener;
@@ -30,13 +31,13 @@ public class ActivityHostedRouter extends Router {
}
}
@Override
@Override @Nullable
public Activity getActivity() {
return lifecycleHandler != null ? lifecycleHandler.getLifecycleActivity() : null;
}
@Override
public void onActivityDestroyed(Activity activity) {
public void onActivityDestroyed(@NonNull Activity activity) {
super.onActivityDestroyed(activity);
lifecycleHandler = null;
}
@@ -49,37 +50,37 @@ public class ActivityHostedRouter extends Router {
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
lifecycleHandler.onActivityResult(requestCode, resultCode, data);
}
@Override
void startActivity(Intent intent) {
void startActivity(@NonNull Intent intent) {
lifecycleHandler.startActivity(intent);
}
@Override
void startActivityForResult(String instanceId, Intent intent, int requestCode) {
void startActivityForResult(@NonNull String instanceId, @NonNull Intent intent, int requestCode) {
lifecycleHandler.startActivityForResult(instanceId, intent, requestCode);
}
@Override
void startActivityForResult(String instanceId, Intent intent, int requestCode, Bundle options) {
void startActivityForResult(@NonNull String instanceId, @NonNull Intent intent, int requestCode, @Nullable Bundle options) {
lifecycleHandler.startActivityForResult(instanceId, intent, requestCode, options);
}
@Override
void registerForActivityResult(String instanceId, int requestCode) {
void registerForActivityResult(@NonNull String instanceId, int requestCode) {
lifecycleHandler.registerForActivityResult(instanceId, requestCode);
}
@Override
void unregisterForActivityResults(String instanceId) {
void unregisterForActivityResults(@NonNull String instanceId) {
lifecycleHandler.unregisterForActivityResults(instanceId);
}
@Override
void requestPermissions(String instanceId, @NonNull String[] permissions, int requestCode) {
void requestPermissions(@NonNull String instanceId, @NonNull String[] permissions, int requestCode) {
lifecycleHandler.requestPermissions(instanceId, permissions, requestCode);
}
@@ -88,12 +89,12 @@ public class ActivityHostedRouter extends Router {
return lifecycleHandler != null;
}
@Override
@Override @NonNull
List<Router> getSiblingRouters() {
return lifecycleHandler.getRouters();
}
@Override
@Override @NonNull
Router getRootRouter() {
return this;
}
@@ -1,6 +1,8 @@
package com.bluelinelabs.conductor;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import java.util.ArrayDeque;
import java.util.ArrayList;
@@ -13,34 +15,37 @@ class Backstack implements Iterable<RouterTransaction> {
private static final String KEY_ENTRIES = "Backstack.entries";
private final Deque<RouterTransaction> backStack = new ArrayDeque<>();
private final Deque<RouterTransaction> backstack = new ArrayDeque<>();
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
public boolean isEmpty() {
return backStack.isEmpty();
return backstack.isEmpty();
}
public int size() {
return backStack.size();
return backstack.size();
}
@Nullable
public RouterTransaction root() {
return backStack.size() > 0 ? backStack.getLast() : null;
return backstack.size() > 0 ? backstack.getLast() : null;
}
@Override
@Override @NonNull
public Iterator<RouterTransaction> iterator() {
return backStack.iterator();
return backstack.iterator();
}
@NonNull
public Iterator<RouterTransaction> reverseIterator() {
return backStack.descendingIterator();
return backstack.descendingIterator();
}
public List<RouterTransaction> popTo(RouterTransaction transaction) {
@NonNull
public List<RouterTransaction> popTo(@NonNull RouterTransaction transaction) {
List<RouterTransaction> popped = new ArrayList<>();
if (backStack.contains(transaction)) {
while (backStack.peek() != transaction) {
if (backstack.contains(transaction)) {
while (backstack.peek() != transaction) {
RouterTransaction poppedTransaction = pop();
popped.add(poppedTransaction);
}
@@ -50,24 +55,27 @@ class Backstack implements Iterable<RouterTransaction> {
return popped;
}
@NonNull
public RouterTransaction pop() {
RouterTransaction popped = backStack.pop();
RouterTransaction popped = backstack.pop();
popped.controller.destroy();
return popped;
}
@Nullable
public RouterTransaction peek() {
return backStack.peek();
return backstack.peek();
}
public void remove(RouterTransaction transaction) {
backStack.removeFirstOccurrence(transaction);
public void remove(@NonNull RouterTransaction transaction) {
backstack.removeFirstOccurrence(transaction);
}
public void push(RouterTransaction transaction) {
backStack.push(transaction);
public void push(@NonNull RouterTransaction transaction) {
backstack.push(transaction);
}
@NonNull
public List<RouterTransaction> popAll() {
List<RouterTransaction> list = new ArrayList<>();
while (!isEmpty()) {
@@ -76,8 +84,8 @@ class Backstack implements Iterable<RouterTransaction> {
return list;
}
public void setBackstack(List<RouterTransaction> backstack) {
for (RouterTransaction existingTransaction : backStack) {
public void setBackstack(@NonNull List<RouterTransaction> backstack) {
for (RouterTransaction existingTransaction : this.backstack) {
boolean contains = false;
for (RouterTransaction newTransaction : backstack) {
if (existingTransaction.controller == newTransaction.controller) {
@@ -91,27 +99,27 @@ class Backstack implements Iterable<RouterTransaction> {
}
}
backStack.clear();
this.backstack.clear();
for (RouterTransaction transaction : backstack) {
backStack.push(transaction);
this.backstack.push(transaction);
}
}
public void saveInstanceState(Bundle outState) {
ArrayList<Bundle> entryBundles = new ArrayList<>(backStack.size());
for (RouterTransaction entry : backStack) {
public void saveInstanceState(@NonNull Bundle outState) {
ArrayList<Bundle> entryBundles = new ArrayList<>(backstack.size());
for (RouterTransaction entry : backstack) {
entryBundles.add(entry.saveInstanceState());
}
outState.putParcelableArrayList(KEY_ENTRIES, entryBundles);
}
public void restoreInstanceState(Bundle savedInstanceState) {
public void restoreInstanceState(@NonNull Bundle savedInstanceState) {
ArrayList<Bundle> entryBundles = savedInstanceState.getParcelableArrayList(KEY_ENTRIES);
if (entryBundles != null) {
Collections.reverse(entryBundles);
for (Bundle transactionBundle : entryBundles) {
backStack.push(new RouterTransaction(transactionBundle));
backstack.push(new RouterTransaction(transactionBundle));
}
}
}
@@ -3,6 +3,8 @@ package com.bluelinelabs.conductor;
import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.ViewGroup;
@@ -42,12 +44,12 @@ public class ChangeHandlerFrameLayout extends FrameLayout implements ControllerC
}
@Override
public void onChangeStarted(Controller to, Controller from, boolean isPush, ViewGroup container, ControllerChangeHandler handler) {
public void onChangeStarted(@Nullable Controller to, @Nullable Controller from, boolean isPush, @NonNull ViewGroup container, @NonNull ControllerChangeHandler handler) {
inProgressTransactionCount++;
}
@Override
public void onChangeCompleted(Controller to, Controller from, boolean isPush, ViewGroup container, ControllerChangeHandler handler) {
public void onChangeCompleted(@Nullable Controller to, @Nullable Controller from, boolean isPush, @NonNull ViewGroup container, @NonNull ControllerChangeHandler handler) {
inProgressTransactionCount--;
}
@@ -3,6 +3,8 @@ package com.bluelinelabs.conductor;
import android.app.Activity;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.UiThread;
import android.view.ViewGroup;
import com.bluelinelabs.conductor.internal.LifecycleHandler;
@@ -26,7 +28,8 @@ public final class Conductor {
* for restoring the Router's state if possible.
* @return A fully configured {@link Router} instance for use with this Activity/ViewGroup pair.
*/
public static Router attachRouter(@NonNull Activity activity, @NonNull ViewGroup container, Bundle savedInstanceState) {
@NonNull @UiThread
public static Router attachRouter(@NonNull Activity activity, @NonNull ViewGroup container, @Nullable Bundle savedInstanceState) {
LifecycleHandler lifecycleHandler = LifecycleHandler.install(activity);
Router router = lifecycleHandler.getRouter(container, savedInstanceState);
@@ -10,6 +10,7 @@ import android.os.Bundle;
import android.os.Parcelable;
import android.support.annotation.IdRes;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import android.util.SparseArray;
import android.view.LayoutInflater;
@@ -17,20 +18,19 @@ 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.ControllerChangeHandler.ControllerChangeListener;
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;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Deque;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.UUID;
@@ -45,6 +45,7 @@ public abstract class Controller {
private static final String KEY_CLASS_NAME = "Controller.className";
private static final String KEY_VIEW_STATE = "Controller.viewState";
private static final String KEY_CHILD_ROUTERS = "Controller.childRouters";
private static final String KEY_CHILD_BACKSTACK = "Controller.childBackstack";
private static final String KEY_SAVED_STATE = "Controller.savedState";
private static final String KEY_INSTANCE_ID = "Controller.instanceId";
private static final String KEY_TARGET_INSTANCE_ID = "Controller.target.instanceId";
@@ -74,32 +75,29 @@ public abstract class Controller {
private String instanceId;
private String targetInstanceId;
private boolean needsAttach;
private boolean attachedToUnownedParent;
private boolean hasSavedViewState;
private boolean isDetachFrozen;
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<>();
private final ArrayList<RouterRequiringFunc> onRouterSetListeners = new ArrayList<>();
private final Deque<Controller> childBackstack = new ArrayDeque<>();
private final List<Controller> childBackstack = new LinkedList<>();
private WeakReference<View> destroyedView;
private final ControllerChangeListener childRouterChangeListener = new ControllerChangeListener() {
private final OnControllerPushedListener onControllerPushedListener = new OnControllerPushedListener() {
@Override
public void onChangeStarted(Controller to, Controller from, boolean isPush, ViewGroup container, ControllerChangeHandler handler) {
if (isPush) {
onChildControllerPushed(to);
}
public void onControllerPushed(Controller controller) {
onChildControllerPushed(controller);
}
@Override
public void onChangeCompleted(Controller to, Controller from, boolean isPush, ViewGroup container, ControllerChangeHandler handler) { }
};
static Controller newInstance(Bundle bundle) {
@NonNull
static Controller newInstance(@NonNull Bundle bundle) {
final String className = bundle.getString(KEY_CLASS_NAME);
//noinspection ConstantConditions
Constructor[] constructors = ClassUtils.classForName(className, false).getConstructors();
@@ -133,8 +131,8 @@ public abstract class Controller {
*
* @param args Any arguments that need to be retained.
*/
protected Controller(Bundle args) {
this.args = args;
protected Controller(@Nullable Bundle args) {
this.args = args != null ? args : new Bundle();
instanceId = UUID.randomUUID().toString();
ensureRequiredConstructor();
}
@@ -162,11 +160,48 @@ public abstract class Controller {
/**
* Returns any arguments that were set in this Controller's constructor
*/
@NonNull
public Bundle getArgs() {
return args;
}
public final Router getChildRouter(@NonNull ViewGroup container, String tag) {
/**
* Retrieves the child {@link Router} for the given container. If no child router for this container
* exists yet, it will be created.
*
* @param container The ViewGroup that hosts the child Router
*/
@NonNull
public final Router getChildRouter(@NonNull ViewGroup container) {
return getChildRouter(container, null);
}
/**
* Retrieves the child {@link Router} for the given container/tag combination. If no child router for
* this container exists yet, it will be created. Note that multiple routers should not exist
* in the same container unless a lot of care is taken to maintain order between them. Avoid using
* the same container unless you have a great reason to do so (ex: ViewPagers).
*
* @param container The ViewGroup that hosts the child Router
* @param tag The router's tag
*/
@NonNull
public final Router getChildRouter(@NonNull ViewGroup container, @Nullable String tag) {
//noinspection ConstantConditions
return getChildRouter(container, tag, true);
}
/**
* Retrieves the child {@link Router} for the given container/tag combination. Note that multiple
* routers should not exist in the same container unless a lot of care is taken to maintain order
* between them. Avoid using the same container unless you have a great reason to do so (ex: ViewPagers).
*
* @param container The ViewGroup that hosts the child Router
* @param tag The router's tag
* @param createIfNeeded If true, a router will be created if one does not yet exist. Else false will be returned in this case.
*/
@Nullable
public final Router getChildRouter(@NonNull ViewGroup container, @Nullable String tag, boolean createIfNeeded) {
@IdRes final int containerId = container.getId();
ControllerHostedRouter childRouter = null;
@@ -178,10 +213,12 @@ public abstract class Controller {
}
if (childRouter == null) {
childRouter = new ControllerHostedRouter(container.getId(), tag);
monitorChildRouter(childRouter);
childRouter.setHost(this, container);
childRouters.add(childRouter);
if (createIfNeeded) {
childRouter = new ControllerHostedRouter(container.getId(), tag);
monitorChildRouter(childRouter);
childRouter.setHost(this, container);
childRouters.add(childRouter);
}
} else if (!childRouter.hasHost()) {
childRouter.setHost(this, container);
monitorChildRouter(childRouter);
@@ -193,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);
}
}
@@ -221,6 +258,7 @@ public abstract class Controller {
/**
* Return this Controller's View, if available.
*/
@Nullable
public final View getView() {
return view;
}
@@ -228,6 +266,7 @@ public abstract class Controller {
/**
* Returns the host Activity of this Controller's {@link Router}
*/
@Nullable
public final Activity getActivity() {
return router.getActivity();
}
@@ -235,6 +274,7 @@ public abstract class Controller {
/**
* Returns the Resources from the host Activity
*/
@Nullable
public final Resources getResources() {
Activity activity = getActivity();
return activity != null ? activity.getResources() : null;
@@ -243,6 +283,7 @@ public abstract class Controller {
/**
* Returns the Application Context derived from the host Activity
*/
@Nullable
public final Context getApplicationContext() {
Activity activity = getActivity();
return activity != null ? activity.getApplicationContext() : null;
@@ -251,6 +292,7 @@ public abstract class Controller {
/**
* Returns this Controller's parent Controller if it is a child Controller.
*/
@Nullable
public final Controller getParentController() {
return parentController;
}
@@ -259,6 +301,7 @@ public abstract class Controller {
* Returns this Controller's instance ID, which is generated when the instance is created and
* retained across restarts.
*/
@NonNull
public final String getInstanceId() {
return instanceId;
}
@@ -269,7 +312,8 @@ public abstract class Controller {
* @param instanceId The instance ID being searched for
* @return The matching Controller, if one exists
*/
final Controller findController(String instanceId) {
@Nullable
final Controller findController(@NonNull String instanceId) {
if (this.instanceId.equals(instanceId)) {
return this;
}
@@ -286,6 +330,7 @@ public abstract class Controller {
/**
* Returns all of this Controller's child Routers
*/
@NonNull
public final List<Router> getChildRouters() {
List<Router> routers = new ArrayList<>();
for (Router router : childRouters) {
@@ -301,7 +346,7 @@ public abstract class Controller {
*
* @param target The Controller that is the target of this one.
*/
public void setTargetController(Controller target) {
public void setTargetController(@Nullable Controller target) {
if (targetInstanceId != null) {
throw new RuntimeException("Target controller already set. A controller's target may only be set once.");
}
@@ -314,6 +359,7 @@ public abstract class Controller {
*
* @return This Controller's target
*/
@Nullable
public final Controller getTargetController() {
if (targetInstanceId != null) {
return router.getRootRouter().getControllerWithInstanceId(targetInstanceId);
@@ -327,7 +373,7 @@ public abstract class Controller {
*
* @param view The View to which this Controller should be bound.
*/
protected void onDestroyView(View view) { }
protected void onDestroyView(@NonNull View view) { }
/**
* Called when this Controller begins the process of being swapped in or out of the host view.
@@ -367,22 +413,22 @@ public abstract class Controller {
/**
* Called when this Controller's host Activity is started
*/
protected void onActivityStarted(Activity activity) { }
protected void onActivityStarted(@NonNull Activity activity) { }
/**
* Called when this Controller's host Activity is resumed
*/
protected void onActivityResumed(Activity activity) { }
protected void onActivityResumed(@NonNull Activity activity) { }
/**
* Called when this Controller's host Activity is paused
*/
protected void onActivityPaused(Activity activity) { }
protected void onActivityPaused(@NonNull Activity activity) { }
/**
* Called when this Controller's host Activity is stopped
*/
protected void onActivityStopped(Activity activity) { }
protected void onActivityStopped(@NonNull Activity activity) { }
/**
* Called to save this Controller's View state. As Views can be detached and destroyed as part of the
@@ -421,7 +467,7 @@ public abstract class Controller {
/**
* Calls startActivity(Intent) from this Controller's host Activity.
*/
public final void startActivity(final Intent intent) {
public final void startActivity(@NonNull final Intent intent) {
executeWithRouter(new RouterRequiringFunc() {
@Override public void execute() { router.startActivity(intent); }
});
@@ -430,7 +476,7 @@ public abstract class Controller {
/**
* Calls startActivityForResult(Intent, int) from this Controller's host Activity.
*/
public final void startActivityForResult(final Intent intent, final int requestCode) {
public final void startActivityForResult(@NonNull final Intent intent, final int requestCode) {
executeWithRouter(new RouterRequiringFunc() {
@Override public void execute() { router.startActivityForResult(instanceId, intent, requestCode); }
});
@@ -439,7 +485,7 @@ public abstract class Controller {
/**
* Calls startActivityForResult(Intent, int, Bundle) from this Controller's host Activity.
*/
public final void startActivityForResult(final Intent intent, final int requestCode, final Bundle options) {
public final void startActivityForResult(@NonNull final Intent intent, final int requestCode, @Nullable final Bundle options) {
executeWithRouter(new RouterRequiringFunc() {
@Override public void execute() { router.startActivityForResult(instanceId, intent, requestCode, options); }
});
@@ -465,7 +511,7 @@ public abstract class Controller {
* @param resultCode The resultCode that was returned to the host Activity's onActivityResult method
* @param data The data Intent that was returned to the host Activity's onActivityResult method
*/
public void onActivityResult(int requestCode, int resultCode, Intent data) { }
public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { }
/**
* Calls requestPermission(String[], int) from this Controller's host Activity. Results for this request,
@@ -506,9 +552,8 @@ public abstract class Controller {
* @return True if this Controller has consumed the back button press, otherwise false
*/
public boolean handleBack() {
Iterator<Controller> childIterator = childBackstack.descendingIterator();
while (childIterator.hasNext()) {
Controller childController = childIterator.next();
for (int i = childBackstack.size() - 1; i >= 0; i--) {
Controller childController = childBackstack.get(i);
if (childController.isAttached() && childController.getRouter().handleBack()) {
return true;
}
@@ -522,7 +567,7 @@ public abstract class Controller {
*
* @param lifecycleListener The listener
*/
public void addLifecycleListener(LifecycleListener lifecycleListener) {
public void addLifecycleListener(@NonNull LifecycleListener lifecycleListener) {
if (!lifecycleListeners.contains(lifecycleListener)) {
lifecycleListeners.add(lifecycleListener);
}
@@ -533,13 +578,14 @@ public abstract class Controller {
*
* @param lifecycleListener The listener to be removed
*/
public void removeLifecycleListener(LifecycleListener lifecycleListener) {
public void removeLifecycleListener(@NonNull LifecycleListener lifecycleListener) {
lifecycleListeners.remove(lifecycleListener);
}
/**
* Returns this Controller's {@link RetainViewMode}. Defaults to {@link RetainViewMode#RELEASE_DETACH}.
*/
@NonNull
public RetainViewMode getRetainViewMode() {
return retainViewMode;
}
@@ -548,8 +594,8 @@ public abstract class Controller {
* Sets this Controller's {@link RetainViewMode}, which will influence when its view will be released.
* This is useful when a Controller's view hierarchy is expensive to tear down and rebuild.
*/
public void setRetainViewMode(RetainViewMode retainViewMode) {
this.retainViewMode = retainViewMode;
public void setRetainViewMode(@NonNull RetainViewMode retainViewMode) {
this.retainViewMode = retainViewMode != null ? retainViewMode : RetainViewMode.RELEASE_DETACH;
if (this.retainViewMode == RetainViewMode.RELEASE_DETACH && !attached) {
removeViewReference();
}
@@ -559,6 +605,7 @@ public abstract class Controller {
* Returns the {@link ControllerChangeHandler} that should be used for pushing this Controller, or null
* if the handler from the {@link RouterTransaction} should be used instead.
*/
@Nullable
public final ControllerChangeHandler getOverriddenPushHandler() {
return overriddenPushHandler;
}
@@ -567,7 +614,7 @@ public abstract class Controller {
* Overrides the {@link ControllerChangeHandler} that should be used for pushing this Controller. If this is a
* non-null value, it will be used instead of the handler from the {@link RouterTransaction}.
*/
public void overridePushHandler(ControllerChangeHandler overriddenPushHandler) {
public void overridePushHandler(@Nullable ControllerChangeHandler overriddenPushHandler) {
this.overriddenPushHandler = overriddenPushHandler;
}
@@ -575,6 +622,7 @@ public abstract class Controller {
* Returns the {@link ControllerChangeHandler} that should be used for popping this Controller, or null
* if the handler from the {@link RouterTransaction} should be used instead.
*/
@Nullable
public ControllerChangeHandler getOverriddenPopHandler() {
return overriddenPopHandler;
}
@@ -583,7 +631,7 @@ public abstract class Controller {
* Overrides the {@link ControllerChangeHandler} that should be used for popping this Controller. If this is a
* non-null value, it will be used instead of the handler from the {@link RouterTransaction}.
*/
public void overridePopHandler(ControllerChangeHandler overriddenPopHandler) {
public void overridePopHandler(@Nullable ControllerChangeHandler overriddenPopHandler) {
this.overriddenPopHandler = overriddenPopHandler;
}
@@ -627,7 +675,7 @@ public abstract class Controller {
* @param menu The menu into which your options should be placed.
* @param inflater The inflater that can be used to inflate your menu items.
*/
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { }
public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) { }
/**
* Prepare the screen's options menu to be displayed. This is called directly before showing the
@@ -635,7 +683,7 @@ public abstract class Controller {
*
* @param menu The menu that will be displayed
*/
public void onPrepareOptionsMenu(Menu menu) { }
public void onPrepareOptionsMenu(@NonNull Menu menu) { }
/**
* Called when an option menu item has been selected by the user.
@@ -643,7 +691,7 @@ public abstract class Controller {
* @param item The selected item.
* @return True if this event has been consumed, false if it has not.
*/
public boolean onOptionsItemSelected(MenuItem item) {
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
return false;
}
@@ -695,25 +743,26 @@ public abstract class Controller {
}
}
final void activityStarted(Activity activity) {
final void activityStarted(@NonNull Activity activity) {
onActivityStarted(activity);
}
final void activityResumed(Activity activity) {
final void activityResumed(@NonNull Activity activity) {
if (!attached && view != null && viewIsAttached) {
attach(view);
} else if (attached) {
needsAttach = false;
hasSavedViewState = false;
}
onActivityResumed(activity);
}
final void activityPaused(Activity activity) {
final void activityPaused(@NonNull Activity activity) {
onActivityPaused(activity);
}
final void activityStopped(Activity activity) {
final void activityStopped(@NonNull Activity activity) {
onActivityStopped(activity);
}
@@ -726,9 +775,15 @@ public abstract class Controller {
}
private void attach(@NonNull View view) {
attachedToUnownedParent = router == null || view.getParent() != router.container;
if (attachedToUnownedParent) {
return;
}
hasSavedViewState = false;
for (LifecycleListener lifecycleListener : lifecycleListeners) {
List<LifecycleListener> listeners = new ArrayList<>(lifecycleListeners);
for (LifecycleListener lifecycleListener : listeners) {
lifecycleListener.preAttach(this, view);
}
@@ -741,20 +796,24 @@ public abstract class Controller {
router.invalidateOptionsMenu();
}
for (LifecycleListener lifecycleListener : lifecycleListeners) {
listeners = new ArrayList<>(lifecycleListeners);
for (LifecycleListener lifecycleListener : listeners) {
lifecycleListener.postAttach(this, view);
}
}
void detach(@NonNull View view, boolean forceViewRefRemoval) {
for (ControllerHostedRouter router : childRouters) {
router.prepareForHostDetach();
if (!attachedToUnownedParent) {
for (ControllerHostedRouter router : childRouters) {
router.prepareForHostDetach();
}
}
final boolean removeViewRef = forceViewRefRemoval || retainViewMode == RetainViewMode.RELEASE_DETACH || isBeingDestroyed;
if (attached) {
for (LifecycleListener lifecycleListener : lifecycleListeners) {
List<LifecycleListener> listeners = new ArrayList<>(lifecycleListeners);
for (LifecycleListener lifecycleListener : listeners) {
lifecycleListener.preDetach(this, view);
}
@@ -765,7 +824,8 @@ public abstract class Controller {
router.invalidateOptionsMenu();
}
for (LifecycleListener lifecycleListener : lifecycleListeners) {
listeners = new ArrayList<>(lifecycleListeners);
for (LifecycleListener lifecycleListener : listeners) {
lifecycleListener.postDetach(this, view);
}
}
@@ -781,13 +841,15 @@ public abstract class Controller {
saveViewState(view);
}
for (LifecycleListener lifecycleListener : lifecycleListeners) {
List<LifecycleListener> listeners = new ArrayList<>(lifecycleListeners);
for (LifecycleListener lifecycleListener : listeners) {
lifecycleListener.preDestroyView(this, view);
}
onDestroyView(view);
view.removeOnAttachStateChangeListener(onAttachStateChangeListener);
viewAttachHandler.unregisterAttachListener(view);
viewAttachHandler = null;
viewIsAttached = false;
if (isBeingDestroyed) {
@@ -795,7 +857,8 @@ public abstract class Controller {
}
view = null;
for (LifecycleListener lifecycleListener : lifecycleListeners) {
listeners = new ArrayList<>(lifecycleListeners);
for (LifecycleListener lifecycleListener : listeners) {
lifecycleListener.postDestroyView(this);
}
@@ -816,21 +879,26 @@ public abstract class Controller {
}
if (view == null) {
for (LifecycleListener lifecycleListener : lifecycleListeners) {
List<LifecycleListener> listeners = new ArrayList<>(lifecycleListeners);
for (LifecycleListener lifecycleListener : listeners) {
lifecycleListener.preCreateView(this);
}
view = onCreateView(LayoutInflater.from(parent.getContext()), parent);
if (view == parent) {
throw new IllegalStateException("Controller's onCreateView method returned the parent ViewGroup. Perhaps you forgot to pass false for LayoutInflater.inflate's attachToRoot parameter?");
}
for (LifecycleListener lifecycleListener : lifecycleListeners) {
listeners = new ArrayList<>(lifecycleListeners);
for (LifecycleListener lifecycleListener : listeners) {
lifecycleListener.postCreateView(this, view);
}
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;
@@ -839,7 +907,7 @@ public abstract class Controller {
}
@Override
public void onViewDetachedFromWindow(View v) {
public void onDetached(View v) {
viewIsAttached = false;
viewWasDetached = true;
@@ -847,17 +915,33 @@ public abstract class Controller {
detach(v, false);
}
}
};
view.addOnAttachStateChangeListener(onAttachStateChangeListener);
});
viewAttachHandler.listenForAttach(view);
} else if (retainViewMode == RetainViewMode.RETAIN_DETACH) {
restoreChildControllerHosts();
}
return view;
}
private void restoreChildControllerHosts() {
for (ControllerHostedRouter childRouter : childRouters) {
if (!childRouter.hasHost()) {
View containerView = view.findViewById(childRouter.getHostId());
if (containerView != null && containerView instanceof ViewGroup) {
childRouter.setHost(this, (ViewGroup)containerView);
monitorChildRouter(childRouter);
childRouter.rebindIfNeeded();
}
}
}
}
private void performDestroy() {
if (!destroyed) {
for (LifecycleListener lifecycleListener : lifecycleListeners) {
List<LifecycleListener> listeners = new ArrayList<>(lifecycleListeners);
for (LifecycleListener lifecycleListener : listeners) {
lifecycleListener.preDestroy(this);
}
@@ -867,7 +951,8 @@ public abstract class Controller {
parentController = null;
for (LifecycleListener lifecycleListener : lifecycleListeners) {
listeners = new ArrayList<>(lifecycleListeners);
for (LifecycleListener lifecycleListener : listeners) {
lifecycleListener.postDestroy(this);
}
}
@@ -885,20 +970,20 @@ public abstract class Controller {
}
for (ControllerHostedRouter childRouter : childRouters) {
childRouter.destroy();
childRouter.destroy(false);
}
if (!attached) {
removeViewReference();
} else if (removeViews) {
detach(view, false);
detach(view, true);
}
}
private void saveViewState(@NonNull View view) {
hasSavedViewState = true;
viewState = new Bundle();
viewState = new Bundle(getClass().getClassLoader());
SparseArray<Parcelable> hierarchyState = new SparseArray<>();
view.saveHierarchyState(hierarchyState);
@@ -908,7 +993,8 @@ public abstract class Controller {
onSaveViewState(view, stateBundle);
viewState.putBundle(KEY_VIEW_STATE_BUNDLE, stateBundle);
for (LifecycleListener lifecycleListener : lifecycleListeners) {
List<LifecycleListener> listeners = new ArrayList<>(lifecycleListeners);
for (LifecycleListener lifecycleListener : listeners) {
lifecycleListener.onSaveViewState(this, viewState);
}
}
@@ -918,19 +1004,10 @@ public abstract class Controller {
view.restoreHierarchyState(viewState.getSparseParcelableArray(KEY_VIEW_STATE_HIERARCHY));
onRestoreViewState(view, viewState.getBundle(KEY_VIEW_STATE_BUNDLE));
for (ControllerHostedRouter childRouter : childRouters) {
if (!childRouter.hasHost()) {
View containerView = view.findViewById(childRouter.getHostId());
restoreChildControllerHosts();
if (containerView != null && containerView instanceof ViewGroup) {
childRouter.setHost(this, (ViewGroup)containerView);
monitorChildRouter(childRouter);
childRouter.rebindIfNeeded();
}
}
}
for (LifecycleListener lifecycleListener : lifecycleListeners) {
List<LifecycleListener> listeners = new ArrayList<>(lifecycleListeners);
for (LifecycleListener lifecycleListener : listeners) {
lifecycleListener.onRestoreViewState(this, viewState);
}
}
@@ -966,10 +1043,17 @@ public abstract class Controller {
}
outState.putParcelableArrayList(KEY_CHILD_ROUTERS, childBundles);
ArrayList<String> childBackstack = new ArrayList<>();
for (Controller controller : this.childBackstack) {
childBackstack.add(controller.instanceId);
}
outState.putStringArrayList(KEY_CHILD_BACKSTACK, childBackstack);
Bundle savedState = new Bundle();
onSaveInstanceState(savedState);
for (LifecycleListener lifecycleListener : lifecycleListeners) {
List<LifecycleListener> listeners = new ArrayList<>(lifecycleListeners);
for (LifecycleListener lifecycleListener : listeners) {
lifecycleListener.onSaveInstanceState(this, savedState);
}
@@ -980,6 +1064,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));
@@ -996,6 +1084,14 @@ public abstract class Controller {
childRouters.add(childRouter);
}
List<String> childBackstackIds = savedInstanceState.getStringArrayList(KEY_CHILD_BACKSTACK);
for (String controllerId : childBackstackIds) {
Controller childController = findController(controllerId);
if (childController != null) {
childBackstack.add(childController);
}
}
this.savedInstanceState = savedInstanceState.getBundle(KEY_SAVED_STATE);
performOnRestoreInstanceState();
}
@@ -1004,7 +1100,8 @@ public abstract class Controller {
if (savedInstanceState != null && router != null) {
onRestoreInstanceState(savedInstanceState);
for (LifecycleListener lifecycleListener : lifecycleListeners) {
List<LifecycleListener> listeners = new ArrayList<>(lifecycleListeners);
for (LifecycleListener lifecycleListener : listeners) {
lifecycleListener.onRestoreInstanceState(this, savedInstanceState);
}
@@ -1012,7 +1109,7 @@ public abstract class Controller {
}
}
final void changeStarted(ControllerChangeHandler changeHandler, ControllerChangeType changeType) {
final void changeStarted(@NonNull ControllerChangeHandler changeHandler, @NonNull ControllerChangeType changeType) {
if (!changeType.isEnter) {
for (ControllerHostedRouter router : childRouters) {
router.setDetachFrozen(true);
@@ -1021,12 +1118,13 @@ public abstract class Controller {
onChangeStarted(changeHandler, changeType);
for (LifecycleListener lifecycleListener : lifecycleListeners) {
List<LifecycleListener> listeners = new ArrayList<>(lifecycleListeners);
for (LifecycleListener lifecycleListener : listeners) {
lifecycleListener.onChangeStart(this, changeHandler, changeType);
}
}
final void changeEnded(ControllerChangeHandler changeHandler, ControllerChangeType changeType) {
final void changeEnded(@NonNull ControllerChangeHandler changeHandler, @NonNull ControllerChangeType changeType) {
if (!changeType.isEnter) {
for (ControllerHostedRouter router : childRouters) {
router.setDetachFrozen(false);
@@ -1035,15 +1133,17 @@ public abstract class Controller {
onChangeEnded(changeHandler, changeType);
for (LifecycleListener lifecycleListener : lifecycleListeners) {
List<LifecycleListener> listeners = new ArrayList<>(lifecycleListeners);
for (LifecycleListener lifecycleListener : listeners) {
lifecycleListener.onChangeEnd(this, changeHandler, changeType);
}
if (isBeingDestroyed && !viewIsAttached && !attached && destroyedView != null) {
View view = destroyedView.get();
if (router.container != null && view.getParent() == router.container) {
if (router.container != null && view != null && view.getParent() == router.container) {
router.container.removeView(view);
}
destroyedView = null;
}
}
@@ -1061,27 +1161,27 @@ public abstract class Controller {
}
}
final void createOptionsMenu(Menu menu, MenuInflater inflater) {
final void createOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
if (attached && hasOptionsMenu && !optionsMenuHidden) {
onCreateOptionsMenu(menu, inflater);
}
}
final void prepareOptionsMenu(Menu menu) {
final void prepareOptionsMenu(@NonNull Menu menu) {
if (attached && hasOptionsMenu && !optionsMenuHidden) {
onPrepareOptionsMenu(menu);
}
}
final boolean optionsItemSelected(MenuItem item) {
final boolean optionsItemSelected(@NonNull MenuItem item) {
return attached && hasOptionsMenu && !optionsMenuHidden && onOptionsItemSelected(item);
}
private void monitorChildRouter(ControllerHostedRouter childRouter) {
childRouter.addChangeListener(childRouterChangeListener);
private void monitorChildRouter(@NonNull ControllerHostedRouter childRouter) {
childRouter.setOnControllerPushedListener(onControllerPushedListener);
}
private void onChildControllerPushed(Controller controller) {
private void onChildControllerPushed(@NonNull Controller controller) {
if (!childBackstack.contains(controller)) {
childBackstack.add(controller);
controller.addLifecycleListener(new LifecycleListener() {
@@ -1093,7 +1193,7 @@ public abstract class Controller {
}
}
final void setParentController(Controller controller) {
final void setParentController(@Nullable Controller controller) {
parentController = controller;
}
@@ -1104,7 +1204,8 @@ public abstract class Controller {
}
}
private static Constructor getDefaultConstructor(Constructor[] constructors) {
@Nullable
private static Constructor getDefaultConstructor(@NonNull Constructor[] constructors) {
for (Constructor constructor : constructors) {
if (constructor.getParameterTypes().length == 0) {
return constructor;
@@ -1113,7 +1214,8 @@ public abstract class Controller {
return null;
}
private static Constructor getBundleConstructor(Constructor[] constructors) {
@Nullable
private static Constructor getBundleConstructor(@NonNull Constructor[] constructors) {
for (Constructor constructor : constructors) {
if (constructor.getParameterTypes().length == 1 && constructor.getParameterTypes()[0] == Bundle.class) {
return constructor;
@@ -2,6 +2,7 @@ package com.bluelinelabs.conductor;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
@@ -27,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.
@@ -37,7 +39,7 @@ public abstract class ControllerChangeHandler {
* @param isPush True if this is a push transaction, false if it's a pop.
* @param changeListener This listener must be called when any transitions or animations are completed.
*/
public abstract void performChange(@NonNull ViewGroup container, View from, View to, boolean isPush, @NonNull ControllerChangeCompletedListener changeListener);
public abstract void performChange(@NonNull ViewGroup container, @Nullable View from, @Nullable View to, boolean isPush, @NonNull ControllerChangeCompletedListener changeListener);
public ControllerChangeHandler() {
ensureDefaultConstructor();
@@ -64,7 +66,7 @@ public abstract class ControllerChangeHandler {
* @param newHandler the change handler that has caused this push to be aborted
* @param newTop the controller that will now be at the top of the backstack
*/
public void onAbortPush(@NonNull ControllerChangeHandler newHandler, Controller newTop) { }
public void onAbortPush(@NonNull ControllerChangeHandler newHandler, @Nullable Controller newTop) { }
/**
* Will be called on change handlers that push a controller if the controller being pushed is
@@ -72,6 +74,27 @@ 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();
bundle.putString(KEY_CLASS_NAME, getClass().getName());
@@ -83,10 +106,6 @@ public abstract class ControllerChangeHandler {
return bundle;
}
final ControllerChangeHandler copy() {
return fromBundle(toBundle());
}
private void ensureDefaultConstructor() {
try {
getClass().getConstructor();
@@ -95,7 +114,8 @@ public abstract class ControllerChangeHandler {
}
}
public static ControllerChangeHandler fromBundle(Bundle bundle) {
@Nullable
public static ControllerChangeHandler fromBundle(@Nullable Bundle bundle) {
if (bundle != null) {
String className = bundle.getString(KEY_CLASS_NAME);
ControllerChangeHandler changeHandler = ClassUtils.newInstance(className);
@@ -107,7 +127,7 @@ public abstract class ControllerChangeHandler {
}
}
static boolean completePushImmediately(String controllerInstanceId) {
static boolean completePushImmediately(@NonNull String controllerInstanceId) {
ControllerChangeHandler changeHandler = inProgressPushHandlers.get(controllerInstanceId);
if (changeHandler != null) {
changeHandler.completeImmediately();
@@ -117,7 +137,7 @@ public abstract class ControllerChangeHandler {
return false;
}
public static void abortPush(Controller toAbort, Controller newController, ControllerChangeHandler newChangeHandler) {
public static void abortPush(@NonNull Controller toAbort, @Nullable Controller newController, @NonNull ControllerChangeHandler newChangeHandler) {
ControllerChangeHandler handlerForPush = inProgressPushHandlers.get(toAbort.getInstanceId());
if (handlerForPush != null) {
handlerForPush.onAbortPush(newChangeHandler, newController);
@@ -125,22 +145,38 @@ public abstract class ControllerChangeHandler {
}
}
public static void executeChange(final Controller to, final Controller from, boolean isPush, ViewGroup container, ControllerChangeHandler inHandler) {
public static void executeChange(@Nullable final Controller to, @Nullable final Controller from, boolean isPush, @NonNull ViewGroup container, @NonNull ControllerChangeHandler inHandler) {
executeChange(to, from, isPush, container, inHandler, new ArrayList<ControllerChangeListener>());
}
public static void executeChange(final Controller to, final Controller from, final boolean isPush, final ViewGroup container, final ControllerChangeHandler inHandler, @NonNull final List<ControllerChangeListener> listeners) {
public static void executeChange(@Nullable final Controller to, @Nullable final Controller from, final boolean isPush, @Nullable final ViewGroup container, @Nullable final ControllerChangeHandler inHandler, @NonNull final List<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);
if (from != null) {
completePushImmediately(from.getInstanceId());
}
} else if (!isPush && from != null) {
abortPush(from, to, handler);
}
for (ControllerChangeListener listener : listeners) {
listener.onChangeStarted(to, from, isPush, container, inHandler);
listener.onChangeStarted(to, from, isPush, container, handler);
}
final ControllerChangeType toChangeType = isPush ? ControllerChangeType.PUSH_ENTER : ControllerChangeType.POP_ENTER;
@@ -175,7 +211,7 @@ public abstract class ControllerChangeHandler {
}
for (ControllerChangeListener listener : listeners) {
listener.onChangeCompleted(to, from, isPush, container, inHandler);
listener.onChangeCompleted(to, from, isPush, container, handler);
}
if (handler.forceRemoveViewOnPush && fromView != null) {
@@ -210,7 +246,7 @@ public abstract class ControllerChangeHandler {
* @param container The containing ViewGroup
* @param handler The change handler being used.
*/
void onChangeStarted(Controller to, Controller from, boolean isPush, ViewGroup container, ControllerChangeHandler handler);
void onChangeStarted(@Nullable Controller to, @Nullable Controller from, boolean isPush, @NonNull ViewGroup container, @NonNull ControllerChangeHandler handler);
/**
* Called when a {@link ControllerChangeHandler} has completed changing {@link Controller}s
@@ -221,7 +257,7 @@ public abstract class ControllerChangeHandler {
* @param container The containing ViewGroup
* @param handler The change handler that was used.
*/
void onChangeCompleted(Controller to, Controller from, boolean isPush, ViewGroup container, ControllerChangeHandler handler);
void onChangeCompleted(@Nullable Controller to, @Nullable Controller from, boolean isPush, @NonNull ViewGroup container, @NonNull ControllerChangeHandler handler);
}
/**
@@ -5,6 +5,7 @@ import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.IdRes;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.view.ViewGroup;
import com.bluelinelabs.conductor.ControllerChangeHandler.ControllerChangeListener;
@@ -24,7 +25,7 @@ class ControllerHostedRouter extends Router {
ControllerHostedRouter() { }
ControllerHostedRouter(int hostId, String tag) {
ControllerHostedRouter(int hostId, @Nullable String tag) {
this.hostId = hostId;
this.tag = tag;
}
@@ -71,25 +72,25 @@ class ControllerHostedRouter extends Router {
}
@Override
void destroy() {
void destroy(boolean popViews) {
setDetachFrozen(false);
super.destroy();
super.destroy(popViews);
}
@Override
@Override @Nullable
public Activity getActivity() {
return hostController != null ? hostController.getActivity() : null;
}
@Override
public void onActivityDestroyed(Activity activity) {
public void onActivityDestroyed(@NonNull Activity activity) {
super.onActivityDestroyed(activity);
removeHost();
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
if (hostController != null && hostController.getRouter() != null) {
hostController.getRouter().onActivityResult(requestCode, resultCode, data);
}
@@ -103,42 +104,42 @@ class ControllerHostedRouter extends Router {
}
@Override
void startActivity(Intent intent) {
void startActivity(@NonNull Intent intent) {
if (hostController != null && hostController.getRouter() != null) {
hostController.getRouter().startActivity(intent);
}
}
@Override
void startActivityForResult(String instanceId, Intent intent, int requestCode) {
void startActivityForResult(@NonNull String instanceId, @NonNull Intent intent, int requestCode) {
if (hostController != null && hostController.getRouter() != null) {
hostController.getRouter().startActivityForResult(instanceId, intent, requestCode);
}
}
@Override
void startActivityForResult(String instanceId, Intent intent, int requestCode, Bundle options) {
void startActivityForResult(@NonNull String instanceId, @NonNull Intent intent, int requestCode, @Nullable Bundle options) {
if (hostController != null && hostController.getRouter() != null) {
hostController.getRouter().startActivityForResult(instanceId, intent, requestCode, options);
}
}
@Override
void registerForActivityResult(String instanceId, int requestCode) {
void registerForActivityResult(@NonNull String instanceId, int requestCode) {
if (hostController != null && hostController.getRouter() != null) {
hostController.getRouter().registerForActivityResult(instanceId, requestCode);
}
}
@Override
void unregisterForActivityResults(String instanceId) {
void unregisterForActivityResults(@NonNull String instanceId) {
if (hostController != null && hostController.getRouter() != null) {
hostController.getRouter().unregisterForActivityResults(instanceId);
}
}
@Override
void requestPermissions(String instanceId, String[] permissions, int requestCode) {
void requestPermissions(@NonNull String instanceId, @NonNull String[] permissions, int requestCode) {
if (hostController != null && hostController.getRouter() != null) {
hostController.getRouter().requestPermissions(instanceId, permissions, requestCode);
}
@@ -150,7 +151,7 @@ class ControllerHostedRouter extends Router {
}
@Override
public void saveInstanceState(Bundle outState) {
public void saveInstanceState(@NonNull Bundle outState) {
super.saveInstanceState(outState);
outState.putInt(KEY_HOST_ID, hostId);
@@ -158,7 +159,7 @@ class ControllerHostedRouter extends Router {
}
@Override
public void restoreInstanceState(Bundle savedInstanceState) {
public void restoreInstanceState(@NonNull Bundle savedInstanceState) {
super.restoreInstanceState(savedInstanceState);
hostId = savedInstanceState.getInt(KEY_HOST_ID);
@@ -166,7 +167,7 @@ class ControllerHostedRouter extends Router {
}
@Override
void setControllerRouter(Controller controller) {
void setControllerRouter(@NonNull Controller controller) {
super.setControllerRouter(controller);
controller.setParentController(hostController);
}
@@ -175,11 +176,12 @@ class ControllerHostedRouter extends Router {
return hostId;
}
@Nullable
String getTag() {
return tag;
}
@Override
@Override @NonNull
List<Router> getSiblingRouters() {
List<Router> list = new ArrayList<>();
list.addAll(hostController.getChildRouters());
@@ -187,7 +189,7 @@ class ControllerHostedRouter extends Router {
return list;
}
@Override
@Override @NonNull
Router getRootRouter() {
if (hostController != null && hostController.getRouter() != null) {
return hostController.getRouter().getRootRouter();
@@ -14,8 +14,23 @@ import android.view.ViewGroup;
*/
abstract public class RestoreViewOnCreateController extends Controller {
@NonNull
@Override
/**
* Convenience constructor for use when no arguments are needed.
*/
protected RestoreViewOnCreateController() {
super(null);
}
/**
* Constructor that takes arguments that need to be retained across restarts.
*
* @param args Any arguments that need to be retained.
*/
protected RestoreViewOnCreateController(@Nullable Bundle args) {
super(args);
}
@Override @NonNull
protected final View onCreateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) {
return onCreateView(inflater, container, viewState);
}
@@ -4,6 +4,8 @@ import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.UiThread;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
@@ -31,6 +33,7 @@ public abstract class Router {
private static final String KEY_POPS_LAST_VIEW = "Router.popsLastView";
protected final Backstack backstack = new Backstack();
private OnControllerPushedListener onControllerPushedListener;
private final List<ControllerChangeListener> changeListeners = new ArrayList<>();
final List<Controller> destroyingControllers = new ArrayList<>();
@@ -41,6 +44,7 @@ public abstract class Router {
/**
* Returns this Router's host Activity
*/
@Nullable
public abstract Activity getActivity();
/**
@@ -51,7 +55,7 @@ public abstract class Router {
* @param resultCode The Activity's onActivityResult resultCode
* @param data The Activity's onActivityResult data
*/
public abstract void onActivityResult(int requestCode, int resultCode, Intent data);
public abstract void onActivityResult(int requestCode, int resultCode, @Nullable Intent data);
/**
* This should be called by the host Activity when its onRequestPermissionsResult method is called. The call will be forwarded
@@ -62,7 +66,7 @@ public abstract class Router {
* @param permissions The Activity's onRequestPermissionsResult permissions
* @param grantResults The Activity's onRequestPermissionsResult grantResults
*/
public void onRequestPermissionsResult(String instanceId, int requestCode, String[] permissions, int[] grantResults) {
public void onRequestPermissionsResult(@NonNull String instanceId, int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
Controller controller = getControllerWithInstanceId(instanceId);
if (controller != null) {
controller.requestPermissionsResult(requestCode, permissions, grantResults);
@@ -75,8 +79,10 @@ public abstract class Router {
*
* @return Whether or not a back action was handled by the Router
*/
@UiThread
public boolean handleBack() {
if (!backstack.isEmpty()) {
//noinspection ConstantConditions
if (backstack.peek().controller.handleBack()) {
return true;
} else if (popCurrentController()) {
@@ -92,8 +98,13 @@ public abstract class Router {
*
* @return Whether or not this Router still has controllers remaining on it after popping.
*/
@UiThread
public boolean popCurrentController() {
return popController(backstack.peek().controller);
RouterTransaction transaction = backstack.peek();
if (transaction == null) {
throw new IllegalStateException("Trying to pop the current controller when there are none on the backstack.");
}
return popController(transaction.controller);
}
/**
@@ -102,7 +113,8 @@ public abstract class Router {
* @param controller The controller that should be popped from this Router
* @return Whether or not this Router still has controllers remaining on it after popping.
*/
public boolean popController(Controller controller) {
@UiThread
public boolean popController(@NonNull Controller controller) {
RouterTransaction topController = backstack.peek();
boolean poppingTopController = topController != null && topController.controller == controller;
@@ -134,6 +146,7 @@ public abstract class Router {
* @param transaction The transaction detailing what should be pushed, including the {@link Controller},
* and its push and pop {@link ControllerChangeHandler}, and its tag.
*/
@UiThread
public void pushController(@NonNull RouterTransaction transaction) {
RouterTransaction from = backstack.peek();
pushToBackstack(transaction);
@@ -146,6 +159,7 @@ public abstract class Router {
* @param transaction The transaction detailing what should be pushed, including the {@link Controller},
* and its push and pop {@link ControllerChangeHandler}, and its tag.
*/
@UiThread
public void replaceTopController(@NonNull RouterTransaction transaction) {
RouterTransaction topTransaction = backstack.peek();
if (!backstack.isEmpty()) {
@@ -153,11 +167,13 @@ public abstract class Router {
}
final ControllerChangeHandler handler = transaction.pushChangeHandler();
final boolean oldHandlerRemovedViews = topTransaction.pushChangeHandler() == null || topTransaction.pushChangeHandler().removesFromViewOnPush();
final boolean newHandlerRemovesViews = handler == null || handler.removesFromViewOnPush();
if (!oldHandlerRemovedViews && newHandlerRemovesViews) {
for (RouterTransaction visibleTransaction : getVisibleTransactions(backstack.iterator())) {
performControllerChange(null, visibleTransaction.controller, true, handler != null ? handler.copy() : new SimpleSwapChangeHandler());
if (topTransaction != null) {
final boolean oldHandlerRemovedViews = topTransaction.pushChangeHandler() == null || topTransaction.pushChangeHandler().removesFromViewOnPush();
final boolean newHandlerRemovesViews = handler == null || handler.removesFromViewOnPush();
if (!oldHandlerRemovedViews && newHandlerRemovesViews) {
for (RouterTransaction visibleTransaction : getVisibleTransactions(backstack.iterator())) {
performControllerChange(null, visibleTransaction.controller, true, handler);
}
}
}
@@ -169,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());
@@ -189,6 +205,7 @@ public abstract class Router {
* in the stack. This defaults to false so that the developer can either finish its containing Activity or otherwise
* hide its parent view without any strange artifacting.
*/
@NonNull
public Router setPopsLastView(boolean popsLastView) {
this.popsLastView = popsLastView;
return this;
@@ -199,6 +216,7 @@ public abstract class Router {
*
* @return Whether or not any {@link Controller}s were popped in order to get to the root transaction
*/
@UiThread
public boolean popToRoot() {
return popToRoot(null);
}
@@ -209,8 +227,10 @@ public abstract class Router {
* @param changeHandler The {@link ControllerChangeHandler} to handle this transaction
* @return Whether or not any {@link Controller}s were popped in order to get to the root transaction
*/
public boolean popToRoot(ControllerChangeHandler changeHandler) {
@UiThread
public boolean popToRoot(@Nullable ControllerChangeHandler changeHandler) {
if (backstack.size() > 1) {
//noinspection ConstantConditions
popToTransaction(backstack.root(), changeHandler);
return true;
} else {
@@ -224,6 +244,7 @@ public abstract class Router {
* @param tag The tag being popped to
* @return Whether or not any {@link Controller}s were popped in order to get to the transaction with the passed tag
*/
@UiThread
public boolean popToTag(@NonNull String tag) {
return popToTag(tag, null);
}
@@ -235,7 +256,8 @@ public abstract class Router {
* @param changeHandler The {@link ControllerChangeHandler} to handle this transaction
* @return Whether or not the {@link Controller} with the passed tag is now at the top
*/
public boolean popToTag(@NonNull String tag, ControllerChangeHandler changeHandler) {
@UiThread
public boolean popToTag(@NonNull String tag, @Nullable ControllerChangeHandler changeHandler) {
for (RouterTransaction transaction : backstack) {
if (tag.equals(transaction.tag())) {
popToTransaction(transaction, changeHandler);
@@ -251,30 +273,11 @@ public abstract class Router {
* @param transaction The transaction detailing what should be pushed, including the {@link Controller},
* and its push and pop {@link ControllerChangeHandler}, and its tag.
*/
@UiThread
public void setRoot(@NonNull RouterTransaction transaction) {
ControllerChangeHandler newHandler = transaction.pushChangeHandler() != null ? transaction.pushChangeHandler() : new SimpleSwapChangeHandler();
List<RouterTransaction> visibleTransactions = getVisibleTransactions(backstack.iterator());
RouterTransaction rootTransaction = visibleTransactions.size() > 0 ? visibleTransactions.get(0) : null;
removeAllExceptVisibleAndUnowned();
trackDestroyingControllers(backstack.popAll());
pushToBackstack(transaction);
for (int i = visibleTransactions.size() - 1; i > 0; i--) {
if (visibleTransactions.get(i).controller.getView() == null) {
ControllerChangeHandler.abortPush(visibleTransactions.get(i).controller, transaction.controller, newHandler);
} else {
performControllerChange(null, visibleTransactions.get(i).controller, true, newHandler);
}
}
if (rootTransaction != null && rootTransaction.controller.getView() == null) {
ControllerChangeHandler.abortPush(rootTransaction.controller, transaction.controller, newHandler);
}
performControllerChange(transaction, rootTransaction, true);
List<RouterTransaction> transactions = new ArrayList<>();
transactions.add(transaction);
setBackstack(transactions, transaction.pushChangeHandler());
}
/**
@@ -283,7 +286,8 @@ public abstract class Router {
* @param instanceId The instance ID being searched for
* @return The matching Controller, if one exists
*/
public Controller getControllerWithInstanceId(String instanceId) {
@Nullable
public Controller getControllerWithInstanceId(@NonNull String instanceId) {
for (RouterTransaction transaction : backstack) {
Controller controllerWithId = transaction.controller.findController(instanceId);
if (controllerWithId != null) {
@@ -299,7 +303,8 @@ public abstract class Router {
* @param tag The tag being searched for
* @return The matching Controller, if one exists
*/
public Controller getControllerWithTag(String tag) {
@Nullable
public Controller getControllerWithTag(@NonNull String tag) {
for (RouterTransaction transaction : backstack) {
if (tag.equals(transaction.tag())) {
return transaction.controller;
@@ -318,6 +323,7 @@ public abstract class Router {
/**
* Returns the current backstack, ordered from root to most recently pushed.
*/
@NonNull
public List<RouterTransaction> getBackstack() {
List<RouterTransaction> list = new ArrayList<>();
Iterator<RouterTransaction> backstackIterator = backstack.reverseIterator();
@@ -334,52 +340,51 @@ public abstract class Router {
* @param newBackstack The new backstack
* @param changeHandler An optional change handler to be used to handle the root view of transition
*/
public void setBackstack(@NonNull List<RouterTransaction> newBackstack, ControllerChangeHandler changeHandler) {
@UiThread
public void setBackstack(@NonNull List<RouterTransaction> newBackstack, @Nullable ControllerChangeHandler changeHandler) {
List<RouterTransaction> oldVisibleTransactions = getVisibleTransactions(backstack.iterator());
removeAllExceptVisibleAndUnowned();
backstack.setBackstack(newBackstack);
for (RouterTransaction transaction : backstack) {
transaction.onAttachedToRouter();
}
if (newBackstack.size() > 0) {
List<RouterTransaction> reverseNewBackstack = new ArrayList<>(newBackstack);
Collections.reverse(reverseNewBackstack);
List<RouterTransaction> newVisibleTransactions = getVisibleTransactions(reverseNewBackstack.iterator());
boolean visibleTransactionsChanged = newVisibleTransactions.size() != oldVisibleTransactions.size();
if (!visibleTransactionsChanged) {
for (int i = 0; i < oldVisibleTransactions.size(); i++) {
if (oldVisibleTransactions.get(i).controller != newVisibleTransactions.get(i).controller) {
visibleTransactionsChanged = true;
break;
}
}
}
boolean visibleTransactionsChanged = !backstacksAreEqual(newVisibleTransactions, oldVisibleTransactions);
if (visibleTransactionsChanged) {
ControllerChangeHandler handler = changeHandler != null ? changeHandler : new SimpleSwapChangeHandler();
Controller rootController = oldVisibleTransactions.size() > 0 ? oldVisibleTransactions.get(0).controller : null;
performControllerChange(newVisibleTransactions.get(0).controller, rootController, true, handler.copy());
performControllerChange(newVisibleTransactions.get(0).controller, rootController, true, 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.copy());
performControllerChange(transaction.controller, newVisibleTransactions.get(i - 1).controller, true, transaction.pushChangeHandler());
}
}
// Ensure all new controllers have a valid router set
for (RouterTransaction transaction : newBackstack) {
transaction.controller.setRouter(this);
}
}
for (RouterTransaction transaction : backstack) {
transaction.onAttachedToRouter();
if (onControllerPushedListener != null) {
for (RouterTransaction transaction : newBackstack) {
onControllerPushedListener.onControllerPushed(transaction.controller);
}
}
backstack.setBackstack(newBackstack);
}
/**
@@ -394,7 +399,7 @@ public abstract class Router {
*
* @param changeListener The listener
*/
public void addChangeListener(ControllerChangeListener changeListener) {
public void addChangeListener(@NonNull ControllerChangeListener changeListener) {
if (!changeListeners.contains(changeListener)) {
changeListeners.add(changeListener);
}
@@ -405,13 +410,14 @@ public abstract class Router {
*
* @param changeListener The listener to be removed
*/
public void removeChangeListener(ControllerChangeListener changeListener) {
public void removeChangeListener(@NonNull ControllerChangeListener changeListener) {
changeListeners.remove(changeListener);
}
/**
* Attaches this Router's existing backstack to its container if one exists.
*/
@UiThread
public void rebindIfNeeded() {
Iterator<RouterTransaction> backstackIterator = backstack.reverseIterator();
while (backstackIterator.hasNext()) {
@@ -423,14 +429,14 @@ public abstract class Router {
}
}
public final void onActivityResult(String instanceId, int requestCode, int resultCode, Intent data) {
public final void onActivityResult(@NonNull String instanceId, int requestCode, int resultCode, @Nullable Intent data) {
Controller controller = getControllerWithInstanceId(instanceId);
if (controller != null) {
controller.onActivityResult(requestCode, resultCode, data);
}
}
public final void onActivityStarted(Activity activity) {
public final void onActivityStarted(@NonNull Activity activity) {
for (RouterTransaction transaction : backstack) {
transaction.controller.activityStarted(activity);
@@ -440,7 +446,7 @@ public abstract class Router {
}
}
public final void onActivityResumed(Activity activity) {
public final void onActivityResumed(@NonNull Activity activity) {
for (RouterTransaction transaction : backstack) {
transaction.controller.activityResumed(activity);
@@ -450,7 +456,7 @@ public abstract class Router {
}
}
public final void onActivityPaused(Activity activity) {
public final void onActivityPaused(@NonNull Activity activity) {
for (RouterTransaction transaction : backstack) {
transaction.controller.activityPaused(activity);
@@ -460,7 +466,7 @@ public abstract class Router {
}
}
public final void onActivityStopped(Activity activity) {
public final void onActivityStopped(@NonNull Activity activity) {
for (RouterTransaction transaction : backstack) {
transaction.controller.activityStopped(activity);
@@ -470,7 +476,7 @@ public abstract class Router {
}
}
public void onActivityDestroyed(Activity activity) {
public void onActivityDestroyed(@NonNull Activity activity) {
prepareForContainerRemoval();
changeListeners.clear();
@@ -503,7 +509,7 @@ public abstract class Router {
}
}
public void saveInstanceState(Bundle outState) {
public void saveInstanceState(@NonNull Bundle outState) {
prepareForHostDetach();
Bundle backstackState = new Bundle();
@@ -513,7 +519,7 @@ public abstract class Router {
outState.putBoolean(KEY_POPS_LAST_VIEW, popsLastView);
}
public void restoreInstanceState(Bundle savedInstanceState) {
public void restoreInstanceState(@NonNull Bundle savedInstanceState) {
Bundle backstackBundle = savedInstanceState.getParcelable(KEY_BACKSTACK);
backstack.restoreInstanceState(backstackBundle);
popsLastView = savedInstanceState.getBoolean(KEY_POPS_LAST_VIEW);
@@ -524,7 +530,7 @@ public abstract class Router {
}
}
public final void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
public final void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
for (RouterTransaction transaction : backstack) {
transaction.controller.createOptionsMenu(menu, inflater);
@@ -534,7 +540,7 @@ public abstract class Router {
}
}
public final void onPrepareOptionsMenu(Menu menu) {
public final void onPrepareOptionsMenu(@NonNull Menu menu) {
for (RouterTransaction transaction : backstack) {
transaction.controller.prepareOptionsMenu(menu);
@@ -544,7 +550,7 @@ public abstract class Router {
}
}
public final boolean onOptionsItemSelected(MenuItem item) {
public final boolean onOptionsItemSelected(@NonNull MenuItem item) {
for (RouterTransaction transaction : backstack) {
if (transaction.controller.optionsItemSelected(item)) {
return true;
@@ -559,7 +565,7 @@ public abstract class Router {
return false;
}
private void popToTransaction(@NonNull RouterTransaction transaction, ControllerChangeHandler changeHandler) {
private void popToTransaction(@NonNull RouterTransaction transaction, @Nullable ControllerChangeHandler changeHandler) {
RouterTransaction topTransaction = backstack.peek();
List<RouterTransaction> poppedTransactions = backstack.popTo(transaction);
trackDestroyingControllers(poppedTransactions);
@@ -573,12 +579,17 @@ public abstract class Router {
}
}
final void setOnControllerPushedListener(OnControllerPushedListener listener) {
onControllerPushedListener = listener;
}
void prepareForContainerRemoval() {
if (container != null) {
container.setOnHierarchyChangeListener(null);
}
}
@NonNull
final List<Controller> getControllers() {
List<Controller> controllers = new ArrayList<>();
@@ -590,6 +601,7 @@ public abstract class Router {
return controllers;
}
@Nullable
public final Boolean handleRequestedPermission(@NonNull String permission) {
for (RouterTransaction transaction : backstack) {
if (transaction.controller.didRequestPermission(permission)) {
@@ -599,7 +611,7 @@ public abstract class Router {
return null;
}
private void performControllerChange(RouterTransaction to, RouterTransaction from, boolean isPush) {
private void performControllerChange(@Nullable RouterTransaction to, @Nullable RouterTransaction from, boolean isPush) {
if (isPush && to != null) {
to.onAttachedToRouter();
}
@@ -611,7 +623,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;
@@ -620,7 +632,7 @@ public abstract class Router {
performControllerChange(toController, fromController, isPush, changeHandler);
}
private void performControllerChange(final Controller to, final Controller from, boolean isPush, @NonNull ControllerChangeHandler changeHandler) {
private void performControllerChange(@Nullable final Controller to, @Nullable final Controller from, boolean isPush, @Nullable ControllerChangeHandler changeHandler) {
if (to != null) {
setControllerRouter(to);
} else if (backstack.size() == 0 && !popsLastView) {
@@ -634,9 +646,13 @@ public abstract class Router {
private void pushToBackstack(@NonNull RouterTransaction entry) {
backstack.push(entry);
if (onControllerPushedListener != null) {
onControllerPushedListener.onControllerPushed(entry.controller);
}
}
private void trackDestroyingController(RouterTransaction transaction) {
private void trackDestroyingController(@NonNull RouterTransaction transaction) {
if (!transaction.controller.isDestroyed()) {
destroyingControllers.add(transaction.controller);
@@ -649,7 +665,7 @@ public abstract class Router {
}
}
private void trackDestroyingControllers(List<RouterTransaction> transactions) {
private void trackDestroyingControllers(@NonNull List<RouterTransaction> transactions) {
for (RouterTransaction transaction : transactions) {
trackDestroyingController(transaction);
}
@@ -679,7 +695,7 @@ public abstract class Router {
}
}
private void addRouterViewsToList(Router router, List<View> list) {
private void addRouterViewsToList(@NonNull Router router, @NonNull List<View> list) {
for (Controller controller : router.getControllers()) {
if (controller.getView() != null) {
list.add(controller.getView());
@@ -691,7 +707,7 @@ public abstract class Router {
}
}
private List<RouterTransaction> getVisibleTransactions(Iterator<RouterTransaction> backstackIterator) {
private List<RouterTransaction> getVisibleTransactions(@NonNull Iterator<RouterTransaction> backstackIterator) {
List<RouterTransaction> transactions = new ArrayList<>();
while (backstackIterator.hasNext()) {
RouterTransaction transaction = backstackIterator.next();
@@ -706,18 +722,36 @@ public abstract class Router {
return transactions;
}
void setControllerRouter(Controller controller) {
private boolean backstacksAreEqual(List<RouterTransaction> lhs, List<RouterTransaction> rhs) {
if (lhs.size() != rhs.size()) {
return false;
}
for (int i = 0; i < rhs.size(); i++) {
if (rhs.get(i).controller() != lhs.get(i).controller()) {
return false;
}
}
return true;
}
void setControllerRouter(@NonNull Controller controller) {
controller.setRouter(this);
}
abstract void invalidateOptionsMenu();
abstract void startActivity(Intent intent);
abstract void startActivityForResult(String instanceId, Intent intent, int requestCode);
abstract void startActivityForResult(String instanceId, Intent intent, int requestCode, Bundle options);
abstract void registerForActivityResult(String instanceId, int requestCode);
abstract void unregisterForActivityResults(String instanceId);
abstract void requestPermissions(String instanceId, String[] permissions, int requestCode);
abstract void startActivity(@NonNull Intent intent);
abstract void startActivityForResult(@NonNull String instanceId, @NonNull Intent intent, int requestCode);
abstract void startActivityForResult(@NonNull String instanceId, @NonNull Intent intent, int requestCode, @Nullable Bundle options);
abstract void registerForActivityResult(@NonNull String instanceId, int requestCode);
abstract void unregisterForActivityResults(@NonNull String instanceId);
abstract void requestPermissions(@NonNull String instanceId, @NonNull String[] permissions, int requestCode);
abstract boolean hasHost();
abstract List<Router> getSiblingRouters();
abstract Router getRootRouter();
@NonNull abstract List<Router> getSiblingRouters();
@NonNull abstract Router getRootRouter();
interface OnControllerPushedListener {
void onControllerPushed(Controller controller);
}
}
@@ -2,6 +2,7 @@ package com.bluelinelabs.conductor;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
/**
* Metadata used for adding {@link Controller}s to a {@link Router}.
@@ -21,6 +22,7 @@ public class RouterTransaction {
private ControllerChangeHandler popControllerChangeHandler;
private boolean attachedToRouter;
@NonNull
public static RouterTransaction with(@NonNull Controller controller) {
return new RouterTransaction(controller);
}
@@ -41,15 +43,18 @@ public class RouterTransaction {
attachedToRouter = true;
}
@NonNull
public Controller controller() {
return controller;
}
@Nullable
public String tag() {
return tag;
}
public RouterTransaction tag(String tag) {
@NonNull
public RouterTransaction tag(@Nullable String tag) {
if (!attachedToRouter) {
this.tag = tag;
return this;
@@ -58,6 +63,7 @@ public class RouterTransaction {
}
}
@Nullable
public ControllerChangeHandler pushChangeHandler() {
ControllerChangeHandler handler = controller.getOverriddenPushHandler();
if (handler == null) {
@@ -66,7 +72,8 @@ public class RouterTransaction {
return handler;
}
public RouterTransaction pushChangeHandler(ControllerChangeHandler handler) {
@NonNull
public RouterTransaction pushChangeHandler(@Nullable ControllerChangeHandler handler) {
if (!attachedToRouter) {
pushControllerChangeHandler = handler;
return this;
@@ -75,6 +82,7 @@ public class RouterTransaction {
}
}
@Nullable
public ControllerChangeHandler popChangeHandler() {
ControllerChangeHandler handler = controller.getOverriddenPopHandler();
if (handler == null) {
@@ -83,7 +91,8 @@ public class RouterTransaction {
return handler;
}
public RouterTransaction popChangeHandler(ControllerChangeHandler handler) {
@NonNull
public RouterTransaction popChangeHandler(@Nullable ControllerChangeHandler handler) {
if (!attachedToRouter) {
popControllerChangeHandler = handler;
return this;
@@ -95,6 +104,7 @@ public class RouterTransaction {
/**
* Used to serialize this transaction into a Bundle
*/
@NonNull
public Bundle saveInstanceState() {
Bundle bundle = new Bundle();
@@ -1,9 +1,11 @@
package com.bluelinelabs.conductor.changehandler;
import android.animation.Animator;
import android.animation.Animator.AnimatorListener;
import android.animation.AnimatorListenerAdapter;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
@@ -25,6 +27,7 @@ public abstract class AnimatorChangeHandler extends ControllerChangeHandler {
private boolean removesFromViewOnPush;
private boolean canceled;
private boolean needsImmediateCompletion;
private boolean completed;
private Animator animator;
public AnimatorChangeHandler() {
@@ -59,7 +62,7 @@ public abstract class AnimatorChangeHandler extends ControllerChangeHandler {
}
@Override
public void onAbortPush(@NonNull ControllerChangeHandler newHandler, Controller newTop) {
public void onAbortPush(@NonNull ControllerChangeHandler newHandler, @Nullable Controller newTop) {
super.onAbortPush(newHandler, newTop);
canceled = true;
@@ -74,7 +77,7 @@ public abstract class AnimatorChangeHandler extends ControllerChangeHandler {
needsImmediateCompletion = true;
if (animator != null) {
animator.cancel();
animator.end();
}
}
@@ -96,7 +99,8 @@ public abstract class AnimatorChangeHandler extends ControllerChangeHandler {
* @param isPush True if this is a push transaction, false if it's a pop.
* @param toAddedToContainer True if the "to" view was added to the container as a part of this ChangeHandler. False if it was already in the hierarchy.
*/
protected abstract Animator getAnimator(@NonNull ViewGroup container, View from, View to, boolean isPush, boolean toAddedToContainer);
@NonNull
protected abstract Animator getAnimator(@NonNull ViewGroup container, @Nullable View from, @Nullable View to, boolean isPush, boolean toAddedToContainer);
/**
* Will be called after the animation is complete to reset the View that was removed to its pre-animation state.
@@ -104,7 +108,7 @@ public abstract class AnimatorChangeHandler extends ControllerChangeHandler {
protected abstract void resetFromView(@NonNull View from);
@Override
public final void performChange(@NonNull final ViewGroup container, final View from, final View to, final boolean isPush, @NonNull final ControllerChangeCompletedListener changeListener) {
public final void performChange(@NonNull final ViewGroup container, @Nullable final View from, @Nullable final View to, final boolean isPush, @NonNull final ControllerChangeCompletedListener changeListener) {
boolean readyToAnimate = true;
final boolean addingToView = to != null && to.getParent() == null;
@@ -136,9 +140,24 @@ public abstract class AnimatorChangeHandler extends ControllerChangeHandler {
}
}
private void performAnimation(@NonNull final ViewGroup container, final View from, final View to, final boolean isPush, final boolean toAddedToContainer, @NonNull final ControllerChangeCompletedListener changeListener) {
if (canceled) {
private void complete(@NonNull ControllerChangeCompletedListener changeListener, @Nullable AnimatorListener animatorListener) {
if (!completed) {
completed = true;
changeListener.onChangeCompleted();
}
if (animator != null) {
if (animatorListener != null) {
animator.removeListener(animatorListener);
}
animator.end();
animator = null;
}
}
private void performAnimation(@NonNull final ViewGroup container, @Nullable final View from, @Nullable final View to, final boolean isPush, final boolean toAddedToContainer, @NonNull final ControllerChangeCompletedListener changeListener) {
if (canceled) {
complete(changeListener, null);
return;
}
@@ -155,9 +174,7 @@ public abstract class AnimatorChangeHandler extends ControllerChangeHandler {
container.removeView(from);
}
changeListener.onChangeCompleted();
animator.removeListener(this);
animator = null;
complete(changeListener, this);
}
@Override
@@ -167,14 +184,11 @@ public abstract class AnimatorChangeHandler extends ControllerChangeHandler {
container.removeView(from);
}
changeListener.onChangeCompleted();
animator.removeListener(this);
complete(changeListener, this);
if (isPush && from != null) {
resetFromView(from);
}
animator = null;
}
}
});
@@ -3,21 +3,28 @@ package com.bluelinelabs.conductor.changehandler;
import android.annotation.TargetApi;
import android.os.Build;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.transition.AutoTransition;
import android.transition.Transition;
import android.view.View;
import android.view.ViewGroup;
import com.bluelinelabs.conductor.ControllerChangeHandler;
/**
* A change handler that will use an AutoTransition.
*/
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public class AutoTransitionChangeHandler extends TransitionChangeHandler {
@Override
@NonNull
protected Transition getTransition(@NonNull ViewGroup container, View from, View to, boolean isPush) {
@Override @NonNull
protected Transition getTransition(@NonNull ViewGroup container, @Nullable View from, @Nullable View to, boolean isPush) {
return new AutoTransition();
}
@Override @NonNull
public ControllerChangeHandler copy() {
return new AutoTransitionChangeHandler();
}
}
@@ -4,9 +4,12 @@ import android.animation.Animator;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.view.View;
import android.view.ViewGroup;
import com.bluelinelabs.conductor.ControllerChangeHandler;
/**
* An {@link AnimatorChangeHandler} that will cross fade two views
*/
@@ -26,8 +29,8 @@ public class FadeChangeHandler extends AnimatorChangeHandler {
super(duration, removesFromViewOnPush);
}
@Override
protected Animator getAnimator(@NonNull ViewGroup container, View from, View to, boolean isPush, boolean toAddedToContainer) {
@Override @NonNull
protected Animator getAnimator(@NonNull ViewGroup container, @Nullable View from, @Nullable View to, boolean isPush, boolean toAddedToContainer) {
AnimatorSet animator = new AnimatorSet();
if (to != null && toAddedToContainer) {
animator.play(ObjectAnimator.ofFloat(to, View.ALPHA, 0, 1));
@@ -44,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());
}
}
@@ -4,9 +4,12 @@ import android.animation.Animator;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.view.View;
import android.view.ViewGroup;
import com.bluelinelabs.conductor.ControllerChangeHandler;
/**
* An {@link AnimatorChangeHandler} that will slide the views left or right, depending on if it's a push or pop.
*/
@@ -26,8 +29,8 @@ public class HorizontalChangeHandler extends AnimatorChangeHandler {
super(duration, removesFromViewOnPush);
}
@Override
protected Animator getAnimator(@NonNull ViewGroup container, View from, View to, boolean isPush, boolean toAddedToContainer) {
@Override @NonNull
protected Animator getAnimator(@NonNull ViewGroup container, @Nullable View from, @Nullable View to, boolean isPush, boolean toAddedToContainer) {
AnimatorSet animatorSet = new AnimatorSet();
if (isPush) {
@@ -53,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());
}
}
@@ -2,6 +2,7 @@ package com.bluelinelabs.conductor.changehandler;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.view.View;
import android.view.View.OnAttachStateChangeListener;
import android.view.ViewGroup;
@@ -44,7 +45,7 @@ public class SimpleSwapChangeHandler extends ControllerChangeHandler implements
}
@Override
public void onAbortPush(@NonNull ControllerChangeHandler newHandler, Controller newTop) {
public void onAbortPush(@NonNull ControllerChangeHandler newHandler, @Nullable Controller newTop) {
super.onAbortPush(newHandler, newTop);
canceled = true;
@@ -62,7 +63,7 @@ public class SimpleSwapChangeHandler extends ControllerChangeHandler implements
}
@Override
public void performChange(@NonNull ViewGroup container, View from, View to, boolean isPush, @NonNull ControllerChangeCompletedListener changeListener) {
public void performChange(@NonNull ViewGroup container, @Nullable View from, @Nullable View to, boolean isPush, @NonNull ControllerChangeCompletedListener changeListener) {
if (!canceled) {
if (from != null && (!isPush || removesFromViewOnPush)) {
container.removeView(from);
@@ -89,7 +90,7 @@ public class SimpleSwapChangeHandler extends ControllerChangeHandler implements
}
@Override
public void onViewAttachedToWindow(View v) {
public void onViewAttachedToWindow(@NonNull View v) {
v.removeOnAttachStateChangeListener(this);
if (changeListener != null) {
@@ -100,5 +101,15 @@ public class SimpleSwapChangeHandler extends ControllerChangeHandler implements
}
@Override
public void onViewDetachedFromWindow(View v) { }
public void onViewDetachedFromWindow(@NonNull View v) { }
@Override @NonNull
public ControllerChangeHandler copy() {
return new SimpleSwapChangeHandler(removesFromViewOnPush());
}
@Override
public boolean isReusable() {
return true;
}
}
@@ -3,6 +3,7 @@ package com.bluelinelabs.conductor.changehandler;
import android.annotation.TargetApi;
import android.os.Build;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.transition.Transition;
import android.transition.Transition.TransitionListener;
import android.transition.TransitionManager;
@@ -29,17 +30,17 @@ public abstract class TransitionChangeHandler extends ControllerChangeHandler {
* @param isPush True if this is a push transaction, false if it's a pop.
*/
@NonNull
protected abstract Transition getTransition(@NonNull ViewGroup container, View from, View to, boolean isPush);
protected abstract Transition getTransition(@NonNull ViewGroup container, @Nullable View from, @Nullable View to, boolean isPush);
@Override
public void onAbortPush(@NonNull ControllerChangeHandler newHandler, Controller newTop) {
public void onAbortPush(@NonNull ControllerChangeHandler newHandler, @Nullable Controller newTop) {
super.onAbortPush(newHandler, newTop);
canceled = true;
}
@Override
public void performChange(@NonNull final ViewGroup container, View from, View to, boolean isPush, @NonNull final ControllerChangeCompletedListener changeListener) {
public void performChange(@NonNull final ViewGroup container, @Nullable View from, @Nullable View to, boolean isPush, @NonNull final ControllerChangeCompletedListener changeListener) {
if (canceled) {
changeListener.onChangeCompleted();
return;
@@ -71,7 +72,7 @@ public abstract class TransitionChangeHandler extends ControllerChangeHandler {
if (from != null) {
container.removeView(from);
}
if (to != null) {
if (to != null && to.getParent() == null) {
container.addView(to);
}
}
@@ -80,4 +81,5 @@ public abstract class TransitionChangeHandler extends ControllerChangeHandler {
public final boolean removesFromViewOnPush() {
return true;
}
}
@@ -3,6 +3,7 @@ package com.bluelinelabs.conductor.changehandler;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.view.View;
import android.view.ViewGroup;
@@ -15,13 +16,10 @@ import com.bluelinelabs.conductor.internal.ClassUtils;
*/
public class TransitionChangeHandlerCompat extends ControllerChangeHandler {
private static final String KEY_TRANSITION_HANDLER_CLASS = "TransitionChangeHandlerCompat.transitionChangeHandler.class";
private static final String KEY_FALLBACK_HANDLER_CLASS = "TransitionChangeHandlerCompat.fallbackChangeHandler.class";
private static final String KEY_TRANSITION_HANDLER_STATE = "TransitionChangeHandlerCompat.transitionChangeHandler.state";
private static final String KEY_FALLBACK_HANDLER_STATE = "TransitionChangeHandlerCompat.fallbackChangeHandler.state";
private static final String KEY_CHANGE_HANDLER_CLASS = "TransitionChangeHandlerCompat.changeHandler.class";
private static final String KEY_HANDLER_STATE = "TransitionChangeHandlerCompat.changeHandler.state";
private TransitionChangeHandler transitionChangeHandler;
private ControllerChangeHandler fallbackChangeHandler;
private ControllerChangeHandler changeHandler;
public TransitionChangeHandlerCompat() { }
@@ -32,57 +30,52 @@ public class TransitionChangeHandlerCompat extends ControllerChangeHandler {
* @param transitionChangeHandler The change handler that will be used on API 21 and above
* @param fallbackChangeHandler The change handler that will be used on APIs below 21
*/
public TransitionChangeHandlerCompat(TransitionChangeHandler transitionChangeHandler, ControllerChangeHandler fallbackChangeHandler) {
this.transitionChangeHandler = transitionChangeHandler;
this.fallbackChangeHandler = fallbackChangeHandler;
public TransitionChangeHandlerCompat(@NonNull TransitionChangeHandler transitionChangeHandler, @NonNull ControllerChangeHandler fallbackChangeHandler) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
changeHandler = transitionChangeHandler;
} else {
changeHandler = fallbackChangeHandler;
}
}
@Override
public void performChange(@NonNull final ViewGroup container, View from, View to, boolean isPush, @NonNull final ControllerChangeCompletedListener changeListener) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
transitionChangeHandler.performChange(container, from, to, isPush, changeListener);
} else {
fallbackChangeHandler.performChange(container, from, to, isPush, changeListener);
}
public void performChange(@NonNull final ViewGroup container, @Nullable View from, @Nullable View to, boolean isPush, @NonNull final ControllerChangeCompletedListener changeListener) {
changeHandler.performChange(container, from, to, isPush, changeListener);
}
@Override
public void saveToBundle(@NonNull Bundle bundle) {
super.saveToBundle(bundle);
bundle.putString(KEY_TRANSITION_HANDLER_CLASS, transitionChangeHandler.getClass().getName());
bundle.putString(KEY_FALLBACK_HANDLER_CLASS, fallbackChangeHandler.getClass().getName());
bundle.putString(KEY_CHANGE_HANDLER_CLASS, changeHandler.getClass().getName());
Bundle transitionBundle = new Bundle();
transitionChangeHandler.saveToBundle(transitionBundle);
bundle.putBundle(KEY_TRANSITION_HANDLER_STATE, transitionBundle);
Bundle fallbackBundle = new Bundle();
fallbackChangeHandler.saveToBundle(fallbackBundle);
bundle.putBundle(KEY_FALLBACK_HANDLER_STATE, fallbackBundle);
Bundle stateBundle = new Bundle();
changeHandler.saveToBundle(stateBundle);
bundle.putBundle(KEY_HANDLER_STATE, stateBundle);
}
@Override
public void restoreFromBundle(@NonNull Bundle bundle) {
super.restoreFromBundle(bundle);
String transitionClassName = bundle.getString(KEY_TRANSITION_HANDLER_CLASS);
transitionChangeHandler = ClassUtils.newInstance(transitionClassName);
String className = bundle.getString(KEY_CHANGE_HANDLER_CLASS);
changeHandler = ClassUtils.newInstance(className);
//noinspection ConstantConditions
transitionChangeHandler.restoreFromBundle(bundle.getBundle(KEY_TRANSITION_HANDLER_STATE));
String fallbackClassName = bundle.getString(KEY_FALLBACK_HANDLER_CLASS);
fallbackChangeHandler = ClassUtils.newInstance(fallbackClassName);
//noinspection ConstantConditions
fallbackChangeHandler.restoreFromBundle(bundle.getBundle(KEY_FALLBACK_HANDLER_STATE));
changeHandler.restoreFromBundle(bundle.getBundle(KEY_HANDLER_STATE));
}
@Override
public boolean removesFromViewOnPush() {
return changeHandler.removesFromViewOnPush();
}
@Override @NonNull
public ControllerChangeHandler copy() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
return transitionChangeHandler.removesFromViewOnPush();
return new TransitionChangeHandlerCompat((TransitionChangeHandler)changeHandler.copy(), null);
} else {
return fallbackChangeHandler.removesFromViewOnPush();
return new TransitionChangeHandlerCompat(null, changeHandler.copy());
}
}
}
@@ -4,9 +4,12 @@ import android.animation.Animator;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.view.View;
import android.view.ViewGroup;
import com.bluelinelabs.conductor.ControllerChangeHandler;
import java.util.ArrayList;
import java.util.List;
@@ -30,8 +33,8 @@ public class VerticalChangeHandler extends AnimatorChangeHandler {
super(duration, removesFromViewOnPush);
}
@Override
protected Animator getAnimator(@NonNull ViewGroup container, View from, View to, boolean isPush, boolean toAddedToContainer) {
@Override @NonNull
protected Animator getAnimator(@NonNull ViewGroup container, @Nullable View from, @Nullable View to, boolean isPush, boolean toAddedToContainer) {
AnimatorSet animator = new AnimatorSet();
List<Animator> viewAnimators = new ArrayList<>();
@@ -48,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());
}
}
@@ -1,16 +1,13 @@
package com.bluelinelabs.conductor.internal;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.TextUtils;
public class ClassUtils {
@SuppressWarnings("unchecked")
public static <T> Class<? extends T> classForName(String className) {
return classForName(className, true);
}
@SuppressWarnings("unchecked")
public static <T> Class<? extends T> classForName(String className, boolean allowEmptyName) {
@Nullable @SuppressWarnings("unchecked")
public static <T> Class<? extends T> classForName(@NonNull String className, boolean allowEmptyName) {
if (allowEmptyName && TextUtils.isEmpty(className)) {
return null;
}
@@ -22,10 +19,10 @@ public class ClassUtils {
}
}
@SuppressWarnings("unchecked")
public static <T> T newInstance(String className) {
@Nullable @SuppressWarnings("unchecked")
public static <T> T newInstance(@NonNull String className) {
try {
Class<? extends T> cls = classForName(className);
Class<? extends T> cls = classForName(className, true);
return cls != null ? cls.newInstance() : null;
} catch (Exception e) {
throw new RuntimeException("An exception occurred while creating a new instance of " + className + ". " + e.getMessage());
@@ -8,7 +8,10 @@ import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.SparseArray;
import android.view.Menu;
import android.view.MenuInflater;
@@ -27,6 +30,7 @@ public class LifecycleHandler extends Fragment implements ActivityLifecycleCallb
private static final String FRAGMENT_TAG = "LifecycleHandler";
private static final String KEY_PENDING_PERMISSION_REQUESTS = "LifecycleHandler.pendingPermissionRequests";
private static final String KEY_PERMISSION_REQUEST_CODES = "LifecycleHandler.permissionRequests";
private static final String KEY_ACTIVITY_REQUEST_CODES = "LifecycleHandler.activityRequests";
private static final String KEY_ROUTER_STATE_PREFIX = "LifecycleHandler.routerState";
@@ -34,9 +38,11 @@ public class LifecycleHandler extends Fragment implements ActivityLifecycleCallb
private Activity activity;
private boolean hasRegisteredCallbacks;
private boolean destroyed;
private boolean attached;
private SparseArray<String> permissionRequestMap = new SparseArray<>();
private SparseArray<String> activityRequestMap = new SparseArray<>();
private ArrayList<PendingPermissionRequest> pendingPermissionRequests = new ArrayList<>();
private final Map<Integer, ActivityHostedRouter> routerMap = new HashMap<>();
@@ -45,7 +51,8 @@ public class LifecycleHandler extends Fragment implements ActivityLifecycleCallb
setHasOptionsMenu(true);
}
private static LifecycleHandler findInActivity(Activity activity) {
@Nullable
private static LifecycleHandler findInActivity(@NonNull Activity activity) {
LifecycleHandler lifecycleHandler = (LifecycleHandler)activity.getFragmentManager().findFragmentByTag(FRAGMENT_TAG);
if (lifecycleHandler != null) {
lifecycleHandler.registerActivityListener(activity);
@@ -53,7 +60,8 @@ public class LifecycleHandler extends Fragment implements ActivityLifecycleCallb
return lifecycleHandler;
}
public static LifecycleHandler install(Activity activity) {
@NonNull
public static LifecycleHandler install(@NonNull Activity activity) {
LifecycleHandler lifecycleHandler = findInActivity(activity);
if (lifecycleHandler == null) {
lifecycleHandler = new LifecycleHandler();
@@ -63,7 +71,8 @@ public class LifecycleHandler extends Fragment implements ActivityLifecycleCallb
return lifecycleHandler;
}
public Router getRouter(ViewGroup container, Bundle savedInstanceState) {
@NonNull
public Router getRouter(@NonNull ViewGroup container, @Nullable Bundle savedInstanceState) {
ActivityHostedRouter router = routerMap.get(getRouterHashKey(container));
if (router == null) {
router = new ActivityHostedRouter();
@@ -83,19 +92,21 @@ public class LifecycleHandler extends Fragment implements ActivityLifecycleCallb
return router;
}
@NonNull
public List<Router> getRouters() {
return new ArrayList<Router>(routerMap.values());
}
@Nullable
public Activity getLifecycleActivity() {
return activity;
}
private static int getRouterHashKey(ViewGroup viewGroup) {
private static int getRouterHashKey(@NonNull ViewGroup viewGroup) {
return viewGroup.getId();
}
private void registerActivityListener(Activity activity) {
private void registerActivityListener(@NonNull Activity activity) {
this.activity = activity;
if (!hasRegisteredCallbacks) {
@@ -110,10 +121,13 @@ public class LifecycleHandler extends Fragment implements ActivityLifecycleCallb
if (savedInstanceState != null) {
StringSparseArrayParceler permissionParcel = savedInstanceState.getParcelable(KEY_PERMISSION_REQUEST_CODES);
permissionRequestMap = permissionParcel != null ? permissionParcel.getStringSparseArray() : null;
permissionRequestMap = permissionParcel != null ? permissionParcel.getStringSparseArray() : new SparseArray<String>();
StringSparseArrayParceler activityParcel = savedInstanceState.getParcelable(KEY_ACTIVITY_REQUEST_CODES);
activityRequestMap = activityParcel != null ? activityParcel.getStringSparseArray() : null;
activityRequestMap = activityParcel != null ? activityParcel.getStringSparseArray() : new SparseArray<String>();
ArrayList<PendingPermissionRequest> pendingRequests = savedInstanceState.getParcelableArrayList(KEY_PENDING_PERMISSION_REQUESTS);
pendingPermissionRequests = pendingRequests != null ? pendingRequests : new ArrayList<PendingPermissionRequest>();
}
}
@@ -123,6 +137,7 @@ public class LifecycleHandler extends Fragment implements ActivityLifecycleCallb
outState.putParcelable(KEY_PERMISSION_REQUEST_CODES, new StringSparseArrayParceler(permissionRequestMap));
outState.putParcelable(KEY_ACTIVITY_REQUEST_CODES, new StringSparseArrayParceler(activityRequestMap));
outState.putParcelableArrayList(KEY_PENDING_PERMISSION_REQUESTS, pendingPermissionRequests);
}
@Override
@@ -141,21 +156,35 @@ public class LifecycleHandler extends Fragment implements ActivityLifecycleCallb
public void onAttach(Activity activity) {
super.onAttach(activity);
destroyed = false;
setAttached();
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
destroyed = false;
setAttached();
}
@Override
public void onDetach() {
super.onDetach();
attached = false;
destroyRouters();
}
private void setAttached() {
if (!attached) {
attached = true;
for (int i = pendingPermissionRequests.size() - 1; i >= 0; i--) {
PendingPermissionRequest request = pendingPermissionRequests.remove(i);
requestPermissions(request.instanceId, request.permissions, request.requestCode);
}
}
}
private void destroyRouters() {
if (!destroyed) {
destroyed = true;
@@ -232,11 +261,11 @@ public class LifecycleHandler extends Fragment implements ActivityLifecycleCallb
return super.onOptionsItemSelected(item);
}
public void registerForActivityResult(String instanceId, int requestCode) {
public void registerForActivityResult(@NonNull String instanceId, int requestCode) {
activityRequestMap.put(requestCode, instanceId);
}
public void unregisterForActivityResults(String instanceId) {
public void unregisterForActivityResults(@NonNull String instanceId) {
for (int i = activityRequestMap.size() - 1; i >= 0; i--) {
if (instanceId.equals(activityRequestMap.get(activityRequestMap.keyAt(i)))) {
activityRequestMap.removeAt(i);
@@ -244,20 +273,24 @@ public class LifecycleHandler extends Fragment implements ActivityLifecycleCallb
}
}
public void startActivityForResult(String instanceId, Intent intent, int requestCode) {
public void startActivityForResult(@NonNull String instanceId, @NonNull Intent intent, int requestCode) {
registerForActivityResult(instanceId, requestCode);
startActivityForResult(intent, requestCode);
}
public void startActivityForResult(String instanceId, Intent intent, int requestCode, Bundle options) {
public void startActivityForResult(@NonNull String instanceId, @NonNull Intent intent, int requestCode, @Nullable Bundle options) {
registerForActivityResult(instanceId, requestCode);
startActivityForResult(intent, requestCode, options);
}
@TargetApi(Build.VERSION_CODES.M)
public void requestPermissions(String instanceId, String[] permissions, int requestCode) {
permissionRequestMap.put(requestCode, instanceId);
requestPermissions(permissions, requestCode);
public void requestPermissions(@NonNull String instanceId, @NonNull String[] permissions, int requestCode) {
if (attached) {
permissionRequestMap.put(requestCode, instanceId);
requestPermissions(permissions, requestCode);
} else {
pendingPermissionRequests.add(new PendingPermissionRequest(instanceId, permissions, requestCode));
}
}
@Override
@@ -316,4 +349,47 @@ public class LifecycleHandler extends Fragment implements ActivityLifecycleCallb
@Override
public void onActivityDestroyed(Activity activity) { }
private static class PendingPermissionRequest implements Parcelable {
final String instanceId;
final String[] permissions;
final int requestCode;
PendingPermissionRequest(@NonNull String instanceId, @NonNull String[] permissions, int requestCode) {
this.instanceId = instanceId;
this.permissions = permissions;
this.requestCode = requestCode;
}
private PendingPermissionRequest(Parcel in) {
instanceId = in.readString();
permissions = in.createStringArray();
requestCode = in.readInt();
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel out, int flags) {
out.writeString(instanceId);
out.writeStringArray(permissions);
out.writeInt(requestCode);
}
public static final Parcelable.Creator<PendingPermissionRequest> CREATOR = new Parcelable.Creator<PendingPermissionRequest>() {
@Override
public PendingPermissionRequest createFromParcel(Parcel in) {
return new PendingPermissionRequest(in);
}
@Override
public PendingPermissionRequest[] newArray(int size) {
return new PendingPermissionRequest[size];
}
};
}
}
@@ -1,6 +1,7 @@
package com.bluelinelabs.conductor.internal;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.view.View;
import android.view.ViewGroup;
@@ -9,8 +10,18 @@ import com.bluelinelabs.conductor.ControllerChangeHandler;
public class NoOpControllerChangeHandler extends ControllerChangeHandler {
@Override
public void performChange(@NonNull ViewGroup container, @NonNull View from, @NonNull View to, boolean isPush, @NonNull ControllerChangeCompletedListener changeListener) {
public void performChange(@NonNull ViewGroup container, @Nullable View from, @Nullable View to, boolean isPush, @NonNull ControllerChangeCompletedListener changeListener) {
changeListener.onChangeCompleted();
}
@NonNull
@Override
public ControllerChangeHandler copy() {
return new NoOpControllerChangeHandler();
}
@Override
public boolean isReusable() {
return true;
}
}
@@ -2,17 +2,18 @@ package com.bluelinelabs.conductor.internal;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.NonNull;
import android.util.SparseArray;
public class StringSparseArrayParceler implements Parcelable {
private final SparseArray<String> stringSparseArray;
public StringSparseArrayParceler(SparseArray<String> stringSparseArray) {
public StringSparseArrayParceler(@NonNull SparseArray<String> stringSparseArray) {
this.stringSparseArray = stringSparseArray;
}
private StringSparseArrayParceler(Parcel in) {
private StringSparseArrayParceler(@NonNull Parcel in) {
stringSparseArray = new SparseArray<>();
final int size = in.readInt();
@@ -22,14 +23,17 @@ public class StringSparseArrayParceler implements Parcelable {
}
}
@NonNull
public SparseArray<String> getStringSparseArray() {
return stringSparseArray;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel out, int flags) {
final int size = stringSparseArray.size();
@@ -44,10 +48,12 @@ public class StringSparseArrayParceler implements Parcelable {
}
public static final Parcelable.Creator<StringSparseArrayParceler> CREATOR = new Parcelable.Creator<StringSparseArrayParceler>() {
@Override
public StringSparseArrayParceler createFromParcel(Parcel in) {
return new StringSparseArrayParceler(in);
}
@Override
public StringSparseArrayParceler[] newArray(int size) {
return new StringSparseArrayParceler[size];
}
@@ -0,0 +1,111 @@
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 (childOnAttachStateChangeListener != null && 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();
v.removeOnAttachStateChangeListener(this);
childOnAttachStateChangeListener = null;
}
}
@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;
}
}
}
@@ -1,9 +1,12 @@
package com.bluelinelabs.conductor;
import org.junit.Assert;
import com.bluelinelabs.conductor.util.TestController;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
public class BackstackTests {
private Backstack backstack;
@@ -15,20 +18,20 @@ public class BackstackTests {
@Test
public void testPush() {
Assert.assertEquals(0, backstack.size());
assertEquals(0, backstack.size());
backstack.push(RouterTransaction.with(new TestController()));
Assert.assertEquals(1, backstack.size());
assertEquals(1, backstack.size());
}
@Test
public void testPop() {
backstack.push(RouterTransaction.with(new TestController()));
backstack.push(RouterTransaction.with(new TestController()));
Assert.assertEquals(2, backstack.size());
assertEquals(2, backstack.size());
backstack.pop();
Assert.assertEquals(1, backstack.size());
assertEquals(1, backstack.size());
backstack.pop();
Assert.assertEquals(0, backstack.size());
assertEquals(0, backstack.size());
}
@Test
@@ -37,13 +40,13 @@ public class BackstackTests {
RouterTransaction transaction2 = RouterTransaction.with(new TestController());
backstack.push(transaction1);
Assert.assertEquals(transaction1, backstack.peek());
assertEquals(transaction1, backstack.peek());
backstack.push(transaction2);
Assert.assertEquals(transaction2, backstack.peek());
assertEquals(transaction2, backstack.peek());
backstack.pop();
Assert.assertEquals(transaction1, backstack.peek());
assertEquals(transaction1, backstack.peek());
}
@Test
@@ -56,11 +59,11 @@ public class BackstackTests {
backstack.push(transaction2);
backstack.push(transaction3);
Assert.assertEquals(3, backstack.size());
assertEquals(3, backstack.size());
backstack.popTo(transaction1);
Assert.assertEquals(1, backstack.size());
Assert.assertEquals(transaction1, backstack.peek());
assertEquals(1, backstack.size());
assertEquals(transaction1, backstack.peek());
}
}
@@ -2,10 +2,12 @@ package com.bluelinelabs.conductor;
import com.bluelinelabs.conductor.changehandler.FadeChangeHandler;
import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler;
import com.bluelinelabs.conductor.util.TestController;
import org.junit.Assert;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
public class ControllerChangeHandlerTests {
@Test
@@ -21,17 +23,17 @@ public class ControllerChangeHandlerTests {
ControllerChangeHandler restoredHorizontal = restoredTransaction.pushChangeHandler();
ControllerChangeHandler restoredFade = restoredTransaction.popChangeHandler();
Assert.assertEquals(horizontalChangeHandler.getClass(), restoredHorizontal.getClass());
Assert.assertEquals(fadeChangeHandler.getClass(), restoredFade.getClass());
assertEquals(horizontalChangeHandler.getClass(), restoredHorizontal.getClass());
assertEquals(fadeChangeHandler.getClass(), restoredFade.getClass());
HorizontalChangeHandler restoredHorizontalCast = (HorizontalChangeHandler)restoredHorizontal;
FadeChangeHandler restoredFadeCast = (FadeChangeHandler)restoredFade;
Assert.assertEquals(horizontalChangeHandler.getAnimationDuration(), restoredHorizontalCast.getAnimationDuration());
Assert.assertEquals(horizontalChangeHandler.removesFromViewOnPush(), restoredHorizontalCast.removesFromViewOnPush());
assertEquals(horizontalChangeHandler.getAnimationDuration(), restoredHorizontalCast.getAnimationDuration());
assertEquals(horizontalChangeHandler.removesFromViewOnPush(), restoredHorizontalCast.removesFromViewOnPush());
Assert.assertEquals(fadeChangeHandler.getAnimationDuration(), restoredFadeCast.getAnimationDuration());
Assert.assertEquals(fadeChangeHandler.removesFromViewOnPush(), restoredFadeCast.removesFromViewOnPush());
assertEquals(fadeChangeHandler.getAnimationDuration(), restoredFadeCast.getAnimationDuration());
assertEquals(fadeChangeHandler.removesFromViewOnPush(), restoredFadeCast.removesFromViewOnPush());
}
}
@@ -6,15 +6,21 @@ import android.view.View;
import android.view.ViewGroup;
import com.bluelinelabs.conductor.Controller.LifecycleListener;
import com.bluelinelabs.conductor.MockChangeHandler.ChangeHandlerListener;
import com.bluelinelabs.conductor.util.ActivityProxy;
import com.bluelinelabs.conductor.util.CallState;
import com.bluelinelabs.conductor.util.MockChangeHandler;
import com.bluelinelabs.conductor.util.MockChangeHandler.ChangeHandlerListener;
import com.bluelinelabs.conductor.util.TestController;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
@RunWith(RobolectricTestRunner.class)
@Config(manifest = Config.NONE)
public class ControllerLifecycleTests {
@@ -60,7 +66,7 @@ public class ControllerLifecycleTests {
router.popCurrentController();
Assert.assertNull(controller.getView());
assertNull(controller.getView());
assertCalls(expectedCallState, controller);
}
@@ -194,212 +200,212 @@ public class ControllerLifecycleTests {
@Override
public void preCreateView(@NonNull Controller controller) {
callState.createViewCalls++;
Assert.assertEquals(1, callState.createViewCalls);
Assert.assertEquals(0, testController.currentCallState.createViewCalls);
assertEquals(1, callState.createViewCalls);
assertEquals(0, testController.currentCallState.createViewCalls);
Assert.assertEquals(0, callState.attachCalls);
Assert.assertEquals(0, testController.currentCallState.attachCalls);
assertEquals(0, callState.attachCalls);
assertEquals(0, testController.currentCallState.attachCalls);
Assert.assertEquals(0, callState.detachCalls);
Assert.assertEquals(0, testController.currentCallState.detachCalls);
assertEquals(0, callState.detachCalls);
assertEquals(0, testController.currentCallState.detachCalls);
Assert.assertEquals(0, callState.destroyViewCalls);
Assert.assertEquals(0, testController.currentCallState.destroyViewCalls);
assertEquals(0, callState.destroyViewCalls);
assertEquals(0, testController.currentCallState.destroyViewCalls);
Assert.assertEquals(0, callState.destroyCalls);
Assert.assertEquals(0, testController.currentCallState.destroyCalls);
assertEquals(0, callState.destroyCalls);
assertEquals(0, testController.currentCallState.destroyCalls);
}
@Override
public void postCreateView(@NonNull Controller controller, @NonNull View view) {
callState.createViewCalls++;
Assert.assertEquals(2, callState.createViewCalls);
Assert.assertEquals(1, testController.currentCallState.createViewCalls);
assertEquals(2, callState.createViewCalls);
assertEquals(1, testController.currentCallState.createViewCalls);
Assert.assertEquals(0, callState.attachCalls);
Assert.assertEquals(0, testController.currentCallState.attachCalls);
assertEquals(0, callState.attachCalls);
assertEquals(0, testController.currentCallState.attachCalls);
Assert.assertEquals(0, callState.detachCalls);
Assert.assertEquals(0, testController.currentCallState.detachCalls);
assertEquals(0, callState.detachCalls);
assertEquals(0, testController.currentCallState.detachCalls);
Assert.assertEquals(0, callState.destroyViewCalls);
Assert.assertEquals(0, testController.currentCallState.destroyViewCalls);
assertEquals(0, callState.destroyViewCalls);
assertEquals(0, testController.currentCallState.destroyViewCalls);
Assert.assertEquals(0, callState.destroyCalls);
Assert.assertEquals(0, testController.currentCallState.destroyCalls);
assertEquals(0, callState.destroyCalls);
assertEquals(0, testController.currentCallState.destroyCalls);
}
@Override
public void preAttach(@NonNull Controller controller, @NonNull View view) {
callState.attachCalls++;
Assert.assertEquals(2, callState.createViewCalls);
Assert.assertEquals(1, testController.currentCallState.createViewCalls);
assertEquals(2, callState.createViewCalls);
assertEquals(1, testController.currentCallState.createViewCalls);
Assert.assertEquals(1, callState.attachCalls);
Assert.assertEquals(0, testController.currentCallState.attachCalls);
assertEquals(1, callState.attachCalls);
assertEquals(0, testController.currentCallState.attachCalls);
Assert.assertEquals(0, callState.detachCalls);
Assert.assertEquals(0, testController.currentCallState.detachCalls);
assertEquals(0, callState.detachCalls);
assertEquals(0, testController.currentCallState.detachCalls);
Assert.assertEquals(0, callState.destroyViewCalls);
Assert.assertEquals(0, testController.currentCallState.destroyViewCalls);
assertEquals(0, callState.destroyViewCalls);
assertEquals(0, testController.currentCallState.destroyViewCalls);
Assert.assertEquals(0, callState.destroyCalls);
Assert.assertEquals(0, testController.currentCallState.destroyCalls);
assertEquals(0, callState.destroyCalls);
assertEquals(0, testController.currentCallState.destroyCalls);
}
@Override
public void postAttach(@NonNull Controller controller, @NonNull View view) {
callState.attachCalls++;
Assert.assertEquals(2, callState.createViewCalls);
Assert.assertEquals(1, testController.currentCallState.createViewCalls);
assertEquals(2, callState.createViewCalls);
assertEquals(1, testController.currentCallState.createViewCalls);
Assert.assertEquals(2, callState.attachCalls);
Assert.assertEquals(1, testController.currentCallState.attachCalls);
assertEquals(2, callState.attachCalls);
assertEquals(1, testController.currentCallState.attachCalls);
Assert.assertEquals(0, callState.detachCalls);
Assert.assertEquals(0, testController.currentCallState.detachCalls);
assertEquals(0, callState.detachCalls);
assertEquals(0, testController.currentCallState.detachCalls);
Assert.assertEquals(0, callState.destroyViewCalls);
Assert.assertEquals(0, testController.currentCallState.destroyViewCalls);
assertEquals(0, callState.destroyViewCalls);
assertEquals(0, testController.currentCallState.destroyViewCalls);
Assert.assertEquals(0, callState.destroyCalls);
Assert.assertEquals(0, testController.currentCallState.destroyCalls);
assertEquals(0, callState.destroyCalls);
assertEquals(0, testController.currentCallState.destroyCalls);
}
@Override
public void preDetach(@NonNull Controller controller, @NonNull View view) {
callState.detachCalls++;
Assert.assertEquals(2, callState.createViewCalls);
Assert.assertEquals(1, testController.currentCallState.createViewCalls);
assertEquals(2, callState.createViewCalls);
assertEquals(1, testController.currentCallState.createViewCalls);
Assert.assertEquals(2, callState.attachCalls);
Assert.assertEquals(1, testController.currentCallState.attachCalls);
assertEquals(2, callState.attachCalls);
assertEquals(1, testController.currentCallState.attachCalls);
Assert.assertEquals(1, callState.detachCalls);
Assert.assertEquals(0, testController.currentCallState.detachCalls);
assertEquals(1, callState.detachCalls);
assertEquals(0, testController.currentCallState.detachCalls);
Assert.assertEquals(0, callState.destroyViewCalls);
Assert.assertEquals(0, testController.currentCallState.destroyViewCalls);
assertEquals(0, callState.destroyViewCalls);
assertEquals(0, testController.currentCallState.destroyViewCalls);
Assert.assertEquals(0, callState.destroyCalls);
Assert.assertEquals(0, testController.currentCallState.destroyCalls);
assertEquals(0, callState.destroyCalls);
assertEquals(0, testController.currentCallState.destroyCalls);
}
@Override
public void postDetach(@NonNull Controller controller, @NonNull View view) {
callState.detachCalls++;
Assert.assertEquals(2, callState.createViewCalls);
Assert.assertEquals(1, testController.currentCallState.createViewCalls);
assertEquals(2, callState.createViewCalls);
assertEquals(1, testController.currentCallState.createViewCalls);
Assert.assertEquals(2, callState.attachCalls);
Assert.assertEquals(1, testController.currentCallState.attachCalls);
assertEquals(2, callState.attachCalls);
assertEquals(1, testController.currentCallState.attachCalls);
Assert.assertEquals(2, callState.detachCalls);
Assert.assertEquals(1, testController.currentCallState.detachCalls);
assertEquals(2, callState.detachCalls);
assertEquals(1, testController.currentCallState.detachCalls);
Assert.assertEquals(0, callState.destroyViewCalls);
Assert.assertEquals(0, testController.currentCallState.destroyViewCalls);
assertEquals(0, callState.destroyViewCalls);
assertEquals(0, testController.currentCallState.destroyViewCalls);
Assert.assertEquals(0, callState.destroyCalls);
Assert.assertEquals(0, testController.currentCallState.destroyCalls);
assertEquals(0, callState.destroyCalls);
assertEquals(0, testController.currentCallState.destroyCalls);
}
@Override
public void preDestroyView(@NonNull Controller controller, @NonNull View view) {
callState.destroyViewCalls++;
Assert.assertEquals(2, callState.createViewCalls);
Assert.assertEquals(1, testController.currentCallState.createViewCalls);
assertEquals(2, callState.createViewCalls);
assertEquals(1, testController.currentCallState.createViewCalls);
Assert.assertEquals(2, callState.attachCalls);
Assert.assertEquals(1, testController.currentCallState.attachCalls);
assertEquals(2, callState.attachCalls);
assertEquals(1, testController.currentCallState.attachCalls);
Assert.assertEquals(2, callState.detachCalls);
Assert.assertEquals(1, testController.currentCallState.detachCalls);
assertEquals(2, callState.detachCalls);
assertEquals(1, testController.currentCallState.detachCalls);
Assert.assertEquals(1, callState.destroyViewCalls);
Assert.assertEquals(0, testController.currentCallState.destroyViewCalls);
assertEquals(1, callState.destroyViewCalls);
assertEquals(0, testController.currentCallState.destroyViewCalls);
Assert.assertEquals(0, callState.destroyCalls);
Assert.assertEquals(0, testController.currentCallState.destroyCalls);
assertEquals(0, callState.destroyCalls);
assertEquals(0, testController.currentCallState.destroyCalls);
}
@Override
public void postDestroyView(@NonNull Controller controller) {
callState.destroyViewCalls++;
Assert.assertEquals(2, callState.createViewCalls);
Assert.assertEquals(1, testController.currentCallState.createViewCalls);
assertEquals(2, callState.createViewCalls);
assertEquals(1, testController.currentCallState.createViewCalls);
Assert.assertEquals(2, callState.attachCalls);
Assert.assertEquals(1, testController.currentCallState.attachCalls);
assertEquals(2, callState.attachCalls);
assertEquals(1, testController.currentCallState.attachCalls);
Assert.assertEquals(2, callState.detachCalls);
Assert.assertEquals(1, testController.currentCallState.detachCalls);
assertEquals(2, callState.detachCalls);
assertEquals(1, testController.currentCallState.detachCalls);
Assert.assertEquals(2, callState.destroyViewCalls);
Assert.assertEquals(1, testController.currentCallState.destroyViewCalls);
assertEquals(2, callState.destroyViewCalls);
assertEquals(1, testController.currentCallState.destroyViewCalls);
Assert.assertEquals(0, callState.destroyCalls);
Assert.assertEquals(0, testController.currentCallState.destroyCalls);
assertEquals(0, callState.destroyCalls);
assertEquals(0, testController.currentCallState.destroyCalls);
}
@Override
public void preDestroy(@NonNull Controller controller) {
callState.destroyCalls++;
Assert.assertEquals(2, callState.createViewCalls);
Assert.assertEquals(1, testController.currentCallState.createViewCalls);
assertEquals(2, callState.createViewCalls);
assertEquals(1, testController.currentCallState.createViewCalls);
Assert.assertEquals(2, callState.attachCalls);
Assert.assertEquals(1, testController.currentCallState.attachCalls);
assertEquals(2, callState.attachCalls);
assertEquals(1, testController.currentCallState.attachCalls);
Assert.assertEquals(2, callState.detachCalls);
Assert.assertEquals(1, testController.currentCallState.detachCalls);
assertEquals(2, callState.detachCalls);
assertEquals(1, testController.currentCallState.detachCalls);
Assert.assertEquals(2, callState.destroyViewCalls);
Assert.assertEquals(1, testController.currentCallState.destroyViewCalls);
assertEquals(2, callState.destroyViewCalls);
assertEquals(1, testController.currentCallState.destroyViewCalls);
Assert.assertEquals(1, callState.destroyCalls);
Assert.assertEquals(0, testController.currentCallState.destroyCalls);
assertEquals(1, callState.destroyCalls);
assertEquals(0, testController.currentCallState.destroyCalls);
}
@Override
public void postDestroy(@NonNull Controller controller) {
callState.destroyCalls++;
Assert.assertEquals(2, callState.createViewCalls);
Assert.assertEquals(1, testController.currentCallState.createViewCalls);
assertEquals(2, callState.createViewCalls);
assertEquals(1, testController.currentCallState.createViewCalls);
Assert.assertEquals(2, callState.attachCalls);
Assert.assertEquals(1, testController.currentCallState.attachCalls);
assertEquals(2, callState.attachCalls);
assertEquals(1, testController.currentCallState.attachCalls);
Assert.assertEquals(2, callState.detachCalls);
Assert.assertEquals(1, testController.currentCallState.detachCalls);
assertEquals(2, callState.detachCalls);
assertEquals(1, testController.currentCallState.detachCalls);
Assert.assertEquals(2, callState.destroyViewCalls);
Assert.assertEquals(1, testController.currentCallState.destroyViewCalls);
assertEquals(2, callState.destroyViewCalls);
assertEquals(1, testController.currentCallState.destroyViewCalls);
Assert.assertEquals(2, callState.destroyCalls);
Assert.assertEquals(1, testController.currentCallState.destroyCalls);
assertEquals(2, callState.destroyCalls);
assertEquals(1, testController.currentCallState.destroyCalls);
}
});
router.pushController(RouterTransaction.with(testController)
.pushChangeHandler(new MockChangeHandler())
.popChangeHandler(new MockChangeHandler()));
.pushChangeHandler(MockChangeHandler.defaultHandler())
.popChangeHandler(MockChangeHandler.defaultHandler()));
router.popController(testController);
Assert.assertEquals(2, callState.createViewCalls);
Assert.assertEquals(2, callState.attachCalls);
Assert.assertEquals(2, callState.detachCalls);
Assert.assertEquals(2, callState.destroyViewCalls);
Assert.assertEquals(2, callState.destroyCalls);
assertEquals(2, callState.createViewCalls);
assertEquals(2, callState.attachCalls);
assertEquals(2, callState.detachCalls);
assertEquals(2, callState.destroyViewCalls);
assertEquals(2, callState.destroyCalls);
}
@Test
public void testChildLifecycle() {
Controller parent = new TestController();
router.pushController(RouterTransaction.with(parent)
.pushChangeHandler(new MockChangeHandler()));
.pushChangeHandler(MockChangeHandler.defaultHandler()));
TestController child = new TestController();
attachLifecycleListener(child);
@@ -408,7 +414,7 @@ public class ControllerLifecycleTests {
assertCalls(expectedCallState, child);
Router childRouter = parent.getChildRouter((ViewGroup)parent.getView().findViewById(TestController.VIEW_ID), null);
Router childRouter = parent.getChildRouter((ViewGroup)parent.getView().findViewById(TestController.VIEW_ID));
childRouter
.setRoot(RouterTransaction.with(child)
.pushChangeHandler(getPushHandler(expectedCallState, child))
@@ -425,8 +431,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);
@@ -435,7 +441,7 @@ public class ControllerLifecycleTests {
assertCalls(expectedCallState, child);
Router childRouter = parent.getChildRouter((ViewGroup)parent.getView().findViewById(TestController.VIEW_ID), null);
Router childRouter = parent.getChildRouter((ViewGroup)parent.getView().findViewById(TestController.VIEW_ID));
childRouter
.setRoot(RouterTransaction.with(child)
.pushChangeHandler(getPushHandler(expectedCallState, child))
@@ -445,26 +451,30 @@ 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() {
public void willStartChange() {
expectedCallState.changeStartCalls++;
expectedCallState.createViewCalls++;
assertCalls(expectedCallState, controller);
}
@Override
void didAttachOrDetach() {
public void didAttachOrDetach() {
expectedCallState.attachCalls++;
assertCalls(expectedCallState, controller);
}
@Override
void didEndChange() {
public void didEndChange() {
expectedCallState.changeEndCalls++;
assertCalls(expectedCallState, controller);
}
@@ -472,15 +482,15 @@ 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() {
public void willStartChange() {
expectedCallState.changeStartCalls++;
assertCalls(expectedCallState, controller);
}
@Override
void didAttachOrDetach() {
public void didAttachOrDetach() {
expectedCallState.destroyViewCalls++;
expectedCallState.detachCalls++;
expectedCallState.destroyCalls++;
@@ -488,7 +498,7 @@ public class ControllerLifecycleTests {
}
@Override
void didEndChange() {
public void didEndChange() {
expectedCallState.changeEndCalls++;
assertCalls(expectedCallState, controller);
}
@@ -496,8 +506,8 @@ public class ControllerLifecycleTests {
}
private void assertCalls(CallState callState, TestController controller) {
Assert.assertEquals("Expected call counts and controller call counts do not match.", callState, controller.currentCallState);
Assert.assertEquals("Expected call counts and lifecycle call counts do not match.", callState, currentCallState);
assertEquals("Expected call counts and controller call counts do not match.", callState, controller.currentCallState);
assertEquals("Expected call counts and lifecycle call counts do not match.", callState, currentCallState);
}
private void attachLifecycleListener(Controller controller) {
@@ -7,14 +7,21 @@ import android.view.View;
import android.view.ViewGroup;
import com.bluelinelabs.conductor.Controller.RetainViewMode;
import com.bluelinelabs.conductor.util.ActivityProxy;
import com.bluelinelabs.conductor.util.CallState;
import com.bluelinelabs.conductor.util.TestController;
import com.bluelinelabs.conductor.util.ViewUtils;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
@RunWith(RobolectricTestRunner.class)
@Config(manifest = Config.NONE)
public class ControllerTests {
@@ -38,29 +45,30 @@ public class ControllerTests {
@Test
public void testViewRetention() {
Controller controller = new TestController();
controller.setRouter(router);
// Test View getting released w/ RELEASE_DETACH
controller.setRetainViewMode(RetainViewMode.RELEASE_DETACH);
Assert.assertNull(controller.getView());
View view = controller.inflate(new AttachFakingFrameLayout(router.getActivity()));
Assert.assertNotNull(controller.getView());
assertNull(controller.getView());
View view = controller.inflate(router.container);
assertNotNull(controller.getView());
ViewUtils.reportAttached(view, true);
Assert.assertNotNull(controller.getView());
assertNotNull(controller.getView());
ViewUtils.reportAttached(view, false);
Assert.assertNull(controller.getView());
assertNull(controller.getView());
// Test View getting retained w/ RETAIN_DETACH
controller.setRetainViewMode(RetainViewMode.RETAIN_DETACH);
view = controller.inflate(new AttachFakingFrameLayout(router.getActivity()));
Assert.assertNotNull(controller.getView());
view = controller.inflate(router.container);
assertNotNull(controller.getView());
ViewUtils.reportAttached(view, true);
Assert.assertNotNull(controller.getView());
assertNotNull(controller.getView());
ViewUtils.reportAttached(view, false);
Assert.assertNotNull(controller.getView());
assertNotNull(controller.getView());
// Ensure re-setting RELEASE_DETACH releases
controller.setRetainViewMode(RetainViewMode.RELEASE_DETACH);
Assert.assertNull(controller.getView());
assertNull(controller.getView());
}
@Test
@@ -93,7 +101,7 @@ public class ControllerTests {
TestController child = new TestController();
router.pushController(RouterTransaction.with(parent));
parent.getChildRouter((ViewGroup)parent.getView().findViewById(TestController.VIEW_ID), null)
parent.getChildRouter((ViewGroup)parent.getView().findViewById(TestController.VIEW_ID))
.setRoot(RouterTransaction.with(child));
CallState childExpectedCallState = new CallState(true);
@@ -150,7 +158,7 @@ public class ControllerTests {
TestController child = new TestController();
router.pushController(RouterTransaction.with(parent));
parent.getChildRouter((ViewGroup)parent.getView().findViewById(TestController.VIEW_ID), null)
parent.getChildRouter((ViewGroup)parent.getView().findViewById(TestController.VIEW_ID))
.setRoot(RouterTransaction.with(child));
CallState childExpectedCallState = new CallState(true);
@@ -214,7 +222,7 @@ public class ControllerTests {
TestController child = new TestController();
router.pushController(RouterTransaction.with(parent));
parent.getChildRouter((ViewGroup)parent.getView().findViewById(TestController.VIEW_ID), null)
parent.getChildRouter((ViewGroup)parent.getView().findViewById(TestController.VIEW_ID))
.setRoot(RouterTransaction.with(child));
CallState childExpectedCallState = new CallState(true);
@@ -262,48 +270,48 @@ public class ControllerTests {
router.pushController(RouterTransaction.with(parent));
Assert.assertEquals(0, parent.getChildRouters().size());
Assert.assertNull(child1.getParentController());
Assert.assertNull(child2.getParentController());
assertEquals(0, parent.getChildRouters().size());
assertNull(child1.getParentController());
assertNull(child2.getParentController());
Router childRouter = parent.getChildRouter((ViewGroup)parent.getView().findViewById(TestController.VIEW_ID), null);
Router childRouter = parent.getChildRouter((ViewGroup)parent.getView().findViewById(TestController.VIEW_ID));
childRouter.setPopsLastView(true);
childRouter.setRoot(RouterTransaction.with(child1));
Assert.assertEquals(1, parent.getChildRouters().size());
Assert.assertEquals(childRouter, parent.getChildRouters().get(0));
Assert.assertEquals(1, childRouter.getBackstackSize());
Assert.assertEquals(child1, childRouter.getControllers().get(0));
Assert.assertEquals(parent, child1.getParentController());
Assert.assertNull(child2.getParentController());
assertEquals(1, parent.getChildRouters().size());
assertEquals(childRouter, parent.getChildRouters().get(0));
assertEquals(1, childRouter.getBackstackSize());
assertEquals(child1, childRouter.getControllers().get(0));
assertEquals(parent, child1.getParentController());
assertNull(child2.getParentController());
childRouter = parent.getChildRouter((ViewGroup)parent.getView().findViewById(TestController.VIEW_ID), null);
childRouter = parent.getChildRouter((ViewGroup)parent.getView().findViewById(TestController.VIEW_ID));
childRouter.pushController(RouterTransaction.with(child2));
Assert.assertEquals(1, parent.getChildRouters().size());
Assert.assertEquals(childRouter, parent.getChildRouters().get(0));
Assert.assertEquals(2, childRouter.getBackstackSize());
Assert.assertEquals(child1, childRouter.getControllers().get(0));
Assert.assertEquals(child2, childRouter.getControllers().get(1));
Assert.assertEquals(parent, child1.getParentController());
Assert.assertEquals(parent, child2.getParentController());
assertEquals(1, parent.getChildRouters().size());
assertEquals(childRouter, parent.getChildRouters().get(0));
assertEquals(2, childRouter.getBackstackSize());
assertEquals(child1, childRouter.getControllers().get(0));
assertEquals(child2, childRouter.getControllers().get(1));
assertEquals(parent, child1.getParentController());
assertEquals(parent, child2.getParentController());
childRouter.popController(child2);
Assert.assertEquals(1, parent.getChildRouters().size());
Assert.assertEquals(childRouter, parent.getChildRouters().get(0));
Assert.assertEquals(1, childRouter.getBackstackSize());
Assert.assertEquals(child1, childRouter.getControllers().get(0));
Assert.assertEquals(parent, child1.getParentController());
Assert.assertNull(child2.getParentController());
assertEquals(1, parent.getChildRouters().size());
assertEquals(childRouter, parent.getChildRouters().get(0));
assertEquals(1, childRouter.getBackstackSize());
assertEquals(child1, childRouter.getControllers().get(0));
assertEquals(parent, child1.getParentController());
assertNull(child2.getParentController());
childRouter.popController(child1);
Assert.assertEquals(1, parent.getChildRouters().size());
Assert.assertEquals(childRouter, parent.getChildRouters().get(0));
Assert.assertEquals(0, childRouter.getBackstackSize());
Assert.assertNull(child1.getParentController());
Assert.assertNull(child2.getParentController());
assertEquals(1, parent.getChildRouters().size());
assertEquals(childRouter, parent.getChildRouters().get(0));
assertEquals(0, childRouter.getBackstackSize());
assertNull(child1.getParentController());
assertNull(child2.getParentController());
}
@Test
@@ -315,47 +323,47 @@ public class ControllerTests {
router.pushController(RouterTransaction.with(parent));
Assert.assertEquals(0, parent.getChildRouters().size());
Assert.assertNull(child1.getParentController());
Assert.assertNull(child2.getParentController());
assertEquals(0, parent.getChildRouters().size());
assertNull(child1.getParentController());
assertNull(child2.getParentController());
Router childRouter1 = parent.getChildRouter((ViewGroup)parent.getView().findViewById(TestController.CHILD_VIEW_ID_1), null);
Router childRouter2 = parent.getChildRouter((ViewGroup)parent.getView().findViewById(TestController.CHILD_VIEW_ID_2), null);
Router childRouter1 = parent.getChildRouter((ViewGroup)parent.getView().findViewById(TestController.CHILD_VIEW_ID_1));
Router childRouter2 = parent.getChildRouter((ViewGroup)parent.getView().findViewById(TestController.CHILD_VIEW_ID_2));
childRouter1.setRoot(RouterTransaction.with(child1));
childRouter2.setRoot(RouterTransaction.with(child2));
Assert.assertEquals(2, parent.getChildRouters().size());
Assert.assertEquals(childRouter1, parent.getChildRouters().get(0));
Assert.assertEquals(childRouter2, parent.getChildRouters().get(1));
Assert.assertEquals(1, childRouter1.getBackstackSize());
Assert.assertEquals(1, childRouter2.getBackstackSize());
Assert.assertEquals(child1, childRouter1.getControllers().get(0));
Assert.assertEquals(child2, childRouter2.getControllers().get(0));
Assert.assertEquals(parent, child1.getParentController());
Assert.assertEquals(parent, child2.getParentController());
assertEquals(2, parent.getChildRouters().size());
assertEquals(childRouter1, parent.getChildRouters().get(0));
assertEquals(childRouter2, parent.getChildRouters().get(1));
assertEquals(1, childRouter1.getBackstackSize());
assertEquals(1, childRouter2.getBackstackSize());
assertEquals(child1, childRouter1.getControllers().get(0));
assertEquals(child2, childRouter2.getControllers().get(0));
assertEquals(parent, child1.getParentController());
assertEquals(parent, child2.getParentController());
parent.removeChildRouter(childRouter2);
Assert.assertEquals(1, parent.getChildRouters().size());
Assert.assertEquals(childRouter1, parent.getChildRouters().get(0));
Assert.assertEquals(1, childRouter1.getBackstackSize());
Assert.assertEquals(0, childRouter2.getBackstackSize());
Assert.assertEquals(child1, childRouter1.getControllers().get(0));
Assert.assertEquals(parent, child1.getParentController());
Assert.assertNull(child2.getParentController());
assertEquals(1, parent.getChildRouters().size());
assertEquals(childRouter1, parent.getChildRouters().get(0));
assertEquals(1, childRouter1.getBackstackSize());
assertEquals(0, childRouter2.getBackstackSize());
assertEquals(child1, childRouter1.getControllers().get(0));
assertEquals(parent, child1.getParentController());
assertNull(child2.getParentController());
parent.removeChildRouter(childRouter1);
Assert.assertEquals(0, parent.getChildRouters().size());
Assert.assertEquals(0, childRouter1.getBackstackSize());
Assert.assertEquals(0, childRouter2.getBackstackSize());
Assert.assertNull(child1.getParentController());
Assert.assertNull(child2.getParentController());
assertEquals(0, parent.getChildRouters().size());
assertEquals(0, childRouter1.getBackstackSize());
assertEquals(0, childRouter2.getBackstackSize());
assertNull(child1.getParentController());
assertNull(child2.getParentController());
}
private void assertCalls(CallState callState, TestController controller) {
Assert.assertEquals("Expected call counts and controller call counts do not match.", callState, controller.currentCallState);
assertEquals("Expected call counts and controller call counts do not match.", callState, controller.currentCallState);
}
}
@@ -4,11 +4,12 @@ import android.os.Bundle;
import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler;
import com.bluelinelabs.conductor.changehandler.VerticalChangeHandler;
import junit.framework.Assert;
import com.bluelinelabs.conductor.util.TestController;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
public class ControllerTransactionTests {
@Test
@@ -22,10 +23,10 @@ public class ControllerTransactionTests {
RouterTransaction restoredTransaction = new RouterTransaction(bundle);
Assert.assertEquals(transaction.controller.getClass(), restoredTransaction.controller.getClass());
Assert.assertEquals(transaction.pushChangeHandler().getClass(), restoredTransaction.pushChangeHandler().getClass());
Assert.assertEquals(transaction.popChangeHandler().getClass(), restoredTransaction.popChangeHandler().getClass());
Assert.assertEquals(transaction.tag(), restoredTransaction.tag());
assertEquals(transaction.controller.getClass(), restoredTransaction.controller.getClass());
assertEquals(transaction.pushChangeHandler().getClass(), restoredTransaction.pushChangeHandler().getClass());
assertEquals(transaction.popChangeHandler().getClass(), restoredTransaction.popChangeHandler().getClass());
assertEquals(transaction.tag(), restoredTransaction.tag());
}
}
@@ -1,85 +0,0 @@
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() { }
void didEndChange() { }
}
final ChangeHandlerListener listener;
boolean removesFromViewOnPush;
public MockChangeHandler() {
this(true, null);
}
public MockChangeHandler(boolean removesViewOnPush) {
this(removesViewOnPush, null);
}
public MockChangeHandler(@NonNull ChangeHandlerListener 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
public void performChange(@NonNull ViewGroup container, View from, View to, boolean isPush, @NonNull ControllerChangeCompletedListener changeListener) {
listener.willStartChange();
if (isPush) {
container.addView(to);
listener.didAttachOrDetach();
if (removesFromViewOnPush && from != null) {
container.removeView(from);
}
} else {
container.removeView(from);
listener.didAttachOrDetach();
if (to != null) {
container.addView(to);
}
}
changeListener.onChangeCompleted();
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);
}
}
@@ -3,13 +3,19 @@ package com.bluelinelabs.conductor;
import android.os.Bundle;
import android.view.ViewGroup;
import org.junit.Assert;
import com.bluelinelabs.conductor.util.ActivityProxy;
import com.bluelinelabs.conductor.util.MockChangeHandler;
import com.bluelinelabs.conductor.util.TestController;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@RunWith(RobolectricTestRunner.class)
@Config(manifest = Config.NONE)
public class ReattachCaseTests {
@@ -36,71 +42,71 @@ 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());
assertTrue(controllerA.isAttached());
assertFalse(controllerB.isAttached());
sleepWakeDevice();
Assert.assertTrue(controllerA.isAttached());
Assert.assertFalse(controllerB.isAttached());
assertTrue(controllerA.isAttached());
assertFalse(controllerB.isAttached());
router.pushController(RouterTransaction.with(controllerB)
.pushChangeHandler(new MockChangeHandler())
.popChangeHandler(new MockChangeHandler()));
.pushChangeHandler(MockChangeHandler.defaultHandler())
.popChangeHandler(MockChangeHandler.defaultHandler()));
Assert.assertFalse(controllerA.isAttached());
Assert.assertTrue(controllerB.isAttached());
assertFalse(controllerA.isAttached());
assertTrue(controllerB.isAttached());
activityProxy.rotate();
router.rebindIfNeeded();
Assert.assertFalse(controllerA.isAttached());
Assert.assertTrue(controllerB.isAttached());
assertFalse(controllerA.isAttached());
assertTrue(controllerB.isAttached());
}
@Test
public void testChildNeedsAttachOnPauseAndOrientation() {
final TestController controllerA = new TestController();
final TestController childController = new TestController();
final TestController controllerB = new TestController();
final Controller controllerA = new TestController();
final Controller childController = new TestController();
final Controller controllerB = new TestController();
router.pushController(RouterTransaction.with(controllerA)
.pushChangeHandler(new MockChangeHandler())
.popChangeHandler(new MockChangeHandler()));
.pushChangeHandler(MockChangeHandler.defaultHandler())
.popChangeHandler(MockChangeHandler.defaultHandler()));
Router childRouter = controllerA.getChildRouter((ViewGroup)controllerA.getView().findViewById(TestController.VIEW_ID), null);
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());
Assert.assertFalse(controllerB.isAttached());
assertTrue(controllerA.isAttached());
assertTrue(childController.isAttached());
assertFalse(controllerB.isAttached());
sleepWakeDevice();
Assert.assertTrue(controllerA.isAttached());
Assert.assertTrue(childController.isAttached());
Assert.assertFalse(controllerB.isAttached());
assertTrue(controllerA.isAttached());
assertTrue(childController.isAttached());
assertFalse(controllerB.isAttached());
router.pushController(RouterTransaction.with(controllerB)
.pushChangeHandler(new MockChangeHandler())
.popChangeHandler(new MockChangeHandler()));
.pushChangeHandler(MockChangeHandler.defaultHandler())
.popChangeHandler(MockChangeHandler.defaultHandler()));
Assert.assertFalse(controllerA.isAttached());
Assert.assertFalse(childController.isAttached());
Assert.assertTrue(controllerB.isAttached());
assertFalse(controllerA.isAttached());
assertFalse(childController.isAttached());
assertTrue(controllerB.isAttached());
activityProxy.rotate();
router.rebindIfNeeded();
Assert.assertFalse(controllerA.isAttached());
Assert.assertFalse(childController.isAttached());
Assert.assertTrue(childController.getNeedsAttach());
Assert.assertTrue(controllerB.isAttached());
assertFalse(controllerA.isAttached());
assertFalse(childController.isAttached());
assertTrue(childController.getNeedsAttach());
assertTrue(controllerB.isAttached());
}
@Test
@@ -110,45 +116,45 @@ 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());
assertTrue(controllerA.isAttached());
assertFalse(controllerB.isAttached());
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), null);
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());
Assert.assertTrue(childController.isAttached());
assertFalse(controllerA.isAttached());
assertTrue(controllerB.isAttached());
assertTrue(childController.isAttached());
activityProxy.rotate();
router.rebindIfNeeded();
Assert.assertFalse(controllerA.isAttached());
Assert.assertTrue(controllerB.isAttached());
Assert.assertTrue(childController.isAttached());
assertFalse(controllerA.isAttached());
assertTrue(controllerB.isAttached());
assertTrue(childController.isAttached());
router.handleBack();
Assert.assertFalse(controllerA.isAttached());
Assert.assertTrue(controllerB.isAttached());
Assert.assertFalse(childController.isAttached());
assertFalse(controllerA.isAttached());
assertTrue(controllerB.isAttached());
assertFalse(childController.isAttached());
router.handleBack();
Assert.assertTrue(controllerA.isAttached());
Assert.assertFalse(controllerB.isAttached());
Assert.assertFalse(childController.isAttached());
assertTrue(controllerA.isAttached());
assertFalse(controllerB.isAttached());
assertFalse(childController.isAttached());
}
// Attempt to test https://github.com/bluelinelabs/Conductor/issues/86#issuecomment-231381271
@@ -159,71 +165,71 @@ 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());
assertTrue(controllerA.isAttached());
assertFalse(controllerB.isAttached());
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), null);
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());
Assert.assertTrue(childController.isAttached());
assertFalse(controllerA.isAttached());
assertTrue(controllerB.isAttached());
assertTrue(childController.isAttached());
router.handleBack();
Assert.assertFalse(controllerA.isAttached());
Assert.assertTrue(controllerB.isAttached());
Assert.assertFalse(childController.isAttached());
assertFalse(controllerA.isAttached());
assertTrue(controllerB.isAttached());
assertFalse(childController.isAttached());
childController = new TestController();
childRouter.pushController(RouterTransaction.with(childController)
.pushChangeHandler(new MockChangeHandler())
.popChangeHandler(new MockChangeHandler()));
.pushChangeHandler(MockChangeHandler.defaultHandler())
.popChangeHandler(MockChangeHandler.defaultHandler()));
Assert.assertFalse(controllerA.isAttached());
Assert.assertTrue(controllerB.isAttached());
Assert.assertTrue(childController.isAttached());
assertFalse(controllerA.isAttached());
assertTrue(controllerB.isAttached());
assertTrue(childController.isAttached());
activityProxy.rotate();
router.rebindIfNeeded();
Assert.assertFalse(controllerA.isAttached());
Assert.assertTrue(controllerB.isAttached());
Assert.assertTrue(childController.isAttached());
assertFalse(controllerA.isAttached());
assertTrue(controllerB.isAttached());
assertTrue(childController.isAttached());
router.handleBack();
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());
Assert.assertTrue(childController.isAttached());
assertFalse(controllerA.isAttached());
assertTrue(controllerB.isAttached());
assertTrue(childController.isAttached());
router.handleBack();
Assert.assertFalse(controllerA.isAttached());
Assert.assertTrue(controllerB.isAttached());
Assert.assertFalse(childController.isAttached());
assertFalse(controllerA.isAttached());
assertTrue(controllerB.isAttached());
assertFalse(childController.isAttached());
router.handleBack();
Assert.assertTrue(controllerA.isAttached());
Assert.assertFalse(controllerB.isAttached());
Assert.assertFalse(childController.isAttached());
assertTrue(controllerA.isAttached());
assertFalse(controllerB.isAttached());
assertFalse(childController.isAttached());
}
private void sleepWakeDevice() {
@@ -0,0 +1,239 @@
package com.bluelinelabs.conductor;
import android.view.View;
import com.bluelinelabs.conductor.util.ActivityProxy;
import com.bluelinelabs.conductor.util.ListUtils;
import com.bluelinelabs.conductor.util.MockChangeHandler;
import com.bluelinelabs.conductor.util.TestController;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
@RunWith(RobolectricTestRunner.class)
@Config(manifest = Config.NONE)
public class RouterChangeHandlerTests {
private Router router;
@Before
public void setup() {
ActivityProxy activityProxy = new ActivityProxy().create(null).start().resume();
router = Conductor.attachRouter(activityProxy.getActivity(), activityProxy.getView(), null);
}
@Test
public void testSetRootHandler() {
MockChangeHandler handler = MockChangeHandler.taggedHandler("root", true);
TestController rootController = new TestController();
router.setRoot(RouterTransaction.with(rootController).pushChangeHandler(handler));
assertTrue(rootController.changeHandlerHistory.isValidHistory);
assertNull(rootController.changeHandlerHistory.latestFromView());
assertNotNull(rootController.changeHandlerHistory.latestToView());
assertEquals(rootController.getView(), rootController.changeHandlerHistory.latestToView());
assertTrue(rootController.changeHandlerHistory.latestIsPush());
assertEquals(handler.tag, rootController.changeHandlerHistory.latestChangeHandler().tag);
}
@Test
public void testPushPopHandlers() {
TestController rootController = new TestController();
router.setRoot(RouterTransaction.with(rootController).pushChangeHandler(MockChangeHandler.defaultHandler()));
View rootView = rootController.getView();
MockChangeHandler pushHandler = MockChangeHandler.taggedHandler("push", true);
MockChangeHandler popHandler = MockChangeHandler.taggedHandler("pop", true);
TestController pushController = new TestController();
router.pushController(RouterTransaction.with(pushController).pushChangeHandler(pushHandler).popChangeHandler(popHandler));
assertTrue(rootController.changeHandlerHistory.isValidHistory);
assertTrue(pushController.changeHandlerHistory.isValidHistory);
assertNotNull(pushController.changeHandlerHistory.latestFromView());
assertNotNull(pushController.changeHandlerHistory.latestToView());
assertEquals(rootView, pushController.changeHandlerHistory.latestFromView());
assertEquals(pushController.getView(), pushController.changeHandlerHistory.latestToView());
assertTrue(pushController.changeHandlerHistory.latestIsPush());
assertEquals(pushHandler.tag, pushController.changeHandlerHistory.latestChangeHandler().tag);
View pushView = pushController.getView();
router.popController(pushController);
assertNotNull(pushController.changeHandlerHistory.latestFromView());
assertNotNull(pushController.changeHandlerHistory.latestToView());
assertEquals(pushView, pushController.changeHandlerHistory.fromViewAt(1));
assertEquals(rootController.getView(), pushController.changeHandlerHistory.latestToView());
assertFalse(pushController.changeHandlerHistory.latestIsPush());
assertEquals(popHandler.tag, pushController.changeHandlerHistory.latestChangeHandler().tag);
}
@Test
public void testResetRootHandlers() {
TestController initialController1 = new TestController();
MockChangeHandler initialPushHandler1 = MockChangeHandler.taggedHandler("initialPush1", true);
MockChangeHandler initialPopHandler1 = MockChangeHandler.taggedHandler("initialPop1", true);
router.setRoot(RouterTransaction.with(initialController1).pushChangeHandler(initialPushHandler1).popChangeHandler(initialPopHandler1));
TestController initialController2 = new TestController();
MockChangeHandler initialPushHandler2 = MockChangeHandler.taggedHandler("initialPush2", false);
MockChangeHandler initialPopHandler2 = MockChangeHandler.taggedHandler("initialPop2", false);
router.pushController(RouterTransaction.with(initialController2).pushChangeHandler(initialPushHandler2).popChangeHandler(initialPopHandler2));
View initialView1 = initialController1.getView();
View initialView2 = initialController2.getView();
TestController newRootController = new TestController();
MockChangeHandler newRootHandler = MockChangeHandler.taggedHandler("newRootHandler", true);
router.setRoot(RouterTransaction.with(newRootController).pushChangeHandler(newRootHandler));
assertTrue(initialController1.changeHandlerHistory.isValidHistory);
assertTrue(initialController2.changeHandlerHistory.isValidHistory);
assertTrue(newRootController.changeHandlerHistory.isValidHistory);
assertEquals(3, initialController1.changeHandlerHistory.size());
assertEquals(2, initialController2.changeHandlerHistory.size());
assertEquals(1, newRootController.changeHandlerHistory.size());
assertNotNull(initialController1.changeHandlerHistory.latestToView());
assertEquals(newRootController.getView(), initialController1.changeHandlerHistory.latestToView());
assertEquals(initialView1, initialController1.changeHandlerHistory.latestFromView());
assertEquals(newRootHandler.tag, initialController1.changeHandlerHistory.latestChangeHandler().tag);
assertTrue(initialController1.changeHandlerHistory.latestIsPush());
assertNull(initialController2.changeHandlerHistory.latestToView());
assertEquals(initialView2, initialController2.changeHandlerHistory.latestFromView());
assertEquals(newRootHandler.tag, initialController2.changeHandlerHistory.latestChangeHandler().tag);
assertTrue(initialController2.changeHandlerHistory.latestIsPush());
assertNotNull(newRootController.changeHandlerHistory.latestToView());
assertEquals(newRootController.getView(), newRootController.changeHandlerHistory.latestToView());
assertEquals(initialView1, newRootController.changeHandlerHistory.latestFromView());
assertEquals(newRootHandler.tag, newRootController.changeHandlerHistory.latestChangeHandler().tag);
assertTrue(newRootController.changeHandlerHistory.latestIsPush());
}
@Test
public void testSetBackstackHandlers() {
TestController initialController1 = new TestController();
MockChangeHandler initialPushHandler1 = MockChangeHandler.taggedHandler("initialPush1", true);
MockChangeHandler initialPopHandler1 = MockChangeHandler.taggedHandler("initialPop1", true);
router.setRoot(RouterTransaction.with(initialController1).pushChangeHandler(initialPushHandler1).popChangeHandler(initialPopHandler1));
TestController initialController2 = new TestController();
MockChangeHandler initialPushHandler2 = MockChangeHandler.taggedHandler("initialPush2", false);
MockChangeHandler initialPopHandler2 = MockChangeHandler.taggedHandler("initialPop2", false);
router.pushController(RouterTransaction.with(initialController2).pushChangeHandler(initialPushHandler2).popChangeHandler(initialPopHandler2));
View initialView1 = initialController1.getView();
View initialView2 = initialController2.getView();
TestController newController1 = new TestController();
TestController newController2 = new TestController();
MockChangeHandler setBackstackHandler = MockChangeHandler.taggedHandler("setBackstackHandler", true);
List<RouterTransaction> newBackstack = ListUtils.listOf(
RouterTransaction.with(newController1),
RouterTransaction.with(newController2)
);
router.setBackstack(newBackstack, setBackstackHandler);
assertTrue(initialController1.changeHandlerHistory.isValidHistory);
assertTrue(initialController2.changeHandlerHistory.isValidHistory);
assertTrue(newController1.changeHandlerHistory.isValidHistory);
assertEquals(3, initialController1.changeHandlerHistory.size());
assertEquals(2, initialController2.changeHandlerHistory.size());
assertEquals(0, newController1.changeHandlerHistory.size());
assertEquals(1, newController2.changeHandlerHistory.size());
assertNotNull(initialController1.changeHandlerHistory.latestToView());
assertEquals(newController2.getView(), initialController1.changeHandlerHistory.latestToView());
assertEquals(initialView1, initialController1.changeHandlerHistory.latestFromView());
assertEquals(setBackstackHandler.tag, initialController1.changeHandlerHistory.latestChangeHandler().tag);
assertTrue(initialController1.changeHandlerHistory.latestIsPush());
assertNull(initialController2.changeHandlerHistory.latestToView());
assertEquals(initialView2, initialController2.changeHandlerHistory.latestFromView());
assertEquals(setBackstackHandler.tag, initialController2.changeHandlerHistory.latestChangeHandler().tag);
assertTrue(initialController2.changeHandlerHistory.latestIsPush());
assertNotNull(newController2.changeHandlerHistory.latestToView());
assertEquals(newController2.getView(), newController2.changeHandlerHistory.latestToView());
assertEquals(initialView1, newController2.changeHandlerHistory.latestFromView());
assertEquals(setBackstackHandler.tag, newController2.changeHandlerHistory.latestChangeHandler().tag);
assertTrue(newController2.changeHandlerHistory.latestIsPush());
}
@Test
public void testSetBackstackWithTwoVisibleHandlers() {
TestController initialController1 = new TestController();
MockChangeHandler initialPushHandler1 = MockChangeHandler.taggedHandler("initialPush1", true);
MockChangeHandler initialPopHandler1 = MockChangeHandler.taggedHandler("initialPop1", true);
router.setRoot(RouterTransaction.with(initialController1).pushChangeHandler(initialPushHandler1).popChangeHandler(initialPopHandler1));
TestController initialController2 = new TestController();
MockChangeHandler initialPushHandler2 = MockChangeHandler.taggedHandler("initialPush2", false);
MockChangeHandler initialPopHandler2 = MockChangeHandler.taggedHandler("initialPop2", false);
router.pushController(RouterTransaction.with(initialController2).pushChangeHandler(initialPushHandler2).popChangeHandler(initialPopHandler2));
View initialView1 = initialController1.getView();
View initialView2 = initialController2.getView();
TestController newController1 = new TestController();
TestController newController2 = new TestController();
MockChangeHandler setBackstackHandler = MockChangeHandler.taggedHandler("setBackstackHandler", true);
List<RouterTransaction> newBackstack = ListUtils.listOf(
RouterTransaction.with(newController1),
RouterTransaction.with(newController2).pushChangeHandler(MockChangeHandler.noRemoveViewOnPushHandler())
);
router.setBackstack(newBackstack, setBackstackHandler);
assertTrue(initialController1.changeHandlerHistory.isValidHistory);
assertTrue(initialController2.changeHandlerHistory.isValidHistory);
assertTrue(newController1.changeHandlerHistory.isValidHistory);
assertEquals(3, initialController1.changeHandlerHistory.size());
assertEquals(2, initialController2.changeHandlerHistory.size());
assertEquals(2, newController1.changeHandlerHistory.size());
assertEquals(1, newController2.changeHandlerHistory.size());
assertNotNull(initialController1.changeHandlerHistory.latestToView());
assertEquals(newController1.getView(), initialController1.changeHandlerHistory.latestToView());
assertEquals(initialView1, initialController1.changeHandlerHistory.latestFromView());
assertEquals(setBackstackHandler.tag, initialController1.changeHandlerHistory.latestChangeHandler().tag);
assertTrue(initialController1.changeHandlerHistory.latestIsPush());
assertNull(initialController2.changeHandlerHistory.latestToView());
assertEquals(initialView2, initialController2.changeHandlerHistory.latestFromView());
assertEquals(setBackstackHandler.tag, initialController2.changeHandlerHistory.latestChangeHandler().tag);
assertTrue(initialController2.changeHandlerHistory.latestIsPush());
assertNotNull(newController1.changeHandlerHistory.latestToView());
assertEquals(newController1.getView(), newController1.changeHandlerHistory.toViewAt(0));
assertEquals(newController2.getView(), newController1.changeHandlerHistory.latestToView());
assertEquals(initialView1, newController1.changeHandlerHistory.fromViewAt(0));
assertEquals(newController1.getView(), newController1.changeHandlerHistory.latestFromView());
assertEquals(setBackstackHandler.tag, newController1.changeHandlerHistory.latestChangeHandler().tag);
assertTrue(newController1.changeHandlerHistory.latestIsPush());
assertNotNull(newController2.changeHandlerHistory.latestToView());
assertEquals(newController2.getView(), newController2.changeHandlerHistory.latestToView());
assertEquals(newController1.getView(), newController2.changeHandlerHistory.latestFromView());
assertEquals(setBackstackHandler.tag, newController2.changeHandlerHistory.latestChangeHandler().tag);
assertTrue(newController2.changeHandlerHistory.latestIsPush());
}
}
@@ -1,15 +1,23 @@
package com.bluelinelabs.conductor;
import org.junit.Assert;
import com.bluelinelabs.conductor.util.ActivityProxy;
import com.bluelinelabs.conductor.util.ListUtils;
import com.bluelinelabs.conductor.util.MockChangeHandler;
import com.bluelinelabs.conductor.util.TestController;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import java.util.ArrayList;
import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
@RunWith(RobolectricTestRunner.class)
@Config(manifest = Config.NONE)
public class RouterTests {
@@ -28,13 +36,13 @@ public class RouterTests {
Controller rootController = new TestController();
Assert.assertFalse(router.hasRootController());
assertFalse(router.hasRootController());
router.setRoot(RouterTransaction.with(rootController).tag(rootTag));
Assert.assertTrue(router.hasRootController());
assertTrue(router.hasRootController());
Assert.assertEquals(rootController, router.getControllerWithTag(rootTag));
assertEquals(rootController, router.getControllerWithTag(rootTag));
}
@Test
@@ -48,8 +56,8 @@ public class RouterTests {
router.setRoot(RouterTransaction.with(oldRootController).tag(oldRootTag));
router.setRoot(RouterTransaction.with(newRootController).tag(newRootTag));
Assert.assertNull(router.getControllerWithTag(oldRootTag));
Assert.assertEquals(newRootController, router.getControllerWithTag(newRootTag));
assertNull(router.getControllerWithTag(oldRootTag));
assertEquals(newRootController, router.getControllerWithTag(newRootTag));
}
@Test
@@ -58,8 +66,8 @@ public class RouterTests {
router.pushController(RouterTransaction.with(controller));
Assert.assertEquals(controller, router.getControllerWithInstanceId(controller.getInstanceId()));
Assert.assertNull(router.getControllerWithInstanceId("fake id"));
assertEquals(controller, router.getControllerWithInstanceId(controller.getInstanceId()));
assertNull(router.getControllerWithInstanceId("fake id"));
}
@Test
@@ -76,8 +84,8 @@ public class RouterTests {
router.pushController(RouterTransaction.with(controller2)
.tag(controller2Tag));
Assert.assertEquals(controller1, router.getControllerWithTag(controller1Tag));
Assert.assertEquals(controller2, router.getControllerWithTag(controller2Tag));
assertEquals(controller1, router.getControllerWithTag(controller1Tag));
assertEquals(controller2, router.getControllerWithTag(controller2Tag));
}
@Test
@@ -91,26 +99,26 @@ public class RouterTests {
router.pushController(RouterTransaction.with(controller1)
.tag(controller1Tag));
Assert.assertEquals(1, router.getBackstackSize());
assertEquals(1, router.getBackstackSize());
router.pushController(RouterTransaction.with(controller2)
.tag(controller2Tag));
Assert.assertEquals(2, router.getBackstackSize());
assertEquals(2, router.getBackstackSize());
router.popCurrentController();
Assert.assertEquals(1, router.getBackstackSize());
assertEquals(1, router.getBackstackSize());
Assert.assertEquals(controller1, router.getControllerWithTag(controller1Tag));
Assert.assertNull(router.getControllerWithTag(controller2Tag));
assertEquals(controller1, router.getControllerWithTag(controller1Tag));
assertNull(router.getControllerWithTag(controller2Tag));
router.popCurrentController();
Assert.assertEquals(0, router.getBackstackSize());
assertEquals(0, router.getBackstackSize());
Assert.assertNull(router.getControllerWithTag(controller1Tag));
Assert.assertNull(router.getControllerWithTag(controller2Tag));
assertNull(router.getControllerWithTag(controller1Tag));
assertNull(router.getControllerWithTag(controller2Tag));
}
@Test
@@ -139,11 +147,11 @@ public class RouterTests {
router.popToTag(controller2Tag);
Assert.assertEquals(2, router.getBackstackSize());
Assert.assertEquals(controller1, router.getControllerWithTag(controller1Tag));
Assert.assertEquals(controller2, router.getControllerWithTag(controller2Tag));
Assert.assertNull(router.getControllerWithTag(controller3Tag));
Assert.assertNull(router.getControllerWithTag(controller4Tag));
assertEquals(2, router.getBackstackSize());
assertEquals(controller1, router.getControllerWithTag(controller1Tag));
assertEquals(controller2, router.getControllerWithTag(controller2Tag));
assertNull(router.getControllerWithTag(controller3Tag));
assertNull(router.getControllerWithTag(controller4Tag));
}
@Test
@@ -167,10 +175,10 @@ public class RouterTests {
router.popController(controller2);
Assert.assertEquals(2, router.getBackstackSize());
Assert.assertEquals(controller1, router.getControllerWithTag(controller1Tag));
Assert.assertNull(router.getControllerWithTag(controller2Tag));
Assert.assertEquals(controller3, router.getControllerWithTag(controller3Tag));
assertEquals(2, router.getBackstackSize());
assertEquals(controller1, router.getControllerWithTag(controller1Tag));
assertNull(router.getControllerWithTag(controller2Tag));
assertEquals(controller3, router.getControllerWithTag(controller3Tag));
}
@Test
@@ -179,81 +187,73 @@ public class RouterTests {
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);
List<RouterTransaction> backstack = ListUtils.listOf(rootTransaction, middleTransaction, topTransaction);
router.setBackstack(backstack, null);
Assert.assertEquals(3, router.getBackstackSize());
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));
assertEquals(rootTransaction, fetchedBackstack.get(0));
assertEquals(middleTransaction, fetchedBackstack.get(1));
assertEquals(topTransaction, fetchedBackstack.get(2));
}
@Test
public void testNewSetBackstack() {
router.setRoot(RouterTransaction.with(new TestController()));
Assert.assertEquals(1, router.getBackstackSize());
assertEquals(1, router.getBackstackSize());
RouterTransaction rootTransaction = RouterTransaction.with(new TestController());
RouterTransaction middleTransaction = RouterTransaction.with(new TestController());
RouterTransaction topTransaction = RouterTransaction.with(new TestController());
List<RouterTransaction> backstack = new ArrayList<>();
backstack.add(rootTransaction);
backstack.add(middleTransaction);
backstack.add(topTransaction);
List<RouterTransaction> backstack = ListUtils.listOf(rootTransaction, middleTransaction, topTransaction);
router.setBackstack(backstack, null);
Assert.assertEquals(3, router.getBackstackSize());
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));
assertEquals(rootTransaction, fetchedBackstack.get(0));
assertEquals(middleTransaction, fetchedBackstack.get(1));
assertEquals(topTransaction, fetchedBackstack.get(2));
assertEquals(router, rootTransaction.controller.getRouter());
assertEquals(router, middleTransaction.controller.getRouter());
assertEquals(router, topTransaction.controller.getRouter());
}
@Test
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);
Assert.assertEquals(2, router.getBackstackSize());
assertEquals(2, router.getBackstackSize());
Assert.assertTrue(oldRootTransaction.controller.isAttached());
Assert.assertTrue(oldTopTransaction.controller.isAttached());
assertTrue(oldRootTransaction.controller.isAttached());
assertTrue(oldTopTransaction.controller.isAttached());
RouterTransaction rootTransaction = RouterTransaction.with(new TestController());
RouterTransaction middleTransaction = RouterTransaction.with(new TestController()).pushChangeHandler(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);
RouterTransaction middleTransaction = RouterTransaction.with(new TestController()).pushChangeHandler(MockChangeHandler.noRemoveViewOnPushHandler());
RouterTransaction topTransaction = RouterTransaction.with(new TestController()).pushChangeHandler(MockChangeHandler.noRemoveViewOnPushHandler());
List<RouterTransaction> backstack = ListUtils.listOf(rootTransaction, middleTransaction, topTransaction);
router.setBackstack(backstack, null);
Assert.assertEquals(3, router.getBackstackSize());
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));
assertEquals(rootTransaction, fetchedBackstack.get(0));
assertEquals(middleTransaction, fetchedBackstack.get(1));
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());
assertFalse(oldRootTransaction.controller.isAttached());
assertFalse(oldTopTransaction.controller.isAttached());
assertTrue(rootTransaction.controller.isAttached());
assertTrue(middleTransaction.controller.isAttached());
assertTrue(topTransaction.controller.isAttached());
}
@Test
@@ -261,61 +261,55 @@ public class RouterTests {
RouterTransaction rootTransaction = RouterTransaction.with(new TestController());
RouterTransaction topTransaction = RouterTransaction.with(new TestController());
List<RouterTransaction> backstack = new ArrayList<>();
backstack.add(rootTransaction);
backstack.add(topTransaction);
List<RouterTransaction> backstack = ListUtils.listOf(rootTransaction, topTransaction);
router.setBackstack(backstack, null);
Assert.assertEquals(2, router.getBackstackSize());
assertEquals(2, router.getBackstackSize());
List<RouterTransaction> fetchedBackstack = router.getBackstack();
Assert.assertEquals(rootTransaction, fetchedBackstack.get(0));
Assert.assertEquals(topTransaction, fetchedBackstack.get(1));
assertEquals(rootTransaction, fetchedBackstack.get(0));
assertEquals(topTransaction, fetchedBackstack.get(1));
RouterTransaction newTopTransaction = RouterTransaction.with(new TestController());
router.replaceTopController(newTopTransaction);
Assert.assertEquals(2, router.getBackstackSize());
assertEquals(2, router.getBackstackSize());
fetchedBackstack = router.getBackstack();
Assert.assertEquals(rootTransaction, fetchedBackstack.get(0));
Assert.assertEquals(newTopTransaction, fetchedBackstack.get(1));
assertEquals(rootTransaction, fetchedBackstack.get(0));
assertEquals(newTopTransaction, fetchedBackstack.get(1));
}
@Test
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);
RouterTransaction topTransaction = RouterTransaction.with(new TestController()).pushChangeHandler(MockChangeHandler.noRemoveViewOnPushHandler());
List<RouterTransaction> backstack = ListUtils.listOf(rootTransaction, topTransaction);
router.setBackstack(backstack, null);
Assert.assertEquals(2, router.getBackstackSize());
assertEquals(2, router.getBackstackSize());
Assert.assertTrue(rootTransaction.controller.isAttached());
Assert.assertTrue(topTransaction.controller.isAttached());
assertTrue(rootTransaction.controller.isAttached());
assertTrue(topTransaction.controller.isAttached());
List<RouterTransaction> fetchedBackstack = router.getBackstack();
Assert.assertEquals(rootTransaction, fetchedBackstack.get(0));
Assert.assertEquals(topTransaction, fetchedBackstack.get(1));
assertEquals(rootTransaction, fetchedBackstack.get(0));
assertEquals(topTransaction, fetchedBackstack.get(1));
RouterTransaction newTopTransaction = RouterTransaction.with(new TestController()).pushChangeHandler(new MockChangeHandler(false));
RouterTransaction newTopTransaction = RouterTransaction.with(new TestController()).pushChangeHandler(MockChangeHandler.noRemoveViewOnPushHandler());
router.replaceTopController(newTopTransaction);
newTopTransaction.pushChangeHandler().completeImmediately();
Assert.assertEquals(2, router.getBackstackSize());
assertEquals(2, router.getBackstackSize());
fetchedBackstack = router.getBackstack();
Assert.assertEquals(rootTransaction, fetchedBackstack.get(0));
Assert.assertEquals(newTopTransaction, fetchedBackstack.get(1));
assertEquals(rootTransaction, fetchedBackstack.get(0));
assertEquals(newTopTransaction, fetchedBackstack.get(1));
Assert.assertTrue(rootTransaction.controller.isAttached());
Assert.assertFalse(topTransaction.controller.isAttached());
Assert.assertTrue(newTopTransaction.controller.isAttached());
assertTrue(rootTransaction.controller.isAttached());
assertFalse(topTransaction.controller.isAttached());
assertTrue(newTopTransaction.controller.isAttached());
}
}
@@ -3,13 +3,19 @@ package com.bluelinelabs.conductor;
import android.os.Bundle;
import android.view.ViewGroup;
import org.junit.Assert;
import com.bluelinelabs.conductor.util.ActivityProxy;
import com.bluelinelabs.conductor.util.MockChangeHandler;
import com.bluelinelabs.conductor.util.TestController;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
@RunWith(RobolectricTestRunner.class)
@Config(manifest = Config.NONE)
public class TargetControllerTests {
@@ -34,21 +40,21 @@ public class TargetControllerTests {
final TestController controllerA = new TestController();
final TestController controllerB = new TestController();
Assert.assertNull(controllerA.getTargetController());
Assert.assertNull(controllerB.getTargetController());
assertNull(controllerA.getTargetController());
assertNull(controllerB.getTargetController());
router.pushController(RouterTransaction.with(controllerA)
.pushChangeHandler(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());
assertNull(controllerA.getTargetController());
assertEquals(controllerA, controllerB.getTargetController());
}
@Test
@@ -56,22 +62,22 @@ public class TargetControllerTests {
final TestController controllerA = new TestController();
final TestController controllerB = new TestController();
Assert.assertNull(controllerA.getTargetController());
Assert.assertNull(controllerB.getTargetController());
assertNull(controllerA.getTargetController());
assertNull(controllerB.getTargetController());
router.pushController(RouterTransaction.with(controllerA)
.pushChangeHandler(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), null);
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());
assertNull(controllerA.getTargetController());
assertEquals(controllerA, controllerB.getTargetController());
}
@Test
@@ -79,22 +85,22 @@ public class TargetControllerTests {
final TestController controllerA = new TestController();
final TestController controllerB = new TestController();
Assert.assertNull(controllerA.getTargetController());
Assert.assertNull(controllerB.getTargetController());
assertNull(controllerA.getTargetController());
assertNull(controllerB.getTargetController());
router.pushController(RouterTransaction.with(controllerA)
.pushChangeHandler(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), null);
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());
assertNull(controllerB.getTargetController());
assertEquals(controllerB, controllerA.getTargetController());
}
}
@@ -0,0 +1,149 @@
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 com.bluelinelabs.conductor.util.ActivityProxy;
import com.bluelinelabs.conductor.util.ViewUtils;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import static org.junit.Assert.assertEquals;
@RunWith(RobolectricTestRunner.class)
@Config(manifest = Config.NONE)
public class ViewAttachHandlerTests {
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);
assertEquals(0, viewAttachListener.attaches);
assertEquals(0, viewAttachListener.detaches);
ViewUtils.reportAttached(view, true);
assertEquals(1, viewAttachListener.attaches);
assertEquals(0, viewAttachListener.detaches);
ViewUtils.reportAttached(view, true);
assertEquals(1, viewAttachListener.attaches);
assertEquals(0, viewAttachListener.detaches);
ViewUtils.reportAttached(view, false);
assertEquals(1, viewAttachListener.attaches);
assertEquals(1, viewAttachListener.detaches);
ViewUtils.reportAttached(view, false);
assertEquals(1, viewAttachListener.attaches);
assertEquals(1, viewAttachListener.detaches);
ViewUtils.reportAttached(view, true);
assertEquals(2, viewAttachListener.attaches);
assertEquals(1, viewAttachListener.detaches);
}
@Test
public void testSimpleViewGroupAttachDetach() {
View view = new LinearLayout(activity);
viewAttachHandler.listenForAttach(view);
assertEquals(0, viewAttachListener.attaches);
assertEquals(0, viewAttachListener.detaches);
ViewUtils.reportAttached(view, true);
assertEquals(1, viewAttachListener.attaches);
assertEquals(0, viewAttachListener.detaches);
ViewUtils.reportAttached(view, true);
assertEquals(1, viewAttachListener.attaches);
assertEquals(0, viewAttachListener.detaches);
ViewUtils.reportAttached(view, false);
assertEquals(1, viewAttachListener.attaches);
assertEquals(1, viewAttachListener.detaches);
ViewUtils.reportAttached(view, false);
assertEquals(1, viewAttachListener.attaches);
assertEquals(1, viewAttachListener.detaches);
ViewUtils.reportAttached(view, true);
assertEquals(2, viewAttachListener.attaches);
assertEquals(1, viewAttachListener.detaches);
}
@Test
public void testNestedViewGroupAttachDetach() {
ViewGroup view = new LinearLayout(activity);
View child = new LinearLayout(activity);
view.addView(child);
viewAttachHandler.listenForAttach(view);
assertEquals(0, viewAttachListener.attaches);
assertEquals(0, viewAttachListener.detaches);
ViewUtils.reportAttached(view, true, false);
assertEquals(0, viewAttachListener.attaches);
assertEquals(0, viewAttachListener.detaches);
ViewUtils.reportAttached(child, true, false);
assertEquals(1, viewAttachListener.attaches);
assertEquals(0, viewAttachListener.detaches);
ViewUtils.reportAttached(view, true, false);
ViewUtils.reportAttached(child, true, false);
assertEquals(1, viewAttachListener.attaches);
assertEquals(0, viewAttachListener.detaches);
ViewUtils.reportAttached(view, false, false);
assertEquals(1, viewAttachListener.attaches);
assertEquals(1, viewAttachListener.detaches);
ViewUtils.reportAttached(view, false, false);
assertEquals(1, viewAttachListener.attaches);
assertEquals(1, viewAttachListener.detaches);
ViewUtils.reportAttached(view, true, false);
assertEquals(1, viewAttachListener.attaches);
assertEquals(1, viewAttachListener.detaches);
ViewUtils.reportAttached(child, true, false);
assertEquals(2, viewAttachListener.attaches);
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++;
}
}
}
@@ -1,4 +1,4 @@
package com.bluelinelabs.conductor;
package com.bluelinelabs.conductor.util;
import android.os.Bundle;
import android.support.annotation.IdRes;
@@ -1,4 +1,4 @@
package com.bluelinelabs.conductor;
package com.bluelinelabs.conductor.util;
import android.content.Context;
import android.os.IBinder;
@@ -1,4 +1,4 @@
package com.bluelinelabs.conductor;
package com.bluelinelabs.conductor.util;
import android.os.Parcel;
import android.os.Parcelable;
@@ -0,0 +1,67 @@
package com.bluelinelabs.conductor.util;
import android.view.View;
import java.util.ArrayList;
import java.util.List;
public class ChangeHandlerHistory {
private List<Entry> entries = new ArrayList<>();
public boolean isValidHistory = true;
public void addEntry(View from, View to, boolean isPush, MockChangeHandler handler) {
entries.add(new Entry(from, to, isPush, handler));
}
public int size() {
return entries.size();
}
public View fromViewAt(int index) {
return entries.get(index).from;
}
public View toViewAt(int index) {
return entries.get(index).to;
}
public boolean isPushAt(int index) {
return entries.get(index).isPush;
}
public MockChangeHandler changeHandlerAt(int index) {
return entries.get(index).changeHandler;
}
public View latestFromView() {
return fromViewAt(size() - 1);
}
public View latestToView() {
return toViewAt(size() - 1);
}
public boolean latestIsPush() {
return isPushAt(size() - 1);
}
public MockChangeHandler latestChangeHandler() {
return changeHandlerAt(size() - 1);
}
private static class Entry {
final View from;
final View to;
final boolean isPush;
final MockChangeHandler changeHandler;
Entry(View from, View to, boolean isPush, MockChangeHandler changeHandler) {
this.from = from;
this.to = to;
this.isPush = isPush;
this.changeHandler = changeHandler;
}
}
}
@@ -0,0 +1,16 @@
package com.bluelinelabs.conductor.util;
import java.util.ArrayList;
import java.util.List;
public class ListUtils {
public static <T> List<T> listOf(T... elements) {
List<T> list = new ArrayList<>();
for (T element : elements) {
list.add(element);
}
return list;
}
}
@@ -0,0 +1,118 @@
package com.bluelinelabs.conductor.util;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.view.View;
import android.view.ViewGroup;
import com.bluelinelabs.conductor.ControllerChangeHandler;
public class MockChangeHandler extends ControllerChangeHandler {
private static final String KEY_REMOVES_FROM_VIEW_ON_PUSH = "MockChangeHandler.removesFromViewOnPush";
private static final String KEY_TAG = "MockChangeHandler.tag";
public static class ChangeHandlerListener {
public void willStartChange() { }
public void didAttachOrDetach() { }
public void didEndChange() { }
}
private final ChangeHandlerListener listener;
private boolean removesFromViewOnPush;
public View from;
public View to;
public String tag;
public static MockChangeHandler defaultHandler() {
return new MockChangeHandler(true, null, null);
}
public static MockChangeHandler noRemoveViewOnPushHandler() {
return new MockChangeHandler(false, null, null);
}
public static MockChangeHandler listeningChangeHandler(@NonNull ChangeHandlerListener listener) {
return new MockChangeHandler(true, null, listener);
}
public static MockChangeHandler taggedHandler(String tag, boolean removeViewOnPush) {
return new MockChangeHandler(removeViewOnPush, tag, null);
}
public MockChangeHandler() {
listener = null;
}
private MockChangeHandler(boolean removesFromViewOnPush, String tag, ChangeHandlerListener listener) {
this.removesFromViewOnPush = removesFromViewOnPush;
if (listener == null) {
this.listener = new ChangeHandlerListener() { };
} else {
this.listener = listener;
}
}
@Override
public void performChange(@NonNull ViewGroup container, @Nullable View from, @Nullable View to, boolean isPush, @NonNull ControllerChangeCompletedListener changeListener) {
this.from = from;
this.to = to;
listener.willStartChange();
if (isPush) {
if (to != null) {
container.addView(to);
listener.didAttachOrDetach();
}
if (removesFromViewOnPush && from != null) {
container.removeView(from);
}
} else {
container.removeView(from);
listener.didAttachOrDetach();
if (to != null) {
container.addView(to);
}
}
changeListener.onChangeCompleted();
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);
bundle.putString(KEY_TAG, tag);
}
@Override
public void restoreFromBundle(@NonNull Bundle bundle) {
super.restoreFromBundle(bundle);
removesFromViewOnPush = bundle.getBoolean(KEY_REMOVES_FROM_VIEW_ON_PUSH);
tag = bundle.getString(KEY_TAG);
}
@NonNull
@Override
public ControllerChangeHandler copy() {
return new MockChangeHandler(removesFromViewOnPush, tag, listener);
}
@Override
public boolean isReusable() {
return true;
}
}
@@ -1,4 +1,4 @@
package com.bluelinelabs.conductor;
package com.bluelinelabs.conductor.util;
import android.app.Activity;
@@ -1,4 +1,4 @@
package com.bluelinelabs.conductor;
package com.bluelinelabs.conductor.util;
import android.content.Intent;
import android.os.Bundle;
@@ -11,6 +11,10 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import com.bluelinelabs.conductor.Controller;
import com.bluelinelabs.conductor.ControllerChangeHandler;
import com.bluelinelabs.conductor.ControllerChangeType;
public class TestController extends Controller {
@IdRes public static final int VIEW_ID = 2342;
@@ -19,11 +23,8 @@ public class TestController extends Controller {
private static final String KEY_CALL_STATE = "TestController.currentCallState";
public CallState currentCallState;
public TestController() {
currentCallState = new CallState();
}
public CallState currentCallState = new CallState();
public ChangeHandlerHistory changeHandlerHistory = new ChangeHandlerHistory();
@NonNull
@Override
@@ -53,6 +54,13 @@ public class TestController extends Controller {
protected void onChangeEnded(@NonNull ControllerChangeHandler changeHandler, @NonNull ControllerChangeType changeType) {
super.onChangeEnded(changeHandler, changeType);
currentCallState.changeEndCalls++;
if (changeHandler instanceof MockChangeHandler) {
MockChangeHandler mockHandler = (MockChangeHandler)changeHandler;
changeHandlerHistory.addEntry(mockHandler.from, mockHandler.to, changeType.isPush, mockHandler);
} else {
changeHandlerHistory.isValidHistory = false;
}
}
@Override
@@ -68,7 +76,7 @@ public class TestController extends Controller {
}
@Override
protected void onDestroyView(View view) {
protected void onDestroyView(@NonNull View view) {
super.onDestroyView(view);
currentCallState.destroyViewCalls++;
}
@@ -1,7 +1,8 @@
package com.bluelinelabs.conductor;
package com.bluelinelabs.conductor.util;
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) {
+10 -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"
}
@@ -37,6 +38,10 @@ android {
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
packagingOptions {
exclude 'META-INF/rxjava.properties'
}
}
dependencies {
@@ -49,6 +54,7 @@ dependencies {
compile project(':conductor-support')
compile project(':conductor-rxlifecycle')
compile project(':conductor-rxlifecycle2')
debugCompile rootProject.ext.leakCanary
releaseCompile rootProject.ext.leakCanaryNoOp
@@ -120,7 +120,7 @@ public class CircularRevealChangeHandler extends AnimatorChangeHandler {
this.cy = cy;
}
@Override
@Override @NonNull
protected Animator getAnimator(@NonNull ViewGroup container, View from, View to, boolean isPush, boolean toAddedToContainer) {
final float radius = (float) Math.hypot(cx, cy);
Animator animator = null;
@@ -16,7 +16,7 @@ public class CircularRevealChangeHandlerCompat extends CircularRevealChangeHandl
super(fromView, containerView);
}
@Override
@Override @NonNull
protected Animator getAnimator(@NonNull ViewGroup container, View from, View to, boolean isPush, boolean toAddedToContainer) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
return super.getAnimator(container, from, to, isPush, toAddedToContainer);
@@ -52,7 +52,7 @@ public class FlipChangeHandler extends AnimatorChangeHandler {
this.animationDuration = animationDuration;
}
@Override
@Override @NonNull
protected Animator getAnimator(@NonNull ViewGroup container, View from, View to, boolean isPush, boolean toAddedToContainer) {
AnimatorSet animatorSet = new AnimatorSet();
@@ -15,7 +15,7 @@ public class ScaleFadeChangeHandler extends AnimatorChangeHandler {
super(DEFAULT_ANIMATION_DURATION, true);
}
@Override
@Override @NonNull
protected Animator getAnimator(@NonNull ViewGroup container, View from, View to, boolean isPush, boolean toAddedToContainer) {
AnimatorSet animator = new AnimatorSet();
if (to != null && toAddedToContainer) {
@@ -46,7 +46,7 @@ public class DragDismissController extends BaseController {
}
@Override
protected void onDestroyView(View view) {
protected void onDestroyView(@NonNull View view) {
super.onDestroyView(view);
((ElasticDragDismissFrameLayout)view).removeListener(dragDismissListener);
@@ -27,6 +27,7 @@ import com.bluelinelabs.conductor.ControllerChangeType;
import com.bluelinelabs.conductor.RouterTransaction;
import com.bluelinelabs.conductor.changehandler.FadeChangeHandler;
import com.bluelinelabs.conductor.demo.R;
import com.bluelinelabs.conductor.demo.controllers.NavigationDemoController.DisplayUpMode;
import com.bluelinelabs.conductor.demo.controllers.base.BaseController;
import butterknife.BindView;
@@ -45,6 +46,7 @@ public class HomeController extends BaseController {
MASTER_DETAIL("Master Detail", R.color.grey_300),
DRAG_DISMISS("Drag Dismiss", R.color.lime_300),
RX_LIFECYCLE("Rx Lifecycle", R.color.teal_300),
RX_LIFECYCLE_2("Rx Lifecycle 2", R.color.brown_300),
OVERLAY("Overlay Controller", R.color.purple_300);
String title;
@@ -79,7 +81,7 @@ public class HomeController extends BaseController {
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
inflater.inflate(R.menu.home, menu);
}
@@ -94,7 +96,7 @@ public class HomeController extends BaseController {
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
if (item.getItemId() == R.id.about) {
SpannableString details = new SpannableString("A small, yet full-featured framework that allows building View-based Android applications");
details.setSpan(new AbsoluteSizeSpan(16, true), 0, details.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
@@ -115,7 +117,7 @@ public class HomeController extends BaseController {
content.append("\n\n");
content.append(link);
getChildRouter(overlayRoot, null)
getChildRouter(overlayRoot)
.setPopsLastView(true)
.setRoot(RouterTransaction.with(new OverlayController(content))
.pushChangeHandler(new FadeChangeHandler())
@@ -133,7 +135,7 @@ public class HomeController extends BaseController {
void onModelRowClick(HomeDemoModel model) {
switch (model) {
case NAVIGATION:
getRouter().pushController(RouterTransaction.with(new NavigationDemoController(0, true))
getRouter().pushController(RouterTransaction.with(new NavigationDemoController(0, DisplayUpMode.SHOW_FOR_CHILDREN_ONLY))
.pushChangeHandler(new FadeChangeHandler())
.popChangeHandler(new FadeChangeHandler())
.tag(NavigationDemoController.TAG_UP_TRANSACTION)
@@ -159,7 +161,7 @@ public class HomeController extends BaseController {
.popChangeHandler(new FadeChangeHandler()));
break;
case OVERLAY:
getChildRouter(overlayRoot, null)
getChildRouter(overlayRoot)
.setPopsLastView(true)
.setRoot(RouterTransaction.with(new OverlayController("I'm an overlay!"))
.pushChangeHandler(new FadeChangeHandler())
@@ -175,6 +177,11 @@ public class HomeController extends BaseController {
.pushChangeHandler(new FadeChangeHandler())
.popChangeHandler(new FadeChangeHandler()));
break;
case RX_LIFECYCLE_2:
getRouter().pushController(RouterTransaction.with(new RxLifecycle2Controller())
.pushChangeHandler(new FadeChangeHandler())
.popChangeHandler(new FadeChangeHandler()));
break;
case MULTIPLE_CHILD_ROUTERS:
getRouter().pushController(RouterTransaction.with(new MultipleChildRouterController())
.pushChangeHandler(new FadeChangeHandler())
@@ -91,7 +91,7 @@ public class MasterDetailListController extends BaseController {
ChildController controller = new ChildController(model.detail, model.backgroundColor, true);
if (twoPaneView) {
getChildRouter(detailContainer, null).setRoot(RouterTransaction.with(controller));
getChildRouter(detailContainer).setRoot(RouterTransaction.with(controller));
} else {
getRouter().pushController(RouterTransaction.with(controller)
.pushChangeHandler(new HorizontalChangeHandler())
@@ -8,6 +8,7 @@ import android.view.ViewGroup;
import com.bluelinelabs.conductor.Router;
import com.bluelinelabs.conductor.RouterTransaction;
import com.bluelinelabs.conductor.demo.R;
import com.bluelinelabs.conductor.demo.controllers.NavigationDemoController.DisplayUpMode;
import com.bluelinelabs.conductor.demo.controllers.base.BaseController;
import butterknife.BindViews;
@@ -26,9 +27,9 @@ public class MultipleChildRouterController extends BaseController {
super.onViewBound(view);
for (ViewGroup childContainer : childContainers) {
Router childRouter = getChildRouter(childContainer, null).setPopsLastView(false);
Router childRouter = getChildRouter(childContainer).setPopsLastView(false);
if (!childRouter.hasRootController()) {
childRouter.setRoot(RouterTransaction.with(new NavigationDemoController(0, false)));
childRouter.setRoot(RouterTransaction.with(new NavigationDemoController(0, DisplayUpMode.HIDE)));
}
}
}
@@ -7,6 +7,8 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import com.bluelinelabs.conductor.ControllerChangeHandler;
import com.bluelinelabs.conductor.ControllerChangeType;
import com.bluelinelabs.conductor.RouterTransaction;
import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler;
import com.bluelinelabs.conductor.demo.R;
@@ -19,27 +21,42 @@ import butterknife.OnClick;
public class NavigationDemoController extends BaseController {
public enum DisplayUpMode {
SHOW,
SHOW_FOR_CHILDREN_ONLY,
HIDE;
private DisplayUpMode getDisplayUpModeForChild() {
switch (this) {
case HIDE:
return HIDE;
default:
return SHOW;
}
}
}
public static final String TAG_UP_TRANSACTION = "NavigationDemoController.up";
private static final String KEY_INDEX = "NavigationDemoController.index";
private static final String KEY_DISPLAY_UP = "NavigationDemoController.displayUp";
private static final String KEY_DISPLAY_UP_MODE = "NavigationDemoController.displayUpMode";
@BindView(R.id.tv_title) TextView tvTitle;
private int index;
private boolean displayUp;
private DisplayUpMode displayUpMode;
public NavigationDemoController(int index, boolean displayUpButton) {
public NavigationDemoController(int index, DisplayUpMode displayUpMode) {
this(new BundleBuilder(new Bundle())
.putInt(KEY_INDEX, index)
.putBoolean(KEY_DISPLAY_UP, displayUpButton)
.putInt(KEY_DISPLAY_UP_MODE, displayUpMode.ordinal())
.build());
}
public NavigationDemoController(Bundle args) {
super(args);
index = args.getInt(KEY_INDEX);
displayUp = args.getBoolean(KEY_DISPLAY_UP);
displayUpMode = DisplayUpMode.values()[args.getInt(KEY_DISPLAY_UP_MODE)];
}
@NonNull
@@ -52,7 +69,7 @@ public class NavigationDemoController extends BaseController {
protected void onViewBound(@NonNull View view) {
super.onViewBound(view);
if (!displayUp) {
if (displayUpMode != DisplayUpMode.SHOW) {
view.findViewById(R.id.btn_up).setVisibility(View.GONE);
}
@@ -60,13 +77,36 @@ public class NavigationDemoController extends BaseController {
tvTitle.setText(getResources().getString(R.string.navigation_title, index));
}
@Override
protected void onChangeEnded(@NonNull ControllerChangeHandler changeHandler, @NonNull ControllerChangeType changeType) {
super.onChangeEnded(changeHandler, changeType);
setButtonsEnabled(true);
}
@Override
protected void onChangeStarted(@NonNull ControllerChangeHandler changeHandler, @NonNull ControllerChangeType changeType) {
super.onChangeStarted(changeHandler, changeType);
setButtonsEnabled(false);
}
@Override
protected String getTitle() {
return "Navigation Demos";
}
private void setButtonsEnabled(boolean enabled) {
final View view = getView();
if (view != null) {
view.findViewById(R.id.btn_next).setEnabled(enabled);
view.findViewById(R.id.btn_up).setEnabled(enabled);
view.findViewById(R.id.btn_pop_to_root).setEnabled(enabled);
}
}
@OnClick(R.id.btn_next) void onNextClicked() {
getRouter().pushController(RouterTransaction.with(new NavigationDemoController(index + 1, displayUp))
getRouter().pushController(RouterTransaction.with(new NavigationDemoController(index + 1, displayUpMode.getDisplayUpModeForChild()))
.pushChangeHandler(new HorizontalChangeHandler())
.popChangeHandler(new HorizontalChangeHandler()));
}
@@ -12,6 +12,8 @@ import com.bluelinelabs.conductor.demo.R;
import com.bluelinelabs.conductor.demo.controllers.base.BaseController;
import com.bluelinelabs.conductor.support.ControllerPagerAdapter;
import java.util.Locale;
import butterknife.BindView;
public class PagerController extends BaseController {
@@ -27,7 +29,7 @@ public class PagerController extends BaseController {
pagerAdapter = new ControllerPagerAdapter(this, false) {
@Override
public Controller getItem(int position) {
return new ChildController(String.format("Child #%d (Swipe to see more)", position), PAGE_COLORS[position], true);
return new ChildController(String.format(Locale.getDefault(), "Child #%d (Swipe to see more)", position), PAGE_COLORS[position], true);
}
@Override
@@ -50,7 +52,7 @@ public class PagerController extends BaseController {
}
@Override
protected void onDestroyView(View view) {
protected void onDestroyView(@NonNull View view) {
viewPager.setAdapter(null);
super.onDestroyView(view);
}
@@ -42,7 +42,7 @@ public class ParentController extends BaseController {
private void addChild(final int index) {
@IdRes final int frameId = getResources().getIdentifier("child_content_" + (index + 1), "id", getActivity().getPackageName());
final ViewGroup container = (ViewGroup)getView().findViewById(frameId);
final Router childRouter = getChildRouter(container, null).setPopsLastView(true);
final Router childRouter = getChildRouter(container).setPopsLastView(true);
if (!childRouter.hasRootController()) {
ChildController childController = new ChildController("Child Controller #" + index, ColorUtil.getMaterialColor(getResources(), index), false);
@@ -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);
}
}
}
}
@@ -0,0 +1,163 @@
package com.bluelinelabs.conductor.demo.controllers;
import android.support.annotation.NonNull;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import com.bluelinelabs.conductor.ControllerChangeHandler;
import com.bluelinelabs.conductor.ControllerChangeType;
import com.bluelinelabs.conductor.RouterTransaction;
import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler;
import com.bluelinelabs.conductor.demo.ActionBarProvider;
import com.bluelinelabs.conductor.demo.DemoApplication;
import com.bluelinelabs.conductor.demo.R;
import com.bluelinelabs.conductor.rxlifecycle2.ControllerEvent;
import com.bluelinelabs.conductor.rxlifecycle2.RxController;
import java.util.concurrent.TimeUnit;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
import butterknife.Unbinder;
import io.reactivex.Observable;
import io.reactivex.functions.Action;
import io.reactivex.functions.Consumer;
// Shamelessly borrowed from the official RxLifecycle demo by Trello and adapted for Conductor Controllers
// instead of Activities or Fragments.
public class RxLifecycle2Controller extends RxController {
private static final String TAG = "RxLifecycleController";
@BindView(R.id.tv_title) TextView tvTitle;
private Unbinder unbinder;
private boolean hasExited;
public RxLifecycle2Controller() {
Observable.interval(1, TimeUnit.SECONDS)
.doOnDispose(new Action() {
@Override
public void run() {
Log.i(TAG, "Disposing from constructor");
}
})
.compose(this.<Long>bindUntilEvent(ControllerEvent.DESTROY))
.subscribe(new Consumer<Long>() {
@Override
public void accept(Long num) {
Log.i(TAG, "Started in constructor, running until onDestroy(): " + num);
}
});
}
@NonNull
@Override
protected View onCreateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) {
Log.i(TAG, "onCreateView() called");
View view = inflater.inflate(R.layout.controller_rxlifecycle, container, false);
unbinder = ButterKnife.bind(this, view);
tvTitle.setText(getResources().getString(R.string.rxlifecycle_title, TAG));
Observable.interval(1, TimeUnit.SECONDS)
.doOnDispose(new Action() {
@Override
public void run() {
Log.i(TAG, "Disposing from onCreateView)");
}
})
.compose(this.<Long>bindUntilEvent(ControllerEvent.DESTROY_VIEW))
.subscribe(new Consumer<Long>() {
@Override
public void accept(Long num) {
Log.i(TAG, "Started in onCreateView(), running until onDestroyView(): " + num);
}
});
return view;
}
@Override
protected void onAttach(@NonNull View view) {
super.onAttach(view);
Log.i(TAG, "onAttach() called");
(((ActionBarProvider)getActivity()).getSupportActionBar()).setTitle("RxLifecycle2 Demo");
Observable.interval(1, TimeUnit.SECONDS)
.doOnDispose(new Action() {
@Override
public void run() {
Log.i(TAG, "Disposing from onAttach()");
}
})
.compose(this.<Long>bindUntilEvent(ControllerEvent.DETACH))
.subscribe(new Consumer<Long>() {
@Override
public void accept(Long num) {
Log.i(TAG, "Started in onAttach(), running until onDetach(): " + num);
}
});
}
@Override
protected void onDestroyView(@NonNull View view) {
super.onDestroyView(view);
Log.i(TAG, "onDestroyView() called");
unbinder.unbind();
unbinder = null;
}
@Override
protected void onDetach(@NonNull View view) {
super.onDetach(view);
Log.i(TAG, "onDetach() called");
}
@Override
public void onDestroy() {
super.onDestroy();
Log.i(TAG, "onDestroy() called");
if (hasExited) {
DemoApplication.refWatcher.watch(this);
}
}
@Override
protected void onChangeEnded(@NonNull ControllerChangeHandler changeHandler, @NonNull ControllerChangeType changeType) {
super.onChangeEnded(changeHandler, changeType);
hasExited = !changeType.isEnter;
if (isDestroyed()) {
DemoApplication.refWatcher.watch(this);
}
}
@OnClick(R.id.btn_next_release_view) void onNextWithReleaseClicked() {
setRetainViewMode(RetainViewMode.RELEASE_DETACH);
getRouter().pushController(RouterTransaction.with(new TextController("Logcat should now report that the observables from onAttach() and onViewBound() have been disposed of, while the constructor observable is still running."))
.pushChangeHandler(new HorizontalChangeHandler())
.popChangeHandler(new HorizontalChangeHandler()));
}
@OnClick(R.id.btn_next_retain_view) void onNextWithRetainClicked() {
setRetainViewMode(RetainViewMode.RETAIN_DETACH);
getRouter().pushController(RouterTransaction.with(new TextController("Logcat should now report that the observables from onAttach() has been disposed of, while the constructor and onViewBound() observables are still running."))
.pushChangeHandler(new HorizontalChangeHandler())
.popChangeHandler(new HorizontalChangeHandler()));
}
}
@@ -7,30 +7,38 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import com.bluelinelabs.conductor.ControllerChangeHandler;
import com.bluelinelabs.conductor.ControllerChangeType;
import com.bluelinelabs.conductor.RouterTransaction;
import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler;
import com.bluelinelabs.conductor.demo.ActionBarProvider;
import com.bluelinelabs.conductor.demo.DemoApplication;
import com.bluelinelabs.conductor.demo.R;
import com.bluelinelabs.conductor.demo.controllers.base.BaseController;
import com.bluelinelabs.conductor.rxlifecycle.ControllerEvent;
import com.bluelinelabs.conductor.rxlifecycle.RxController;
import java.util.concurrent.TimeUnit;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
import butterknife.Unbinder;
import rx.Observable;
import rx.functions.Action0;
import rx.functions.Action1;
// Shamelessly borrowed from the official RxLifecycle demo by Trello and adapted for Conductor Controllers
// instead of Activities or Fragments.
public class RxLifecycleController extends BaseController {
public class RxLifecycleController extends RxController {
private static final String TAG = "RxLifecycleController";
@BindView(R.id.tv_title) TextView tvTitle;
public RxLifecycleController() {
private Unbinder unbinder;
private boolean hasExited;
public RxLifecycleController() {
Observable.interval(1, TimeUnit.SECONDS)
.doOnUnsubscribe(new Action0() {
@Override
@@ -47,10 +55,14 @@ public class RxLifecycleController extends BaseController {
});
}
@NonNull
@Override
public void onViewBound(@NonNull View view) {
protected View onCreateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) {
Log.i(TAG, "onCreateView() called");
View view = inflater.inflate(R.layout.controller_rxlifecycle, container, false);
unbinder = ButterKnife.bind(this, view);
tvTitle.setText(getResources().getString(R.string.rxlifecycle_title, TAG));
Observable.interval(1, TimeUnit.SECONDS)
@@ -67,6 +79,8 @@ public class RxLifecycleController extends BaseController {
Log.i(TAG, "Started in onCreateView(), running until onDestroyView(): " + num);
}
});
return view;
}
@Override
@@ -75,6 +89,8 @@ public class RxLifecycleController extends BaseController {
Log.i(TAG, "onAttach() called");
(((ActionBarProvider)getActivity()).getSupportActionBar()).setTitle("RxLifecycle Demo");
Observable.interval(1, TimeUnit.SECONDS)
.doOnUnsubscribe(new Action0() {
@Override
@@ -92,10 +108,13 @@ public class RxLifecycleController extends BaseController {
}
@Override
protected void onDestroyView(View view) {
protected void onDestroyView(@NonNull View view) {
super.onDestroyView(view);
Log.i(TAG, "onDestroyView() called");
unbinder.unbind();
unbinder = null;
}
@Override
@@ -110,17 +129,20 @@ public class RxLifecycleController extends BaseController {
super.onDestroy();
Log.i(TAG, "onDestroy() called");
if (hasExited) {
DemoApplication.refWatcher.watch(this);
}
}
@Override
protected String getTitle() {
return "RxLifecycle Demo";
}
protected void onChangeEnded(@NonNull ControllerChangeHandler changeHandler, @NonNull ControllerChangeType changeType) {
super.onChangeEnded(changeHandler, changeType);
@NonNull
@Override
protected View inflateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) {
return inflater.inflate(R.layout.controller_rxlifecycle, container, false);
hasExited = !changeType.isEnter;
if (isDestroyed()) {
DemoApplication.refWatcher.watch(this);
}
}
@OnClick(R.id.btn_next_release_view) void onNextWithReleaseClicked() {
@@ -6,12 +6,12 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.bluelinelabs.conductor.rxlifecycle.RxController;
import com.bluelinelabs.conductor.Controller;
import butterknife.ButterKnife;
import butterknife.Unbinder;
public abstract class ButterKnifeController extends RxController {
public abstract class ButterKnifeController extends Controller {
private Unbinder unbinder;
@@ -34,7 +34,7 @@ public abstract class ButterKnifeController extends RxController {
protected void onViewBound(@NonNull View view) { }
@Override
protected void onDestroyView(View view) {
protected void onDestroyView(@NonNull View view) {
super.onDestroyView(view);
unbinder.unbind();
unbinder = null;
@@ -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
@@ -3,7 +3,8 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#88000000" >
android:background="#88000000"
android:clickable="true" >
<FrameLayout
android:layout_width="match_parent"
@@ -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>
+18 -18
View File
@@ -1,23 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<array name="mdcolor_300">
<item name="red_300" type="color">@color/red_300</item>
<item name="light_green_300" type="color">@color/light_green_300</item>
<item name="teal_300" type="color">@color/teal_300</item>
<item name="pink_300" type="color">@color/pink_300</item>
<item name="brown_300" type="color">@color/brown_300</item>
<item name="purple_300" type="color">@color/purple_300</item>
<item name="lime_300" type="color">@color/lime_300</item>
<item name="blue_grey_300" type="color">@color/blue_grey_300</item>
<item name="deep_purple_300" type="color">@color/deep_purple_300</item>
<item name="green_300" type="color">@color/green_300</item>
<item name="indigo_300" type="color">@color/indigo_300</item>
<item name="yellow_300" type="color">@color/yellow_300</item>
<item name="blue_300" type="color">@color/blue_300</item>
<item name="orange_300" type="color">@color/orange_300</item>
<item name="light_blue_300" type="color">@color/light_blue_300</item>
<item name="deep_orange_300" type="color">@color/deep_orange_300</item>
<item name="cyan_300" type="color">@color/cyan_300</item>
<item name="grey_300" type="color">@color/grey_300</item>
<item name="md_red_300" type="color">@color/red_300</item>
<item name="md_light_green_300" type="color">@color/light_green_300</item>
<item name="md_teal_300" type="color">@color/teal_300</item>
<item name="md_pink_300" type="color">@color/pink_300</item>
<item name="md_brown_300" type="color">@color/brown_300</item>
<item name="md_purple_300" type="color">@color/purple_300</item>
<item name="md_lime_300" type="color">@color/lime_300</item>
<item name="md_blue_grey_300" type="color">@color/blue_grey_300</item>
<item name="md_deep_purple_300" type="color">@color/deep_purple_300</item>
<item name="md_green_300" type="color">@color/green_300</item>
<item name="md_indigo_300" type="color">@color/indigo_300</item>
<item name="md_yellow_300" type="color">@color/yellow_300</item>
<item name="md_blue_300" type="color">@color/blue_300</item>
<item name="md_orange_300" type="color">@color/orange_300</item>
<item name="md_light_blue_300" type="color">@color/light_blue_300</item>
<item name="md_deep_orange_300" type="color">@color/deep_orange_300</item>
<item name="md_cyan_300" type="color">@color/cyan_300</item>
<item name="md_grey_300" type="color">@color/grey_300</item>
</array>
</resources>
+39 -20
View File
@@ -1,30 +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'
rxAndroid = 'io.reactivex:rxandroid:1.2.1'
rxLifecycle = 'com.trello:rxlifecycle:0.8.0'
rxLifecycleAndroid = 'com.trello:rxlifecycle-android:0.8.0'
leakCanary = "com.squareup.leakcanary:leakcanary-android:$leakCanaryVersion"
leakCanaryNoOp = "com.squareup.leakcanary:leakcanary-android-no-op:$leakCanaryVersion"
junit = 'junit:junit:4.11'
roboelectric = 'org.robolectric:robolectric:3.0'
rxJava = "io.reactivex:rxjava:$rxJavaVersion"
rxJava2 = "io.reactivex.rxjava2:rxjava:$rxJava2Version"
rxLifecycle = "com.trello:rxlifecycle:$rxLifecycleVersion"
rxLifecycleAndroid = "com.trello:rxlifecycle-android:$rxLifecycleVersion"
rxLifecycle2 = "com.trello.rxlifecycle2:rxlifecycle:$rxLifecycle2Version"
rxLifecycleAndroid2 = "com.trello.rxlifecycle2:rxlifecycle-android:$rxLifecycle2Version"
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.4-SNAPSHOT
VERSION_NAME=2.0.8-SNAPSHOT
VERSION_CODE=2
GROUP=com.bluelinelabs
+5 -4
View File
@@ -1,5 +1,6 @@
include ':conductor'
include':conductor-support'
include':conductor-rxlifecycle'
include':conductor-lint'
include':demo'
include ':conductor-support'
include ':conductor-rxlifecycle'
include ':conductor-rxlifecycle2'
include ':conductor-lint'
include ':demo'