Compare commits
47 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3b275d31c2 | |||
| 0e21c8c9c1 | |||
| 8297e0273d | |||
| 46519c2c2c | |||
| 211da8b2ea | |||
| 26db962168 | |||
| f4c1c6ccf5 | |||
| c89caa87e0 | |||
| 2748566437 | |||
| 506c99ed41 | |||
| 4fe0ec5f51 | |||
| afa93f2cc1 | |||
| 836f92b615 | |||
| 94c817bbd9 | |||
| fc1fee3e17 | |||
| 76b7572a01 | |||
| 3fc63b7f5f | |||
| 0ef52211a2 | |||
| 7574131940 | |||
| 1ab9a4c4f6 | |||
| 3bc23bd5cd | |||
| 5f138e5d43 | |||
| 03701d05a9 | |||
| a19968e0c9 | |||
| 5501ab2ac8 | |||
| 804fdb615e | |||
| c01b2a74d6 | |||
| 8a8622c261 | |||
| 6820aa7d6a | |||
| 9ce27e4dee | |||
| 3c8ad0a833 | |||
| a720ac57e8 | |||
| 7d6901389b | |||
| e54e88bf0d | |||
| 010117603c | |||
| cd11ac9d6b | |||
| e78347709b | |||
| 341debc5b9 | |||
| 2346e48154 | |||
| 4174e12958 | |||
| c0abed0813 | |||
| 6fdb1d6ed3 | |||
| 3334b8e21f | |||
| 240424dc63 | |||
| f768e9ab00 | |||
| 4c89124683 | |||
| e0bbd48935 |
@@ -1,26 +0,0 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Deploy a jar, source jar, and javadoc jar to Sonatype's snapshot repo.
|
||||
#
|
||||
# Adapted from https://coderwall.com/p/9b_lfq and
|
||||
# http://benlimmer.com/2013/12/26/automatically-publish-javadoc-to-gh-pages-with-travis-ci/
|
||||
|
||||
SLUG="bluelinelabs/Conductor"
|
||||
JDK="oraclejdk8"
|
||||
BRANCH="develop"
|
||||
|
||||
set -e
|
||||
|
||||
if [ "$TRAVIS_REPO_SLUG" != "$SLUG" ]; then
|
||||
echo "Skipping snapshot deployment: wrong repository. Expected '$SLUG' but was '$TRAVIS_REPO_SLUG'."
|
||||
elif [ "$TRAVIS_JDK_VERSION" != "$JDK" ]; then
|
||||
echo "Skipping snapshot deployment: wrong JDK. Expected '$JDK' but was '$TRAVIS_JDK_VERSION'."
|
||||
elif [ "$TRAVIS_PULL_REQUEST" != "false" ]; then
|
||||
echo "Skipping snapshot deployment: was pull request."
|
||||
elif [ "$TRAVIS_BRANCH" != "$BRANCH" ]; then
|
||||
echo "Skipping snapshot deployment: wrong branch. Expected '$BRANCH' but was '$TRAVIS_BRANCH'."
|
||||
else
|
||||
echo "Deploying snapshot..."
|
||||
./gradlew clean uploadArchives
|
||||
echo "Snapshot deployed!"
|
||||
fi
|
||||
@@ -0,0 +1,38 @@
|
||||
name: Test & Publish
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: set up JDK 11
|
||||
uses: actions/setup-java@v2
|
||||
with:
|
||||
distribution: 'adopt'
|
||||
java-version: '11'
|
||||
- name: Grant execute permission for gradlew
|
||||
run: chmod +x gradlew
|
||||
- name: Test with Gradle
|
||||
run: ./gradlew test
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.ref == 'refs/heads/develop'
|
||||
needs: test
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: set up JDK 11
|
||||
uses: actions/setup-java@v2
|
||||
with:
|
||||
distribution: 'adopt'
|
||||
java-version: '11'
|
||||
- name: Grant execute permission for gradlew
|
||||
run: chmod +x gradlew
|
||||
- name: Test with Gradle
|
||||
run: ./gradlew clean uploadArchives
|
||||
env:
|
||||
ORG_GRADLE_PROJECT_SONATYPE_NEXUS_USERNAME: ${{ secrets.SONATYPE_NEXUS_USERNAME }}
|
||||
ORG_GRADLE_PROJECT_SONATYPE_NEXUS_PASSWORD: ${{ secrets.SONATYPE_NEXUS_PASSWORD }}
|
||||
+1
-3
@@ -35,6 +35,4 @@ pom.xml.*
|
||||
local.properties
|
||||
|
||||
*.prefs
|
||||
|
||||
# The keystore file
|
||||
app/spothero-release.keystore
|
||||
.DS_Store
|
||||
|
||||
Generated
+1
@@ -6,3 +6,4 @@
|
||||
/caches
|
||||
/gradle.xml
|
||||
/modules.xml
|
||||
/compiler.xml
|
||||
|
||||
Generated
+9
-1
@@ -118,7 +118,15 @@
|
||||
</arrangement>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="kotlin">
|
||||
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
||||
<option name="CALL_PARAMETERS_WRAP" value="5" />
|
||||
<option name="CALL_PARAMETERS_LPAREN_ON_NEXT_LINE" value="true" />
|
||||
<option name="CALL_PARAMETERS_RPAREN_ON_NEXT_LINE" value="true" />
|
||||
<option name="METHOD_PARAMETERS_WRAP" value="5" />
|
||||
<option name="METHOD_PARAMETERS_LPAREN_ON_NEXT_LINE" value="true" />
|
||||
<option name="METHOD_PARAMETERS_RPAREN_ON_NEXT_LINE" value="true" />
|
||||
<option name="EXTENDS_LIST_WRAP" value="1" />
|
||||
<option name="METHOD_CALL_CHAIN_WRAP" value="1" />
|
||||
<option name="ASSIGNMENT_WRAP" value="1" />
|
||||
<indentOptions>
|
||||
<option name="INDENT_SIZE" value="2" />
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="2" />
|
||||
|
||||
-35
@@ -1,35 +0,0 @@
|
||||
language: android
|
||||
|
||||
android:
|
||||
components:
|
||||
- tools
|
||||
- build-tools-28.0.3
|
||||
- android-28
|
||||
- extra-android-m2repository
|
||||
licenses:
|
||||
- '.+'
|
||||
|
||||
script:
|
||||
- ./gradlew test
|
||||
|
||||
jdk:
|
||||
- oraclejdk8
|
||||
|
||||
branches:
|
||||
only:
|
||||
- develop
|
||||
- master
|
||||
|
||||
after_success:
|
||||
- .buildscript/deploy_snapshot.sh
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.gradle
|
||||
|
||||
sudo: false
|
||||
|
||||
env:
|
||||
global:
|
||||
- secure: Px0uj7aMtKUVZVBnoEjHrEHh4cwO2qKJrHHPvwBiDqhwLBaWQIVXYOy0njaYc5o8p96Fv7bMu7NZx/72vMu1+nmTKxgzrMIxvMW6kczBvJUpv6xd1NuC34x+5Xq5gBNOFvb8JarWStcKgIvnFqvBsRUeI1Hsz7Olb8HF+fEo1kShuP18ezSsBkXruw8JuGiU9x0kq4YhZ7vRvFnc3sJX2FL6heuvQsnUWrolUOsKRadNkCibo+Euuls7ExvbbAXN4LEO3rs0G2eBUBbi2wXvTMG9symtItEHTMPO7K+aQfNQnHsY91TYveH/IJM1u5p6OldsUSOUigzpDmpVYW94aLuJaYqc6Ibq3eUws+tv2didOHXZW5zOCFjldDFBIQFPA3fih/wK/JP0taQ0uIu1+2eifvuERarMkGsYlOFe5tJd10ipi+kK5vNxoRwS9kGv5WwP5fVPX2m5XbD2y1LnugCCcAumfNX7NyNBIRqTy7BP34O3EMLZpMxjwSLnUBnYd4V/0LEvoVmbYmrLhWwpojBJmdwe2QknrPuvRErxNujRA1uEVupbU5A6RW1BmrtzSahJYoROI+ayG7UTOSbFN8+DorER1SUXsrOFlawak8yWsoi6OIynTKucrFM3YcBdJ3Su5AIhfBAOASZa6CUa6sn6Zo8mHmDVGKeckvXnLCU=
|
||||
- secure: 3Dj6roVTO2a9Z8lwlTGzJJ+QGeyIYSuQ/Z6YsYnW3wB9Mw36uWqw9rOmMNIGjjyAlER8bKgalHr90Pus87oaNaIlbEyvq+L+I0FVAwognViFjo/a2apCcq81THoKjT1l0sgGRzvLNDynQe1q2L0tOu21wtBhMLb7FKQYB9+oD3H+rcU63xD2tv3ToJz+j+dccZ9nrtgk0MQ1xAeMtEb4tdq3flKATKhIuDkp9chaDxx/ZGwyhdE0UP29UUyP5Np1QvpFAlAJIZloZPvde2e05fwxTh4rwUCetkfJknDK6WrGiq97WGRXJpfORNuwGn7jxDCtgxcAm9nGF8qmI/v78BhjJ857CfJBTLGv4QI0RszlhXyezJqqRYjCn9S4yx8UAOixVJfJfFNHLqD4MFn41b7j8J3HDJPxNt0t/qYhUMrgrZVosNOUqhwCyQTKDqtrpvmSUbhHpk2+fxZF1GEL5N540rA0OjLmFUUEDSvRQVaa/waeqXrRefOhsXIx20dHs93C6XDffjnxVMKlqtPab2MlHV/23QCuSa6eW+lKyOZ6ZkWLwwDsLbnuD0WJfJpiL1xaTdPJNIDb6kB/Bno4V1O4xrYncEOaA4Vr4amO8le9Q33/QeNrUZSXs/eye5t0f02wCzubuiEDVEeoR/qj5+5CNWv3AWr/f8AgXp80Pzw=
|
||||
@@ -20,27 +20,27 @@ Conductor is architecture-agnostic and does not try to force any design decision
|
||||
## Installation
|
||||
|
||||
```gradle
|
||||
implementation 'com.bluelinelabs:conductor:3.0.0-rc5'
|
||||
implementation 'com.bluelinelabs:conductor:3.1.3'
|
||||
|
||||
// AndroidX Transition change handlers:
|
||||
implementation 'com.bluelinelabs:conductor-androidx-transition:3.0.0-rc5'
|
||||
implementation 'com.bluelinelabs:conductor-androidx-transition:3.1.3'
|
||||
|
||||
// ViewPager PagerAdapter:
|
||||
implementation 'com.bluelinelabs:conductor-viewpager:3.0.0-rc5'
|
||||
implementation 'com.bluelinelabs:conductor-viewpager:3.1.3'
|
||||
|
||||
// RxJava2 lifecycle support:
|
||||
implementation 'com.bluelinelabs:conductor-rxlifecycle2:3.0.0-rc5'
|
||||
// ViewPager2 Adapter:
|
||||
implementation 'com.bluelinelabs:conductor-viewpager2:3.1.3'
|
||||
|
||||
// RxJava2 Autodispose support:
|
||||
implementation 'com.bluelinelabs:conductor-autodispose:3.0.0-rc5'
|
||||
implementation 'com.bluelinelabs:conductor-autodispose:3.1.3'
|
||||
|
||||
// Lifecycle-aware Controllers (architecture components):
|
||||
implementation 'com.bluelinelabs:conductor-archlifecycle:3.0.0-rc5'
|
||||
implementation 'com.bluelinelabs:conductor-archlifecycle:3.1.3'
|
||||
```
|
||||
|
||||
**SNAPSHOT**
|
||||
|
||||
Just use `3.0.1-SNAPSHOT` as your version number in any of the dependencies above and add the url to the snapshot repository:
|
||||
Just use `3.1.4-SNAPSHOT` as your version number in any of the dependencies above and add the url to the snapshot repository:
|
||||
|
||||
```gradle
|
||||
allprojects {
|
||||
@@ -76,7 +76,8 @@ public class MainActivity extends Activity {
|
||||
|
||||
ViewGroup container = (ViewGroup) findViewById(R.id.controller_container);
|
||||
|
||||
router = Conductor.attachRouter(this, container, savedInstanceState);
|
||||
router = Conductor.attachRouter(this, container, savedInstanceState)
|
||||
.setPopRootControllerMode(PopRootControllerMode.NEVER);
|
||||
if (!router.hasRootController()) {
|
||||
router.setRoot(RouterTransaction.with(new HomeController()));
|
||||
}
|
||||
|
||||
+4
-22
@@ -2,39 +2,21 @@ buildscript {
|
||||
apply from: rootProject.file('dependencies.gradle')
|
||||
|
||||
repositories {
|
||||
jcenter()
|
||||
mavenCentral()
|
||||
google()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:3.6.3'
|
||||
classpath "com.android.tools.build:gradle:$agpVersion"
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
|
||||
classpath "com.vanniktech:gradle-maven-publish-plugin:$mvnPublishVersion"
|
||||
classpath "org.jetbrains.dokka:dokka-gradle-plugin:$dokkaVersion"
|
||||
}
|
||||
}
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven { url 'https://maven.google.com' }
|
||||
jcenter()
|
||||
}
|
||||
|
||||
plugins.withType(com.android.build.gradle.BasePlugin).configureEach { plugin ->
|
||||
plugin.extension.compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
}
|
||||
}
|
||||
|
||||
tasks.withType(JavaCompile).configureEach { task ->
|
||||
task.sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
task.targetCompatibility = JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
tasks.withType(org.jetbrains.kotlin.gradle.dsl.KotlinJvmCompile).configureEach { task ->
|
||||
task.kotlinOptions {
|
||||
jvmTarget = "1.8"
|
||||
}
|
||||
google()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
apply plugin: 'java-library'
|
||||
apply plugin: 'kotlin'
|
||||
|
||||
configurations {
|
||||
lintChecks
|
||||
@@ -7,7 +7,9 @@ configurations {
|
||||
dependencies {
|
||||
compileOnly rootProject.ext.lintapi
|
||||
compileOnly rootProject.ext.lintchecks
|
||||
compileOnly rootProject.ext.kotlinStd
|
||||
|
||||
testImplementation rootProject.ext.junit
|
||||
testImplementation rootProject.ext.lint
|
||||
testImplementation rootProject.ext.lintTests
|
||||
}
|
||||
|
||||
+1
-8
@@ -14,7 +14,6 @@ import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.uast.UClass;
|
||||
import org.jetbrains.uast.UElement;
|
||||
import org.jetbrains.uast.UMethod;
|
||||
import org.jetbrains.uast.UTypeReferenceExpression;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
@@ -48,13 +47,7 @@ public final class ControllerChangeHandlerIssueDetector extends Detector impleme
|
||||
return;
|
||||
}
|
||||
|
||||
boolean hasSuperType = false;
|
||||
for (UTypeReferenceExpression superType : node.getUastSuperTypes()) {
|
||||
if (CLASS_NAME.equals(superType.asRenderString())) {
|
||||
hasSuperType = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
final boolean hasSuperType = evaluator.extendsClass(node.getPsi(), CLASS_NAME, true);
|
||||
if (!hasSuperType) {
|
||||
return;
|
||||
}
|
||||
|
||||
+2
-9
@@ -16,7 +16,6 @@ import org.jetbrains.uast.UClass;
|
||||
import org.jetbrains.uast.UElement;
|
||||
import org.jetbrains.uast.UMethod;
|
||||
import org.jetbrains.uast.UParameter;
|
||||
import org.jetbrains.uast.UTypeReferenceExpression;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
@@ -35,7 +34,7 @@ public final class ControllerIssueDetector extends Detector implements Detector.
|
||||
|
||||
@Override
|
||||
public List<Class<? extends UElement>> getApplicableUastTypes() {
|
||||
return Collections.<Class<? extends UElement>>singletonList(UClass.class);
|
||||
return Collections.singletonList(UClass.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -49,13 +48,7 @@ public final class ControllerIssueDetector extends Detector implements Detector.
|
||||
return;
|
||||
}
|
||||
|
||||
boolean hasSuperType = false;
|
||||
for (UTypeReferenceExpression superType : node.getUastSuperTypes()) {
|
||||
if (CLASS_NAME.equals(superType.asRenderString())) {
|
||||
hasSuperType = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
final boolean hasSuperType = evaluator.extendsClass(node.getPsi(), CLASS_NAME, true);
|
||||
if (!hasSuperType) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
package com.bluelinelabs.conductor.lint;
|
||||
|
||||
import com.android.tools.lint.detector.api.Issue;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import static com.android.tools.lint.detector.api.ApiKt.CURRENT_API;
|
||||
|
||||
@SuppressWarnings({"unused", "UnstableApiUsage"})
|
||||
public final class IssueRegistry extends com.android.tools.lint.client.api.IssueRegistry {
|
||||
|
||||
@Override
|
||||
public List<Issue> getIssues() {
|
||||
return Arrays.asList(
|
||||
ControllerIssueDetector.ISSUE,
|
||||
ControllerChangeHandlerIssueDetector.ISSUE
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getApi() {
|
||||
return CURRENT_API;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.bluelinelabs.conductor.lint
|
||||
|
||||
import com.android.tools.lint.client.api.Vendor
|
||||
import com.android.tools.lint.detector.api.CURRENT_API
|
||||
import com.android.tools.lint.client.api.IssueRegistry as LintIssueRegistry
|
||||
|
||||
@Suppress("UnstableApiUsage", "unused")
|
||||
class IssueRegistry : LintIssueRegistry() {
|
||||
|
||||
override val issues = listOf(
|
||||
ControllerIssueDetector.ISSUE,
|
||||
ControllerChangeHandlerIssueDetector.ISSUE
|
||||
)
|
||||
|
||||
override val api: Int = CURRENT_API
|
||||
|
||||
private val githubIssueLink = "https://github.com/bluelinelabs/Conductor/issues/new"
|
||||
|
||||
override val vendor = Vendor(
|
||||
vendorName = "Conductor",
|
||||
feedbackUrl = githubIssueLink,
|
||||
contact = githubIssueLink
|
||||
)
|
||||
}
|
||||
+41
-16
@@ -1,24 +1,27 @@
|
||||
package com.bluelinelabs.conductor.lint;
|
||||
|
||||
import org.intellij.lang.annotations.Language;
|
||||
import org.junit.Test;
|
||||
|
||||
import static com.android.tools.lint.checks.infrastructure.TestFiles.java;
|
||||
import static com.android.tools.lint.checks.infrastructure.TestLintTask.lint;
|
||||
|
||||
import com.android.tools.lint.checks.infrastructure.TestFile;
|
||||
|
||||
import org.intellij.lang.annotations.Language;
|
||||
import org.junit.Test;
|
||||
|
||||
@SuppressWarnings("UnstableApiUsage")
|
||||
public class ControllerChangeHandlerDetectorTest {
|
||||
|
||||
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"
|
||||
+ "public class SampleHandler extends com.bluelinelabs.conductor.ControllerChangeHandler {\n"
|
||||
+ "^\n"
|
||||
+ "1 errors, 0 warnings\n";
|
||||
|
||||
private final TestFile controllerChangeHandlerStub = java(
|
||||
"package com.bluelinelabs.conductor;\n"
|
||||
+ "abstract class ControllerChangeHandler {}"
|
||||
);
|
||||
|
||||
@Test
|
||||
public void testWithNoConstructor() {
|
||||
@Language("JAVA") String source = ""
|
||||
@@ -27,7 +30,7 @@ public class ControllerChangeHandlerDetectorTest {
|
||||
+ "}";
|
||||
|
||||
lint()
|
||||
.files(java(source))
|
||||
.files(controllerChangeHandlerStub, java(source))
|
||||
.issues(ControllerIssueDetector.ISSUE, ControllerChangeHandlerIssueDetector.ISSUE)
|
||||
.run()
|
||||
.expectClean();
|
||||
@@ -42,7 +45,7 @@ public class ControllerChangeHandlerDetectorTest {
|
||||
+ "}";
|
||||
|
||||
lint()
|
||||
.files(java(source))
|
||||
.files(controllerChangeHandlerStub, java(source))
|
||||
.issues(ControllerIssueDetector.ISSUE, ControllerChangeHandlerIssueDetector.ISSUE)
|
||||
.run()
|
||||
.expectClean();
|
||||
@@ -57,7 +60,7 @@ public class ControllerChangeHandlerDetectorTest {
|
||||
+ "}";
|
||||
|
||||
lint()
|
||||
.files(java(source))
|
||||
.files(controllerChangeHandlerStub, java(source))
|
||||
.issues(ControllerIssueDetector.ISSUE, ControllerChangeHandlerIssueDetector.ISSUE)
|
||||
.run()
|
||||
.expect(CONSTRUCTOR);
|
||||
@@ -73,7 +76,7 @@ public class ControllerChangeHandlerDetectorTest {
|
||||
+ "}";
|
||||
|
||||
lint()
|
||||
.files(java(source))
|
||||
.files(controllerChangeHandlerStub, java(source))
|
||||
.issues(ControllerIssueDetector.ISSUE, ControllerChangeHandlerIssueDetector.ISSUE)
|
||||
.run()
|
||||
.expectClean();
|
||||
@@ -88,7 +91,7 @@ public class ControllerChangeHandlerDetectorTest {
|
||||
+ "}";
|
||||
|
||||
lint()
|
||||
.files(java(source))
|
||||
.files(controllerChangeHandlerStub, java(source))
|
||||
.issues(ControllerIssueDetector.ISSUE, ControllerChangeHandlerIssueDetector.ISSUE)
|
||||
.run()
|
||||
.expect(CONSTRUCTOR);
|
||||
@@ -103,10 +106,32 @@ public class ControllerChangeHandlerDetectorTest {
|
||||
+ "}";
|
||||
|
||||
lint()
|
||||
.files(java(source))
|
||||
.files(controllerChangeHandlerStub, java(source))
|
||||
.issues(ControllerIssueDetector.ISSUE, ControllerChangeHandlerIssueDetector.ISSUE)
|
||||
.run()
|
||||
.expect(PRIVATE_CLASS_ERROR);
|
||||
.expect("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");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWithPrivateClassOfBaseClass() {
|
||||
@Language("JAVA") String baseClass = ""
|
||||
+ "package test;\n"
|
||||
+ "abstract class BaseChangeHandler extends com.bluelinelabs.conductor.ControllerChangeHandler {}";
|
||||
|
||||
@Language("JAVA") String source = ""
|
||||
+ "package test;\n"
|
||||
+ "private class SampleHandler extends test.BaseChangeHandler {}";
|
||||
|
||||
lint()
|
||||
.files(controllerChangeHandlerStub, java(baseClass), java(source))
|
||||
.issues(ControllerIssueDetector.ISSUE, ControllerChangeHandlerIssueDetector.ISSUE)
|
||||
.run()
|
||||
.expect("src/test/SampleHandler.java:2: Error: This ControllerChangeHandler class should be public (test.SampleHandler) [ValidControllerChangeHandler]\n" +
|
||||
"private class SampleHandler extends test.BaseChangeHandler {}\n" +
|
||||
"~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
|
||||
"1 errors, 0 warnings");
|
||||
}
|
||||
}
|
||||
|
||||
+47
-12
@@ -1,24 +1,33 @@
|
||||
package com.bluelinelabs.conductor.lint;
|
||||
|
||||
import org.intellij.lang.annotations.Language;
|
||||
import org.junit.Test;
|
||||
|
||||
import static com.android.tools.lint.checks.infrastructure.TestFiles.java;
|
||||
import static com.android.tools.lint.checks.infrastructure.TestLintTask.lint;
|
||||
|
||||
import com.android.tools.lint.checks.infrastructure.TestFile;
|
||||
|
||||
import org.intellij.lang.annotations.Language;
|
||||
import org.junit.Test;
|
||||
|
||||
@SuppressWarnings("UnstableApiUsage")
|
||||
public class ControllerDetectorTest {
|
||||
|
||||
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";
|
||||
+ "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";
|
||||
|
||||
private final TestFile controllerStub = java(
|
||||
"package com.bluelinelabs.conductor;\n"
|
||||
+ "abstract class Controller {}"
|
||||
);
|
||||
|
||||
|
||||
@Test
|
||||
public void testWithNoConstructor() {
|
||||
@Language("JAVA") String source = ""
|
||||
@@ -27,7 +36,7 @@ public class ControllerDetectorTest {
|
||||
+ "}";
|
||||
|
||||
lint()
|
||||
.files(java(source))
|
||||
.files(controllerStub, java(source))
|
||||
.issues(ControllerIssueDetector.ISSUE, ControllerChangeHandlerIssueDetector.ISSUE)
|
||||
.run()
|
||||
.expectClean();
|
||||
@@ -42,7 +51,7 @@ public class ControllerDetectorTest {
|
||||
+ "}";
|
||||
|
||||
lint()
|
||||
.files(java(source))
|
||||
.files(controllerStub, java(source))
|
||||
.issues(ControllerIssueDetector.ISSUE, ControllerChangeHandlerIssueDetector.ISSUE)
|
||||
.run()
|
||||
.expectClean();
|
||||
@@ -57,7 +66,7 @@ public class ControllerDetectorTest {
|
||||
+ "}";
|
||||
|
||||
lint()
|
||||
.files(java(source))
|
||||
.files(controllerStub, java(source))
|
||||
.issues(ControllerIssueDetector.ISSUE, ControllerChangeHandlerIssueDetector.ISSUE)
|
||||
.run()
|
||||
.expect(CONSTRUCTOR_ERROR);
|
||||
@@ -73,12 +82,38 @@ public class ControllerDetectorTest {
|
||||
+ "}";
|
||||
|
||||
lint()
|
||||
.files(java(source))
|
||||
.files(controllerStub, java(source))
|
||||
.issues(ControllerIssueDetector.ISSUE, ControllerChangeHandlerIssueDetector.ISSUE)
|
||||
.run()
|
||||
.expectClean();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWithBaseClassAndPrivateConstructor() {
|
||||
@Language("JAVA")
|
||||
String baseClass = ""
|
||||
+ "package test;\n"
|
||||
+ "public class BaseController extends com.bluelinelabs.conductor.Controller {}";
|
||||
|
||||
@Language("JAVA")
|
||||
String source = ""
|
||||
+ "package test;\n"
|
||||
+ "public class SampleController extends BaseController {\n"
|
||||
+ " private SampleController() { }\n"
|
||||
+ "}";
|
||||
|
||||
lint()
|
||||
.files(controllerStub, java(baseClass), java(source))
|
||||
.issues(ControllerIssueDetector.ISSUE, ControllerChangeHandlerIssueDetector.ISSUE)
|
||||
.run()
|
||||
.expect(
|
||||
"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 BaseController {\n" +
|
||||
"^\n" +
|
||||
"1 errors, 0 warnings"
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWithPrivateConstructor() {
|
||||
@Language("JAVA") String source = ""
|
||||
@@ -88,7 +123,7 @@ public class ControllerDetectorTest {
|
||||
+ "}";
|
||||
|
||||
lint()
|
||||
.files(java(source))
|
||||
.files(controllerStub, java(source))
|
||||
.issues(ControllerIssueDetector.ISSUE, ControllerChangeHandlerIssueDetector.ISSUE)
|
||||
.run()
|
||||
.expect(CONSTRUCTOR_ERROR);
|
||||
@@ -103,7 +138,7 @@ public class ControllerDetectorTest {
|
||||
+ "}";
|
||||
|
||||
lint()
|
||||
.files(java(source))
|
||||
.files(controllerStub, java(source))
|
||||
.issues(ControllerIssueDetector.ISSUE, ControllerChangeHandlerIssueDetector.ISSUE)
|
||||
.run()
|
||||
.expect(CLASS_ERROR);
|
||||
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
POM_NAME=Conductor AndroidX Transition Extensions
|
||||
POM_ARTIFACT_ID=conductor-transition-androidx
|
||||
POM_ARTIFACT_ID=conductor-androidx-transition
|
||||
POM_PACKAGING=aar
|
||||
@@ -1,3 +0,0 @@
|
||||
POM_NAME=Conductor RxLifecycle2 Extensions
|
||||
POM_ARTIFACT_ID=conductor-rxlifecycle2
|
||||
POM_PACKAGING=aar
|
||||
@@ -1,3 +0,0 @@
|
||||
<manifest package="com.bluelinelabs.conductor.rxlifecycle2">
|
||||
<application />
|
||||
</manifest>
|
||||
-14
@@ -1,14 +0,0 @@
|
||||
package com.bluelinelabs.conductor.rxlifecycle2;
|
||||
|
||||
public enum ControllerEvent {
|
||||
|
||||
CREATE,
|
||||
CONTEXT_AVAILABLE,
|
||||
CREATE_VIEW,
|
||||
ATTACH,
|
||||
DETACH,
|
||||
DESTROY_VIEW,
|
||||
CONTEXT_UNAVAILABLE,
|
||||
DESTROY
|
||||
|
||||
}
|
||||
-71
@@ -1,71 +0,0 @@
|
||||
package com.bluelinelabs.conductor.rxlifecycle2;
|
||||
|
||||
import android.content.Context;
|
||||
import androidx.annotation.NonNull;
|
||||
import android.view.View;
|
||||
|
||||
import com.bluelinelabs.conductor.Controller;
|
||||
import com.trello.rxlifecycle2.OutsideLifecycleException;
|
||||
|
||||
import io.reactivex.subjects.BehaviorSubject;
|
||||
|
||||
public class ControllerLifecycleSubjectHelper {
|
||||
private ControllerLifecycleSubjectHelper() {
|
||||
}
|
||||
|
||||
public static BehaviorSubject<ControllerEvent> create(Controller controller) {
|
||||
ControllerEvent initialState;
|
||||
if (controller.isBeingDestroyed() || controller.isDestroyed()) {
|
||||
throw new OutsideLifecycleException("Cannot bind to Controller lifecycle when outside of it.");
|
||||
} else if (controller.isAttached()) {
|
||||
initialState = ControllerEvent.ATTACH;
|
||||
} else if (controller.getView() != null) {
|
||||
initialState = ControllerEvent.CREATE_VIEW;
|
||||
} else if (controller.getActivity() != null) {
|
||||
initialState = ControllerEvent.CONTEXT_AVAILABLE;
|
||||
} else {
|
||||
initialState = ControllerEvent.CREATE;
|
||||
}
|
||||
|
||||
final BehaviorSubject<ControllerEvent> subject = BehaviorSubject.createDefault(initialState);
|
||||
|
||||
controller.addLifecycleListener(new Controller.LifecycleListener() {
|
||||
@Override
|
||||
public void preContextAvailable(@NonNull Controller controller) {
|
||||
subject.onNext(ControllerEvent.CONTEXT_AVAILABLE);
|
||||
}
|
||||
|
||||
@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 preContextUnavailable(@NonNull Controller controller, @NonNull Context context) {
|
||||
subject.onNext(ControllerEvent.CONTEXT_UNAVAILABLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preDestroy(@NonNull Controller controller) {
|
||||
subject.onNext(ControllerEvent.DESTROY);
|
||||
}
|
||||
});
|
||||
|
||||
return subject;
|
||||
}
|
||||
}
|
||||
-49
@@ -1,49 +0,0 @@
|
||||
package com.bluelinelabs.conductor.rxlifecycle2;
|
||||
|
||||
import android.os.Bundle;
|
||||
import androidx.annotation.CheckResult;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.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);
|
||||
}
|
||||
}
|
||||
-43
@@ -1,43 +0,0 @@
|
||||
package com.bluelinelabs.conductor.rxlifecycle2;
|
||||
|
||||
import androidx.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 CONTEXT_AVAILABLE:
|
||||
return ControllerEvent.CONTEXT_UNAVAILABLE;
|
||||
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.");
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
apply plugin: 'com.android.library'
|
||||
|
||||
android {
|
||||
compileSdkVersion rootProject.ext.compileSdkVersion
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode Integer.parseInt(project.VERSION_CODE)
|
||||
versionName project.VERSION_NAME
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation project(':conductor')
|
||||
}
|
||||
|
||||
ext.artifactId = 'conductor-android-transition'
|
||||
|
||||
apply from: rootProject.file('dependencies.gradle')
|
||||
apply plugin: "com.vanniktech.maven.publish"
|
||||
@@ -1,3 +0,0 @@
|
||||
POM_NAME=Conductor Platform Transition Extensions
|
||||
POM_ARTIFACT_ID=conductor-transition-platform
|
||||
POM_PACKAGING=aar
|
||||
@@ -1,3 +0,0 @@
|
||||
<manifest package="com.bluelinelabs.conductor.platformtransition">
|
||||
<application />
|
||||
</manifest>
|
||||
-647
@@ -1,647 +0,0 @@
|
||||
package com.bluelinelabs.conductor.changehandler.platformtransition;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.SharedElementCallback;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Build;
|
||||
import android.transition.Transition;
|
||||
import android.transition.Transition.TransitionListener;
|
||||
import android.transition.TransitionSet;
|
||||
import android.util.ArrayMap;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewTreeObserver;
|
||||
import android.view.ViewTreeObserver.OnPreDrawListener;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.bluelinelabs.conductor.Controller;
|
||||
import com.bluelinelabs.conductor.ControllerChangeHandler;
|
||||
import com.bluelinelabs.conductor.internal.TransitionUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A TransitionChangeHandler that facilitates using different Transitions for the entering view, the exiting view,
|
||||
* and shared elements between the two.
|
||||
* <p/>
|
||||
* Note that this class uses Android's <b>platform</b> {@link Transition}. If you're using androidx transitions, consider
|
||||
* using the {@code SharedElementTransitionChangeHandler} provided by the {@code androidx-transitions} Conductor module.
|
||||
*/
|
||||
// Much of this class is based on FragmentTransition.java and FragmentTransitionCompat21.java from the Android support library
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
public abstract class SharedElementTransitionChangeHandler extends TransitionChangeHandler {
|
||||
|
||||
// A map of from -> to names. Generally these will be the same.
|
||||
@NonNull final ArrayMap<String, String> sharedElementNames = new ArrayMap<>();
|
||||
|
||||
@NonNull final List<String> waitForTransitionNames = new ArrayList<>();
|
||||
@NonNull final List<ViewParentPair> removedViews = new ArrayList<>();
|
||||
|
||||
@Nullable Transition exitTransition;
|
||||
@Nullable Transition enterTransition;
|
||||
@Nullable Transition sharedElementTransition;
|
||||
@Nullable private SharedElementCallback exitTransitionCallback;
|
||||
@Nullable private SharedElementCallback enterTransitionCallback;
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected final Transition getTransition(@NonNull ViewGroup container, @Nullable View from, @Nullable View to, boolean isPush) {
|
||||
exitTransition = getExitTransition(container, from, to, isPush);
|
||||
enterTransition = getEnterTransition(container, from, to, isPush);
|
||||
sharedElementTransition = getSharedElementTransition(container, from, to, isPush);
|
||||
exitTransitionCallback = getExitTransitionCallback(container, from, to, isPush);
|
||||
enterTransitionCallback = getEnterTransitionCallback(container, from, to, isPush);
|
||||
|
||||
if (enterTransition == null && sharedElementTransition == null && exitTransition == null) {
|
||||
throw new IllegalStateException("SharedElementTransitionChangeHandler must have at least one transaction.");
|
||||
}
|
||||
|
||||
return mergeTransitions(isPush);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void prepareForTransition(@NonNull final ViewGroup container, @Nullable final View from, @Nullable final View to, @NonNull final Transition transition, final boolean isPush, @NonNull final OnTransitionPreparedListener onTransitionPreparedListener) {
|
||||
OnTransitionPreparedListener listener = () -> {
|
||||
configureTransition(container, from, to, transition, isPush);
|
||||
onTransitionPreparedListener.onPrepared();
|
||||
};
|
||||
|
||||
configureSharedElements(container, from, to, isPush);
|
||||
|
||||
if (to != null && to.getParent() == null && waitForTransitionNames.size() > 0) {
|
||||
waitOnAllTransitionNames(to, listener);
|
||||
container.addView(to);
|
||||
} else {
|
||||
listener.onPrepared();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void executePropertyChanges(@NonNull ViewGroup container, @Nullable View from, @Nullable View to, @Nullable Transition transition, boolean isPush) {
|
||||
if (to != null && removedViews.size() > 0) {
|
||||
to.setVisibility(View.VISIBLE);
|
||||
|
||||
for (ViewParentPair removedView : removedViews) {
|
||||
removedView.parent.addView(removedView.view);
|
||||
}
|
||||
|
||||
removedViews.clear();
|
||||
}
|
||||
|
||||
super.executePropertyChanges(container, from, to, transition, isPush);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAbortPush(@NonNull ControllerChangeHandler newHandler, @Nullable Controller newTop) {
|
||||
super.onAbortPush(newHandler, newTop);
|
||||
|
||||
removedViews.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onEnd() {
|
||||
exitTransition = null;
|
||||
enterTransition = null;
|
||||
sharedElementTransition = null;
|
||||
}
|
||||
|
||||
void configureTransition(@NonNull final ViewGroup container, @Nullable View from, @Nullable View to, @NonNull final Transition transition, boolean isPush) {
|
||||
final View nonExistentView = new View(container.getContext());
|
||||
|
||||
List<View> fromSharedElements = new ArrayList<>();
|
||||
List<View> toSharedElements = new ArrayList<>();
|
||||
|
||||
configureSharedElements(container, nonExistentView, to, from, isPush, fromSharedElements, toSharedElements);
|
||||
|
||||
List<View> exitingViews = exitTransition != null ? configureEnteringExitingViews(exitTransition, from, fromSharedElements, nonExistentView) : null;
|
||||
if (exitingViews == null || exitingViews.isEmpty()) {
|
||||
exitTransition = null;
|
||||
}
|
||||
|
||||
if (enterTransition != null) {
|
||||
enterTransition.addTarget(nonExistentView);
|
||||
}
|
||||
|
||||
final List<View> enteringViews = new ArrayList<>();
|
||||
scheduleRemoveTargets(transition, enterTransition, enteringViews, exitTransition, exitingViews, sharedElementTransition, toSharedElements);
|
||||
scheduleTargetChange(container, to, nonExistentView, toSharedElements, enteringViews, exitingViews);
|
||||
|
||||
setNameOverrides(container, toSharedElements);
|
||||
scheduleNameReset(container, toSharedElements);
|
||||
}
|
||||
|
||||
private void waitOnAllTransitionNames(@NonNull final View to, @NonNull final OnTransitionPreparedListener onTransitionPreparedListener) {
|
||||
OnPreDrawListener onPreDrawListener = new OnPreDrawListener() {
|
||||
boolean addedSubviewListeners;
|
||||
|
||||
@Override
|
||||
public boolean onPreDraw() {
|
||||
List<View> foundViews = new ArrayList<>();
|
||||
boolean allViewsFound = true;
|
||||
for (String transitionName : waitForTransitionNames) {
|
||||
View namedView = TransitionUtils.findNamedView(to, transitionName);
|
||||
if (namedView != null) {
|
||||
foundViews.add(TransitionUtils.findNamedView(to, transitionName));
|
||||
} else {
|
||||
allViewsFound = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (allViewsFound && !addedSubviewListeners) {
|
||||
addedSubviewListeners = true;
|
||||
waitOnChildTransitionNames(to, foundViews, this, onTransitionPreparedListener);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
to.getViewTreeObserver().addOnPreDrawListener(onPreDrawListener);
|
||||
}
|
||||
|
||||
void waitOnChildTransitionNames(@NonNull final View to, @NonNull List<View> foundViews, @NonNull final OnPreDrawListener parentPreDrawListener, @NonNull final OnTransitionPreparedListener onTransitionPreparedListener) {
|
||||
for (final View view : foundViews) {
|
||||
OneShotPreDrawListener.add(true, view, () -> {
|
||||
waitForTransitionNames.remove(view.getTransitionName());
|
||||
|
||||
removedViews.add(new ViewParentPair(view, (ViewGroup) view.getParent()));
|
||||
((ViewGroup) view.getParent()).removeView(view);
|
||||
|
||||
if (waitForTransitionNames.size() == 0) {
|
||||
to.getViewTreeObserver().removeOnPreDrawListener(parentPreDrawListener);
|
||||
to.setVisibility(View.INVISIBLE);
|
||||
onTransitionPreparedListener.onPrepared();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void scheduleTargetChange(@NonNull final ViewGroup container, @Nullable final View to, @NonNull final View nonExistentView,
|
||||
@NonNull final List<View> toSharedElements, @NonNull final List<View> enteringViews, @Nullable final List<View> exitingViews) {
|
||||
OneShotPreDrawListener.add(true, container, () -> {
|
||||
if (enterTransition != null) {
|
||||
enterTransition.removeTarget(nonExistentView);
|
||||
List<View> views = configureEnteringExitingViews(enterTransition, to, toSharedElements, nonExistentView);
|
||||
enteringViews.addAll(views);
|
||||
}
|
||||
|
||||
if (exitingViews != null) {
|
||||
if (exitTransition != null) {
|
||||
List<View> tempExiting = new ArrayList<>();
|
||||
tempExiting.add(nonExistentView);
|
||||
TransitionUtils.replaceTargets(exitTransition, exitingViews, tempExiting);
|
||||
}
|
||||
exitingViews.clear();
|
||||
exitingViews.add(nonExistentView);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private Transition mergeTransitions(boolean isPush) {
|
||||
boolean overlap = enterTransition == null || exitTransition == null || allowTransitionOverlap(isPush);
|
||||
|
||||
if (overlap) {
|
||||
return TransitionUtils.mergeTransitions(TransitionSet.ORDERING_TOGETHER, exitTransition, enterTransition, sharedElementTransition);
|
||||
} else {
|
||||
Transition staggered = TransitionUtils.mergeTransitions(TransitionSet.ORDERING_SEQUENTIAL, exitTransition, enterTransition);
|
||||
return TransitionUtils.mergeTransitions(TransitionSet.ORDERING_TOGETHER, staggered, sharedElementTransition);
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull List<View> configureEnteringExitingViews(@NonNull Transition transition, @Nullable View view, @NonNull List<View> sharedElements, @NonNull View nonExistentView) {
|
||||
List<View> viewList = new ArrayList<>();
|
||||
if (view != null) {
|
||||
captureTransitioningViews(viewList, view);
|
||||
}
|
||||
viewList.removeAll(sharedElements);
|
||||
if (!viewList.isEmpty()) {
|
||||
viewList.add(nonExistentView);
|
||||
TransitionUtils.addTargets(transition, viewList);
|
||||
}
|
||||
return viewList;
|
||||
}
|
||||
|
||||
private void configureSharedElements(@NonNull ViewGroup container, @NonNull final View nonExistentView, @Nullable final View to, @Nullable View from,
|
||||
final boolean isPush, @NonNull final List<View> fromSharedElements, @NonNull final List<View> toSharedElements) {
|
||||
|
||||
if (to == null || from == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
ArrayMap<String, View> capturedFromSharedElements = captureFromSharedElements(from);
|
||||
|
||||
if (sharedElementNames.isEmpty()) {
|
||||
sharedElementTransition = null;
|
||||
} else if (capturedFromSharedElements != null) {
|
||||
fromSharedElements.addAll(capturedFromSharedElements.values());
|
||||
}
|
||||
|
||||
if (enterTransition == null && exitTransition == null && sharedElementTransition == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
callSharedElementStartEnd(capturedFromSharedElements, true);
|
||||
|
||||
final Rect toEpicenter;
|
||||
if (sharedElementTransition != null) {
|
||||
toEpicenter = new Rect();
|
||||
TransitionUtils.setTargets(sharedElementTransition, nonExistentView, fromSharedElements);
|
||||
setFromEpicenter(capturedFromSharedElements);
|
||||
if (enterTransition != null) {
|
||||
enterTransition.setEpicenterCallback(new Transition.EpicenterCallback() {
|
||||
@Override
|
||||
public Rect onGetEpicenter(Transition transition) {
|
||||
if (toEpicenter.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return toEpicenter;
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
toEpicenter = null;
|
||||
}
|
||||
|
||||
OneShotPreDrawListener.add(true, container, () -> {
|
||||
ArrayMap<String, View> capturedToSharedElements = captureToSharedElements(to, isPush);
|
||||
|
||||
if (capturedToSharedElements != null) {
|
||||
toSharedElements.addAll(capturedToSharedElements.values());
|
||||
toSharedElements.add(nonExistentView);
|
||||
}
|
||||
|
||||
callSharedElementStartEnd(capturedToSharedElements, false);
|
||||
if (sharedElementTransition != null) {
|
||||
sharedElementTransition.getTargets().clear();
|
||||
sharedElementTransition.getTargets().addAll(toSharedElements);
|
||||
TransitionUtils.replaceTargets(sharedElementTransition, fromSharedElements, toSharedElements);
|
||||
|
||||
final View toEpicenterView = getToEpicenterView(capturedToSharedElements);
|
||||
if (toEpicenterView != null && toEpicenter != null) {
|
||||
TransitionUtils.getBoundsOnScreen(toEpicenterView, toEpicenter);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Nullable View getToEpicenterView(@Nullable ArrayMap<String, View> toSharedElements) {
|
||||
if (enterTransition != null && sharedElementNames.size() > 0 && toSharedElements != null) {
|
||||
return toSharedElements.get(sharedElementNames.valueAt(0));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void setFromEpicenter(@Nullable ArrayMap<String, View> fromSharedElements) {
|
||||
if (sharedElementNames.size() > 0 && fromSharedElements != null) {
|
||||
final View fromEpicenterView = fromSharedElements.get(sharedElementNames.keyAt(0));
|
||||
|
||||
if (sharedElementTransition != null) {
|
||||
TransitionUtils.setEpicenter(sharedElementTransition, fromEpicenterView);
|
||||
}
|
||||
|
||||
if (exitTransition != null) {
|
||||
TransitionUtils.setEpicenter(exitTransition, fromEpicenterView);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable ArrayMap<String, View> captureToSharedElements(@Nullable final View to, boolean isPush) {
|
||||
if (sharedElementNames.isEmpty() || sharedElementTransition == null || to == null) {
|
||||
sharedElementNames.clear();
|
||||
return null;
|
||||
}
|
||||
|
||||
final ArrayMap<String, View> toSharedElements = new ArrayMap<>();
|
||||
TransitionUtils.findNamedViews(toSharedElements, to);
|
||||
for (ViewParentPair removedView : removedViews) {
|
||||
toSharedElements.put(removedView.view.getTransitionName(), removedView.view);
|
||||
}
|
||||
|
||||
final List<String> names = new ArrayList<>(sharedElementNames.values());
|
||||
|
||||
toSharedElements.retainAll(names);
|
||||
if (enterTransitionCallback != null) {
|
||||
enterTransitionCallback.onMapSharedElements(names, toSharedElements);
|
||||
for (int i = names.size() - 1; i >= 0; i--) {
|
||||
String name = names.get(i);
|
||||
View view = toSharedElements.get(name);
|
||||
if (view == null) {
|
||||
String key = findKeyForValue(sharedElementNames, name);
|
||||
if (key != null) {
|
||||
sharedElementNames.remove(key);
|
||||
}
|
||||
} else if (!name.equals(view.getTransitionName())) {
|
||||
String key = findKeyForValue(sharedElementNames, name);
|
||||
if (key != null) {
|
||||
sharedElementNames.put(key, view.getTransitionName());
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (int i = sharedElementNames.size() - 1; i >= 0; i--) {
|
||||
final String targetName = sharedElementNames.valueAt(i);
|
||||
if (!toSharedElements.containsKey(targetName)) {
|
||||
sharedElementNames.removeAt(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
return toSharedElements;
|
||||
}
|
||||
|
||||
@Nullable String findKeyForValue(@NonNull ArrayMap<String, String> map, @NonNull String value) {
|
||||
final int numElements = map.size();
|
||||
for (int i = 0; i < numElements; i++) {
|
||||
if (value.equals(map.valueAt(i))) {
|
||||
return map.keyAt(i);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private ArrayMap<String, View> captureFromSharedElements(@NonNull View from) {
|
||||
if (sharedElementNames.isEmpty() || sharedElementTransition == null) {
|
||||
sharedElementNames.clear();
|
||||
return null;
|
||||
}
|
||||
|
||||
final ArrayMap<String, View> fromSharedElements = new ArrayMap<>();
|
||||
TransitionUtils.findNamedViews(fromSharedElements, from);
|
||||
|
||||
final List<String> names = new ArrayList<>(sharedElementNames.keySet());
|
||||
|
||||
fromSharedElements.retainAll(names);
|
||||
if (exitTransitionCallback != null) {
|
||||
exitTransitionCallback.onMapSharedElements(names, fromSharedElements);
|
||||
for (int i = names.size() - 1; i >= 0; i--) {
|
||||
String name = names.get(i);
|
||||
View view = fromSharedElements.get(name);
|
||||
if (view == null) {
|
||||
sharedElementNames.remove(name);
|
||||
} else if (!name.equals(view.getTransitionName())) {
|
||||
String targetValue = sharedElementNames.remove(name);
|
||||
sharedElementNames.put(view.getTransitionName(), targetValue);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
sharedElementNames.retainAll(fromSharedElements.keySet());
|
||||
}
|
||||
return fromSharedElements;
|
||||
}
|
||||
|
||||
void callSharedElementStartEnd(@Nullable ArrayMap<String, View> sharedElements, boolean isStart) {
|
||||
if (enterTransitionCallback != null) {
|
||||
final int count = sharedElements == null ? 0 : sharedElements.size();
|
||||
List<View> views = new ArrayList<>(count);
|
||||
List<String> names = new ArrayList<>(count);
|
||||
for (int i = 0; i < count; i++) {
|
||||
names.add(sharedElements.keyAt(i));
|
||||
views.add(sharedElements.valueAt(i));
|
||||
}
|
||||
if (isStart) {
|
||||
enterTransitionCallback.onSharedElementStart(names, views, null);
|
||||
} else {
|
||||
enterTransitionCallback.onSharedElementEnd(names, views, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void captureTransitioningViews(@NonNull List<View> transitioningViews, @NonNull View view) {
|
||||
if (view.getVisibility() == View.VISIBLE) {
|
||||
if (view instanceof ViewGroup) {
|
||||
ViewGroup viewGroup = (ViewGroup) view;
|
||||
if (viewGroup.isTransitionGroup()) {
|
||||
transitioningViews.add(viewGroup);
|
||||
} else {
|
||||
int count = viewGroup.getChildCount();
|
||||
for (int i = 0; i < count; i++) {
|
||||
View child = viewGroup.getChildAt(i);
|
||||
captureTransitioningViews(transitioningViews, child);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
transitioningViews.add(view);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void scheduleRemoveTargets(@NonNull final Transition overallTransition,
|
||||
@Nullable final Transition enterTransition, @Nullable final List<View> enteringViews,
|
||||
@Nullable final Transition exitTransition, @Nullable final List<View> exitingViews,
|
||||
@Nullable final Transition sharedElementTransition, @Nullable final List<View> toSharedElements) {
|
||||
overallTransition.addListener(new TransitionListener() {
|
||||
@Override
|
||||
public void onTransitionStart(Transition transition) {
|
||||
if (enterTransition != null && enteringViews != null) {
|
||||
TransitionUtils.replaceTargets(enterTransition, enteringViews, null);
|
||||
}
|
||||
if (exitTransition != null && exitingViews != null) {
|
||||
TransitionUtils.replaceTargets(exitTransition, exitingViews, null);
|
||||
}
|
||||
if (sharedElementTransition != null && toSharedElements != null) {
|
||||
TransitionUtils.replaceTargets(sharedElementTransition, toSharedElements, null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTransitionEnd(Transition transition) { }
|
||||
|
||||
@Override
|
||||
public void onTransitionCancel(Transition transition) { }
|
||||
|
||||
@Override
|
||||
public void onTransitionPause(Transition transition) { }
|
||||
|
||||
@Override
|
||||
public void onTransitionResume(Transition transition) { }
|
||||
});
|
||||
}
|
||||
|
||||
private void setNameOverrides(@NonNull final View container, @NonNull final List<View> toSharedElements) {
|
||||
OneShotPreDrawListener.add(true, container, () -> {
|
||||
final int numSharedElements = toSharedElements.size();
|
||||
for (int i = 0; i < numSharedElements; i++) {
|
||||
View view = toSharedElements.get(i);
|
||||
String name = view.getTransitionName();
|
||||
if (name != null) {
|
||||
String inName = findKeyForValue(sharedElementNames, name);
|
||||
view.setTransitionName(inName);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void scheduleNameReset(@NonNull final ViewGroup container, @NonNull final List<View> toSharedElements) {
|
||||
OneShotPreDrawListener.add(true, container, () -> {
|
||||
final int numSharedElements = toSharedElements.size();
|
||||
for (int i = 0; i < numSharedElements; i++) {
|
||||
final View view = toSharedElements.get(i);
|
||||
final String name = view.getTransitionName();
|
||||
final String inName = sharedElementNames.get(name);
|
||||
view.setTransitionName(inName);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Will be called when views are ready to have their shared elements configured. Within this method one of the addSharedElement methods
|
||||
* should be called for each shared element that will be used. If one or more of these shared elements will not instantly be available in
|
||||
* the incoming view (for ex, in a RecyclerView), waitOnSharedElementNamed can be called to delay the transition until everything is available.
|
||||
*/
|
||||
public abstract void configureSharedElements(@NonNull ViewGroup container, @Nullable View from, @Nullable View to, boolean isPush);
|
||||
|
||||
/**
|
||||
* Should return the transition that will be used on the exiting ("from") view, if one is desired.
|
||||
*/
|
||||
@Nullable
|
||||
public abstract Transition getExitTransition(@NonNull ViewGroup container, @Nullable View from, @Nullable View to, boolean isPush);
|
||||
|
||||
/**
|
||||
* Should return the transition that will be used on shared elements between the from and to views.
|
||||
*/
|
||||
@Nullable
|
||||
public abstract Transition getSharedElementTransition(@NonNull ViewGroup container, @Nullable View from, @Nullable View to, boolean isPush);
|
||||
|
||||
/**
|
||||
* Should return the transition that will be used on the entering ("to") view, if one is desired.
|
||||
*/
|
||||
@Nullable
|
||||
public abstract Transition getEnterTransition(@NonNull ViewGroup container, @Nullable View from, @Nullable View to, boolean isPush);
|
||||
|
||||
/**
|
||||
* Should return a callback that can be used to customize transition behavior of the shared element transition for the "from" view.
|
||||
*/
|
||||
@Nullable
|
||||
public SharedElementCallback getExitTransitionCallback(@NonNull ViewGroup container, @Nullable View from, @Nullable View to, boolean isPush) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should return a callback that can be used to customize transition behavior of the shared element transition for the "to" view.
|
||||
*/
|
||||
@Nullable
|
||||
public SharedElementCallback getEnterTransitionCallback(@NonNull ViewGroup container, @Nullable View from, @Nullable View to, boolean isPush) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should return whether or not the the exit transition and enter transition should overlap. If true,
|
||||
* the enter transition will start as soon as possible. Otherwise, the enter transition will wait until the
|
||||
* completion of the exit transition. Defaults to true.
|
||||
*/
|
||||
public boolean allowTransitionOverlap(boolean isPush) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to register an element that will take part in the shared element transition.
|
||||
*
|
||||
* @param name The transition name that is used for both the entering and exiting views.
|
||||
*/
|
||||
protected final void addSharedElement(@NonNull String name) {
|
||||
sharedElementNames.put(name, name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to register an element that will take part in the shared element transition. Maps the name used in the
|
||||
* "from" view to the name used in the "to" view if they are not the same.
|
||||
*
|
||||
* @param fromName The transition name used in the "from" view
|
||||
* @param toName The transition name used in the "to" view
|
||||
*/
|
||||
protected final void addSharedElement(@NonNull String fromName, @NonNull String toName) {
|
||||
sharedElementNames.put(fromName, toName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to register an element that will take part in the shared element transition. Maps the name used in the
|
||||
* "from" view to the name used in the "to" view if they are not the same.
|
||||
*
|
||||
* @param sharedElement The view from the "from" view that will take part in the shared element transition
|
||||
* @param toName The transition name used in the "to" view
|
||||
*/
|
||||
protected final void addSharedElement(@NonNull View sharedElement, @NonNull String toName) {
|
||||
String transitionName = sharedElement.getTransitionName();
|
||||
if (transitionName == null) {
|
||||
throw new IllegalArgumentException("Unique transitionNames are required for all sharedElements");
|
||||
}
|
||||
sharedElementNames.put(transitionName, toName);
|
||||
}
|
||||
|
||||
/**
|
||||
* The transition will be delayed until the view with the name passed in is available in the "to" hierarchy. This is
|
||||
* particularly useful for views that don't load instantly, like RecyclerViews. Note that using this method can
|
||||
* potentially lock up your app indefinitely if the view never loads!
|
||||
*/
|
||||
protected final void waitOnSharedElementNamed(@NonNull String name) {
|
||||
if (!sharedElementNames.values().contains(name)) {
|
||||
throw new IllegalStateException("Can't wait on a shared element that hasn't been registered using addSharedElement");
|
||||
}
|
||||
waitForTransitionNames.add(name);
|
||||
}
|
||||
|
||||
private static class OneShotPreDrawListener implements OnPreDrawListener, View.OnAttachStateChangeListener {
|
||||
|
||||
private final View view;
|
||||
private ViewTreeObserver viewTreeObserver;
|
||||
private final Runnable runnable;
|
||||
private final boolean preDrawReturnValue;
|
||||
|
||||
private OneShotPreDrawListener(boolean preDrawReturnValue, @NonNull View view, @NonNull Runnable runnable) {
|
||||
this.preDrawReturnValue = preDrawReturnValue;
|
||||
this.view = view;
|
||||
viewTreeObserver = view.getViewTreeObserver();
|
||||
this.runnable = runnable;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static OneShotPreDrawListener add(boolean preDrawReturnValue, @NonNull View view, @NonNull Runnable runnable) {
|
||||
OneShotPreDrawListener listener = new OneShotPreDrawListener(preDrawReturnValue, view, runnable);
|
||||
view.getViewTreeObserver().addOnPreDrawListener(listener);
|
||||
view.addOnAttachStateChangeListener(listener);
|
||||
return listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPreDraw() {
|
||||
removeListener();
|
||||
runnable.run();
|
||||
return preDrawReturnValue;
|
||||
}
|
||||
|
||||
private void removeListener() {
|
||||
if (viewTreeObserver.isAlive()) {
|
||||
viewTreeObserver.removeOnPreDrawListener(this);
|
||||
} else {
|
||||
view.getViewTreeObserver().removeOnPreDrawListener(this);
|
||||
}
|
||||
view.removeOnAttachStateChangeListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewAttachedToWindow(View v) {
|
||||
viewTreeObserver = v.getViewTreeObserver();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewDetachedFromWindow(View v) {
|
||||
removeListener();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class ViewParentPair {
|
||||
@NonNull final View view;
|
||||
@NonNull final ViewGroup parent;
|
||||
|
||||
ViewParentPair(@NonNull View view, ViewGroup parent) {
|
||||
this.view = view;
|
||||
this.parent = parent;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
-149
@@ -1,149 +0,0 @@
|
||||
package com.bluelinelabs.conductor.changehandler.platformtransition;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.os.Build;
|
||||
import android.transition.Transition;
|
||||
import android.transition.Transition.TransitionListener;
|
||||
import android.transition.TransitionManager;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.bluelinelabs.conductor.Controller;
|
||||
import com.bluelinelabs.conductor.ControllerChangeHandler;
|
||||
|
||||
/**
|
||||
* A base {@link ControllerChangeHandler} that facilitates using {@link Transition}s to replace Controller Views.
|
||||
* <p/>
|
||||
* Note that this class uses Android's <b>platform</b> {@link Transition}. If you're using androidx transitions, consider
|
||||
* using the {@code TransitionChangeHandler} provided by the {@code androidx-transitions} Conductor module.
|
||||
*/
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
public abstract class TransitionChangeHandler extends ControllerChangeHandler {
|
||||
|
||||
public interface OnTransitionPreparedListener {
|
||||
void onPrepared();
|
||||
}
|
||||
|
||||
boolean canceled;
|
||||
private boolean needsImmediateCompletion;
|
||||
|
||||
/**
|
||||
* Should be overridden to return the Transition to use while replacing Views.
|
||||
*
|
||||
* @param container The container these Views are hosted in
|
||||
* @param from The previous View in the container or {@code null} if there was no Controller before this transition
|
||||
* @param to The next View that should be put in the container or {@code null} if no Controller is being transitioned to
|
||||
* @param isPush True if this is a push transaction, false if it's a pop
|
||||
*/
|
||||
@NonNull
|
||||
protected abstract Transition getTransition(@NonNull ViewGroup container, @Nullable View from, @Nullable View to, boolean isPush);
|
||||
|
||||
@Override
|
||||
public void onAbortPush(@NonNull ControllerChangeHandler newHandler, @Nullable Controller newTop) {
|
||||
super.onAbortPush(newHandler, newTop);
|
||||
|
||||
canceled = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void completeImmediately() {
|
||||
super.completeImmediately();
|
||||
|
||||
needsImmediateCompletion = true;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private ControllerChangeCompletedListener listener;
|
||||
|
||||
@Override
|
||||
public void performChange(@NonNull final ViewGroup container, @Nullable final View from, @Nullable final View to, final boolean isPush, @NonNull final ControllerChangeCompletedListener changeListener) {
|
||||
listener = changeListener;
|
||||
if (canceled) {
|
||||
changeListener.onChangeCompleted();
|
||||
return;
|
||||
}
|
||||
if (needsImmediateCompletion) {
|
||||
executePropertyChanges(container, from, to, null, isPush);
|
||||
changeListener.onChangeCompleted();
|
||||
return;
|
||||
}
|
||||
|
||||
final Runnable onTransitionNotStarted = changeListener::onChangeCompleted;
|
||||
|
||||
final Transition transition = getTransition(container, from, to, isPush);
|
||||
transition.addListener(new TransitionListener() {
|
||||
@Override
|
||||
public void onTransitionStart(Transition transition) {
|
||||
container.removeCallbacks(onTransitionNotStarted);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTransitionEnd(Transition transition) {
|
||||
listener.onChangeCompleted();
|
||||
listener = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTransitionCancel(Transition transition) {
|
||||
listener.onChangeCompleted();
|
||||
listener = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTransitionPause(Transition transition) { }
|
||||
|
||||
@Override
|
||||
public void onTransitionResume(Transition transition) { }
|
||||
});
|
||||
|
||||
prepareForTransition(container, from, to, transition, isPush, () -> {
|
||||
if (!canceled) {
|
||||
TransitionManager.beginDelayedTransition(container, transition);
|
||||
executePropertyChanges(container, from, to, transition, isPush);
|
||||
container.post(onTransitionNotStarted);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removesFromViewOnPush() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called before a transition occurs. This can be used to reorder views, set their transition names, etc. The transition will begin
|
||||
* when {@code onTransitionPreparedListener} is called.
|
||||
*
|
||||
* @param container The container these Views are hosted in
|
||||
* @param from The previous View in the container or {@code null} if there was no Controller before this transition
|
||||
* @param to The next View that should be put in the container or {@code null} if no Controller is being transitioned to
|
||||
* @param transition The transition that is being prepared for
|
||||
* @param isPush True if this is a push transaction, false if it's a pop
|
||||
*/
|
||||
public void prepareForTransition(@NonNull ViewGroup container, @Nullable View from, @Nullable View to, @NonNull Transition transition, boolean isPush, @NonNull OnTransitionPreparedListener onTransitionPreparedListener) {
|
||||
onTransitionPreparedListener.onPrepared();
|
||||
}
|
||||
|
||||
/**
|
||||
* This should set all view properties needed for the transition to work properly. By default it removes the "from" view
|
||||
* and adds the "to" view.
|
||||
*
|
||||
* @param container The container these Views are hosted in
|
||||
* @param from The previous View in the container or {@code null} if there was no Controller before this transition
|
||||
* @param to The next View that should be put in the container or {@code null} if no Controller is being transitioned to
|
||||
* @param transition The transition with which {@code TransitionManager.beginDelayedTransition} has been called. This will be null only if another ControllerChangeHandler immediately overrides this one.
|
||||
* @param isPush True if this is a push transaction, false if it's a pop
|
||||
*/
|
||||
public void executePropertyChanges(@NonNull ViewGroup container, @Nullable View from, @Nullable View to, @Nullable Transition transition, boolean isPush) {
|
||||
if (from != null && (removesFromViewOnPush() || !isPush) && from.getParent() == container) {
|
||||
container.removeView(from);
|
||||
}
|
||||
if (to != null && to.getParent() == null) {
|
||||
container.addView(to);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
-184
@@ -1,184 +0,0 @@
|
||||
package com.bluelinelabs.conductor.internal;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Build;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import android.transition.Transition;
|
||||
import android.transition.TransitionSet;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
public class TransitionUtils {
|
||||
|
||||
public static void findNamedViews(@NonNull Map<String, View> namedViews, View view) {
|
||||
if (view.getVisibility() == View.VISIBLE) {
|
||||
String transitionName = view.getTransitionName();
|
||||
if (transitionName != null) {
|
||||
namedViews.put(transitionName, view);
|
||||
}
|
||||
|
||||
if (view instanceof ViewGroup) {
|
||||
ViewGroup viewGroup = (ViewGroup) view;
|
||||
int childCount = viewGroup.getChildCount();
|
||||
for (int i = 0; i < childCount; i++) {
|
||||
View child = viewGroup.getChildAt(i);
|
||||
findNamedViews(namedViews, child);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static View findNamedView(@NonNull View view, @NonNull String transitionName) {
|
||||
if (transitionName.equals(view.getTransitionName())) {
|
||||
return view;
|
||||
}
|
||||
|
||||
if (view instanceof ViewGroup) {
|
||||
ViewGroup viewGroup = (ViewGroup) view;
|
||||
int childCount = viewGroup.getChildCount();
|
||||
for (int i = 0; i < childCount; i++) {
|
||||
View viewWithTransitionName = findNamedView(viewGroup.getChildAt(i), transitionName);
|
||||
if (viewWithTransitionName != null) {
|
||||
return viewWithTransitionName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void setEpicenter(@NonNull Transition transition, @Nullable View view) {
|
||||
if (view != null) {
|
||||
final Rect epicenter = new Rect();
|
||||
getBoundsOnScreen(view, epicenter);
|
||||
transition.setEpicenterCallback(new Transition.EpicenterCallback() {
|
||||
@Override
|
||||
public Rect onGetEpicenter(Transition transition) {
|
||||
return epicenter;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public static void getBoundsOnScreen(@NonNull View view, @NonNull Rect epicenter) {
|
||||
int[] loc = new int[2];
|
||||
view.getLocationOnScreen(loc);
|
||||
epicenter.set(loc[0], loc[1], loc[0] + view.getWidth(), loc[1] + view.getHeight());
|
||||
}
|
||||
|
||||
public static void setTargets(@NonNull Transition transition, @NonNull View nonExistentView, @NonNull List<View> sharedViews) {
|
||||
final List<View> views = transition.getTargets();
|
||||
views.clear();
|
||||
final int count = sharedViews.size();
|
||||
for (int i = 0; i < count; i++) {
|
||||
final View view = sharedViews.get(i);
|
||||
bfsAddViewChildren(views, view);
|
||||
}
|
||||
views.add(nonExistentView);
|
||||
sharedViews.add(nonExistentView);
|
||||
addTargets(transition, sharedViews);
|
||||
}
|
||||
|
||||
public static void addTargets(@Nullable Transition transition, @NonNull List<View> views) {
|
||||
if (transition == null) {
|
||||
return;
|
||||
}
|
||||
if (transition instanceof TransitionSet) {
|
||||
TransitionSet set = (TransitionSet) transition;
|
||||
int numTransitions = set.getTransitionCount();
|
||||
for (int i = 0; i < numTransitions; i++) {
|
||||
Transition child = set.getTransitionAt(i);
|
||||
addTargets(child, views);
|
||||
}
|
||||
} else if (!hasSimpleTarget(transition)) {
|
||||
List<View> targets = transition.getTargets();
|
||||
if (isNullOrEmpty(targets)) {
|
||||
int numViews = views.size();
|
||||
for (int i = 0; i < numViews; i++) {
|
||||
transition.addTarget(views.get(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void replaceTargets(@NonNull Transition transition, @NonNull List<View> oldTargets, @Nullable List<View> newTargets) {
|
||||
if (transition instanceof TransitionSet) {
|
||||
TransitionSet set = (TransitionSet) transition;
|
||||
int numTransitions = set.getTransitionCount();
|
||||
for (int i = 0; i < numTransitions; i++) {
|
||||
Transition child = set.getTransitionAt(i);
|
||||
replaceTargets(child, oldTargets, newTargets);
|
||||
}
|
||||
} else if (!hasSimpleTarget(transition)) {
|
||||
List<View> targets = transition.getTargets();
|
||||
if (targets != null && targets.size() == oldTargets.size() && targets.containsAll(oldTargets)) {
|
||||
final int targetCount = newTargets == null ? 0 : newTargets.size();
|
||||
for (int i = 0; i < targetCount; i++) {
|
||||
transition.addTarget(newTargets.get(i));
|
||||
}
|
||||
for (int i = oldTargets.size() - 1; i >= 0; i--) {
|
||||
transition.removeTarget(oldTargets.get(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void bfsAddViewChildren(@NonNull final List<View> views, @NonNull final View startView) {
|
||||
final int startIndex = views.size();
|
||||
if (containedBeforeIndex(views, startView, startIndex)) {
|
||||
return; // This child is already in the list, so all its children are also.
|
||||
}
|
||||
views.add(startView);
|
||||
for (int index = startIndex; index < views.size(); index++) {
|
||||
final View view = views.get(index);
|
||||
if (view instanceof ViewGroup) {
|
||||
ViewGroup viewGroup = (ViewGroup) view;
|
||||
final int childCount = viewGroup.getChildCount();
|
||||
for (int childIndex = 0; childIndex < childCount; childIndex++) {
|
||||
final View child = viewGroup.getChildAt(childIndex);
|
||||
if (!containedBeforeIndex(views, child, startIndex)) {
|
||||
views.add(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean containedBeforeIndex(@NonNull List<View> views, View view, int maxIndex) {
|
||||
for (int i = 0; i < maxIndex; i++) {
|
||||
if (views.get(i) == view) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean hasSimpleTarget(@NonNull Transition transition) {
|
||||
return !isNullOrEmpty(transition.getTargetIds())
|
||||
|| !isNullOrEmpty(transition.getTargetNames())
|
||||
|| !isNullOrEmpty(transition.getTargetTypes());
|
||||
}
|
||||
|
||||
private static boolean isNullOrEmpty(@Nullable List list) {
|
||||
return list == null || list.isEmpty();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static TransitionSet mergeTransitions(int ordering, Transition... transitions) {
|
||||
TransitionSet transitionSet = new TransitionSet();
|
||||
for (Transition transition : transitions) {
|
||||
if (transition != null) {
|
||||
transitionSet.addTransition(transition);
|
||||
}
|
||||
}
|
||||
transitionSet.setOrdering(ordering);
|
||||
return transitionSet;
|
||||
}
|
||||
}
|
||||
@@ -14,13 +14,13 @@ android {
|
||||
|
||||
dependencies {
|
||||
testImplementation rootProject.ext.junit
|
||||
testImplementation rootProject.ext.roboelectric
|
||||
testImplementation rootProject.ext.robolectric
|
||||
|
||||
implementation rootProject.ext.androidxAppCompat
|
||||
implementation project(':conductor')
|
||||
}
|
||||
|
||||
ext.artifactId = 'conductor-viewpager'
|
||||
|
||||
apply from: rootProject.file('dependencies.gradle')
|
||||
|
||||
ext.artifactId = 'conductor-viewpager'
|
||||
apply plugin: "com.vanniktech.maven.publish"
|
||||
|
||||
+11
-11
@@ -6,6 +6,10 @@ import android.util.SparseArray;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.viewpager.widget.PagerAdapter;
|
||||
|
||||
import com.bluelinelabs.conductor.Controller;
|
||||
import com.bluelinelabs.conductor.Router;
|
||||
import com.bluelinelabs.conductor.RouterTransaction;
|
||||
@@ -15,12 +19,8 @@ import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.viewpager.widget.PagerAdapter;
|
||||
|
||||
/**
|
||||
* An adapter for ViewPagers that uses Routers as pages
|
||||
* An ViewPager adapter that uses Routers as pages
|
||||
*/
|
||||
public abstract class RouterPagerAdapter extends PagerAdapter {
|
||||
|
||||
@@ -32,9 +32,9 @@ public abstract class RouterPagerAdapter extends PagerAdapter {
|
||||
|
||||
private final Controller host;
|
||||
private int maxPagesToStateSave = Integer.MAX_VALUE;
|
||||
private Map<Integer, String> tags = new HashMap<>();
|
||||
private final Map<Integer, String> tags = new HashMap<>();
|
||||
private SparseArray<Bundle> savedPages = new SparseArray<>();
|
||||
private SparseArray<Router> visibleRouters = new SparseArray<>();
|
||||
private final SparseArray<Router> visibleRouters = new SparseArray<>();
|
||||
private ArrayList<Integer> savedPageHistory = new ArrayList<>();
|
||||
private Router currentPrimaryRouter;
|
||||
|
||||
@@ -103,7 +103,7 @@ public abstract class RouterPagerAdapter extends PagerAdapter {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroyItem(ViewGroup container, int position, Object object) {
|
||||
public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
|
||||
Router router = (Router)object;
|
||||
|
||||
Bundle savedState = new Bundle();
|
||||
@@ -121,8 +121,8 @@ public abstract class RouterPagerAdapter extends PagerAdapter {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPrimaryItem(ViewGroup container, int position, Object object) {
|
||||
Router router = (Router)object;
|
||||
public void setPrimaryItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
|
||||
Router router = (Router) object;
|
||||
if (router != currentPrimaryRouter) {
|
||||
if (currentPrimaryRouter != null) {
|
||||
for (RouterTransaction transaction : currentPrimaryRouter.getBackstack()) {
|
||||
@@ -139,7 +139,7 @@ public abstract class RouterPagerAdapter extends PagerAdapter {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isViewFromObject(View view, Object object) {
|
||||
public boolean isViewFromObject(@NonNull View view, @NonNull Object object) {
|
||||
Router router = (Router)object;
|
||||
final List<RouterTransaction> backstack = router.getBackstack();
|
||||
for (RouterTransaction transaction : backstack) {
|
||||
|
||||
+27
-53
@@ -1,13 +1,6 @@
|
||||
package com.bluelinelabs.conductor.viewpager
|
||||
|
||||
import android.app.Activity
|
||||
import android.widget.FrameLayout
|
||||
import androidx.core.view.ViewCompat
|
||||
import com.bluelinelabs.conductor.Conductor
|
||||
import com.bluelinelabs.conductor.Router
|
||||
import com.bluelinelabs.conductor.RouterTransaction.Companion.with
|
||||
import com.bluelinelabs.conductor.viewpager.util.FakePager
|
||||
import com.bluelinelabs.conductor.viewpager.util.TestController
|
||||
import com.bluelinelabs.conductor.viewpager.util.TestActivity
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
@@ -19,43 +12,25 @@ import org.robolectric.annotation.Config
|
||||
@Config(manifest = Config.NONE)
|
||||
class StateSaveTests {
|
||||
|
||||
private val pager: FakePager
|
||||
private val pagerAdapter: RouterPagerAdapter
|
||||
private val testController = Robolectric.buildActivity(TestActivity::class.java)
|
||||
.setup()
|
||||
.get()
|
||||
.testController()
|
||||
|
||||
init {
|
||||
val activityController = Robolectric.buildActivity(Activity::class.java).setup()
|
||||
val router = Conductor.attachRouter(activityController.get(), FrameLayout(activityController.get()), null)
|
||||
val controller = TestController()
|
||||
router.setRoot(with(controller))
|
||||
pager = FakePager(FrameLayout(activityController.get()).also {
|
||||
it.id = ViewCompat.generateViewId()
|
||||
})
|
||||
pager.offscreenPageLimit = 1
|
||||
pagerAdapter = object : RouterPagerAdapter(controller) {
|
||||
override fun configureRouter(router: Router, position: Int) {
|
||||
if (!router.hasRootController()) {
|
||||
router.setRoot(with(TestController()))
|
||||
}
|
||||
}
|
||||
|
||||
override fun getCount(): Int {
|
||||
return 20
|
||||
}
|
||||
}
|
||||
pager.setAdapter(pagerAdapter)
|
||||
}
|
||||
private val pagerAdapter = testController.pagerAdapter
|
||||
private val pager = testController.pager
|
||||
private val destroyedItems = testController.destroyedItems
|
||||
|
||||
@Test
|
||||
fun testNoMaxSaves() {
|
||||
// Load all pages
|
||||
for (i in 0 until pagerAdapter.count) {
|
||||
pager.pageTo(i)
|
||||
pager.currentItem = i
|
||||
}
|
||||
pager.pageTo(pagerAdapter.count / 2)
|
||||
|
||||
// Ensure all non-visible pages are saved
|
||||
assertEquals(
|
||||
pagerAdapter.count - 1 - (pager.offscreenPageLimit * 2),
|
||||
destroyedItems.size,
|
||||
pagerAdapter.savedPages.size()
|
||||
)
|
||||
}
|
||||
@@ -67,38 +42,37 @@ class StateSaveTests {
|
||||
|
||||
// Load all pages
|
||||
for (i in 0 until pagerAdapter.count) {
|
||||
pager.pageTo(i)
|
||||
pager.currentItem = i
|
||||
}
|
||||
|
||||
val firstSelectedItem = pagerAdapter.count / 2
|
||||
pager.pageTo(firstSelectedItem)
|
||||
for (i in pagerAdapter.count downTo firstSelectedItem) {
|
||||
pager.currentItem = i
|
||||
}
|
||||
|
||||
var savedPages = pagerAdapter.savedPages
|
||||
|
||||
// Ensure correct number of pages are saved
|
||||
assertEquals(maxPages, savedPages.size())
|
||||
|
||||
// Ensure correct pages are saved
|
||||
assertEquals(
|
||||
pagerAdapter.count - 3,
|
||||
savedPages.keyAt(0)
|
||||
)
|
||||
assertEquals(
|
||||
pagerAdapter.count - 2,
|
||||
savedPages.keyAt(1)
|
||||
)
|
||||
assertEquals(
|
||||
pagerAdapter.count - 1,
|
||||
savedPages.keyAt(2)
|
||||
)
|
||||
assertEquals(destroyedItems[destroyedItems.lastIndex], savedPages.keyAt(0))
|
||||
assertEquals(destroyedItems[destroyedItems.lastIndex - 1], savedPages.keyAt(1))
|
||||
assertEquals(destroyedItems[destroyedItems.lastIndex - 2], savedPages.keyAt(2))
|
||||
|
||||
val secondSelectedItem = 1
|
||||
pager.pageTo(secondSelectedItem)
|
||||
for (i in firstSelectedItem downTo secondSelectedItem) {
|
||||
pager.currentItem = i
|
||||
}
|
||||
|
||||
savedPages = pagerAdapter.savedPages
|
||||
|
||||
// Ensure correct number of pages are saved
|
||||
assertEquals(maxPages, savedPages.size())
|
||||
|
||||
// Ensure correct pages are saved
|
||||
assertEquals(firstSelectedItem - 1, savedPages.keyAt(0))
|
||||
assertEquals(firstSelectedItem, savedPages.keyAt(1))
|
||||
assertEquals(firstSelectedItem + 1, savedPages.keyAt(2))
|
||||
assertEquals(destroyedItems[destroyedItems.lastIndex], savedPages.keyAt(0))
|
||||
assertEquals(destroyedItems[destroyedItems.lastIndex - 1], savedPages.keyAt(1))
|
||||
assertEquals(destroyedItems[destroyedItems.lastIndex - 2], savedPages.keyAt(2))
|
||||
}
|
||||
}
|
||||
-61
@@ -1,61 +0,0 @@
|
||||
package com.bluelinelabs.conductor.viewpager.util;
|
||||
|
||||
import android.util.SparseArray;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.bluelinelabs.conductor.viewpager.RouterPagerAdapter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class FakePager {
|
||||
|
||||
private ViewGroup container;
|
||||
private int offscreenPageLimit;
|
||||
private final SparseArray<Object> pages = new SparseArray<>();
|
||||
|
||||
private RouterPagerAdapter adapter;
|
||||
|
||||
public FakePager(ViewGroup container) {
|
||||
this.container = container;
|
||||
}
|
||||
|
||||
public void setAdapter(RouterPagerAdapter adapter) {
|
||||
this.adapter = adapter;
|
||||
}
|
||||
|
||||
public void pageTo(int page) {
|
||||
int firstPage = Math.max(0, page - offscreenPageLimit);
|
||||
int lastPage = Math.min(adapter.getCount() - 1, page + offscreenPageLimit);
|
||||
|
||||
List<Integer> pagesI = new ArrayList<>();
|
||||
for (int i = 0; i < pages.size(); i++) {
|
||||
pagesI.add(pages.keyAt(i));
|
||||
}
|
||||
|
||||
for (int i = pages.size() - 1; i >= 0; i--) {
|
||||
int key = pages.keyAt(i);
|
||||
|
||||
if (key < firstPage || key > lastPage) {
|
||||
adapter.destroyItem(container, key, pages.get(key));
|
||||
pages.remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
for (int key = firstPage; key <= lastPage; key++) {
|
||||
if (pages.get(key) == null) {
|
||||
pages.put(key, adapter.instantiateItem(container, key));
|
||||
}
|
||||
}
|
||||
|
||||
adapter.setPrimaryItem(container, page, pages.get(page));
|
||||
}
|
||||
|
||||
public int getOffscreenPageLimit() {
|
||||
return offscreenPageLimit;
|
||||
}
|
||||
|
||||
public void setOffscreenPageLimit(int offscreenPageLimit) {
|
||||
this.offscreenPageLimit = offscreenPageLimit;
|
||||
}
|
||||
}
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
package com.bluelinelabs.conductor.viewpager.util
|
||||
|
||||
import android.app.Activity
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.FrameLayout
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.viewpager.widget.ViewPager
|
||||
import com.bluelinelabs.conductor.Conductor
|
||||
import com.bluelinelabs.conductor.Controller
|
||||
import com.bluelinelabs.conductor.Router
|
||||
import com.bluelinelabs.conductor.RouterTransaction
|
||||
import com.bluelinelabs.conductor.asTransaction
|
||||
import com.bluelinelabs.conductor.viewpager.RouterPagerAdapter
|
||||
|
||||
class TestActivity : Activity() {
|
||||
|
||||
private lateinit var router: Router
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
router = Conductor.attachRouter(
|
||||
this,
|
||||
findViewById(android.R.id.content),
|
||||
savedInstanceState
|
||||
)
|
||||
if (!router.hasRootController()) {
|
||||
router.setRoot(TestController().asTransaction())
|
||||
}
|
||||
}
|
||||
|
||||
fun testController(): TestController {
|
||||
return router.backstack.single().controller as TestController
|
||||
}
|
||||
}
|
||||
|
||||
class TestController : Controller() {
|
||||
|
||||
val destroyedItems = mutableListOf<Int>()
|
||||
lateinit var pagerAdapter: RouterPagerAdapter
|
||||
lateinit var pager: ViewPager
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup,
|
||||
savedViewState: Bundle?
|
||||
): View {
|
||||
pager = ViewPager(container.context).also {
|
||||
it.id = ViewCompat.generateViewId()
|
||||
}
|
||||
pager.offscreenPageLimit = 1
|
||||
pagerAdapter = object : RouterPagerAdapter(this) {
|
||||
|
||||
override fun configureRouter(router: Router, position: Int) {
|
||||
if (!router.hasRootController()) {
|
||||
router.setRoot(RouterTransaction.with(PageController()))
|
||||
}
|
||||
}
|
||||
|
||||
override fun getCount(): Int {
|
||||
return 20
|
||||
}
|
||||
|
||||
override fun destroyItem(container: ViewGroup, position: Int, `object`: Any) {
|
||||
super.destroyItem(container, position, `object`)
|
||||
destroyedItems.add(position)
|
||||
}
|
||||
}
|
||||
pager.adapter = pagerAdapter
|
||||
return pager
|
||||
}
|
||||
}
|
||||
|
||||
class PageController : Controller() {
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup,
|
||||
savedViewState: Bundle?
|
||||
): View {
|
||||
return FrameLayout(container.context)
|
||||
}
|
||||
}
|
||||
+16
-7
@@ -1,7 +1,6 @@
|
||||
apply from: rootProject.file('dependencies.gradle')
|
||||
|
||||
apply plugin: 'com.android.library'
|
||||
apply plugin: "com.vanniktech.maven.publish"
|
||||
apply plugin: 'kotlin-android'
|
||||
apply plugin: 'kotlin-parcelize'
|
||||
|
||||
android {
|
||||
compileSdkVersion rootProject.ext.compileSdkVersion
|
||||
@@ -12,14 +11,24 @@ android {
|
||||
versionCode Integer.parseInt(project.VERSION_CODE)
|
||||
versionName project.VERSION_NAME
|
||||
}
|
||||
|
||||
testOptions {
|
||||
unitTests {
|
||||
includeAndroidResources = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api rootProject.ext.rxJava2
|
||||
api rootProject.ext.rxLifecycle2
|
||||
api rootProject.ext.rxLifecycleAndroid2
|
||||
testImplementation rootProject.ext.junit
|
||||
testImplementation rootProject.ext.robolectric
|
||||
|
||||
implementation rootProject.ext.androidxAppCompat
|
||||
implementation rootProject.ext.androidxViewPager2
|
||||
implementation project(':conductor')
|
||||
}
|
||||
|
||||
ext.artifactId = 'conductor-rxlifecycle2'
|
||||
apply from: rootProject.file('dependencies.gradle')
|
||||
|
||||
ext.artifactId = 'conductor-viewpager2'
|
||||
apply plugin: "com.vanniktech.maven.publish"
|
||||
@@ -0,0 +1,3 @@
|
||||
POM_NAME=Conductor ViewPager2 Adapter
|
||||
POM_ARTIFACT_ID=conductor-viewpager2
|
||||
POM_PACKAGING=aar
|
||||
@@ -0,0 +1,3 @@
|
||||
<manifest package="com.bluelinelabs.conductor.viewpager2">
|
||||
<application />
|
||||
</manifest>
|
||||
+255
@@ -0,0 +1,255 @@
|
||||
package com.bluelinelabs.conductor.viewpager2
|
||||
|
||||
import android.os.Bundle
|
||||
import android.os.Parcelable
|
||||
import android.util.LongSparseArray
|
||||
import android.util.SparseArray
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.viewpager2.adapter.StatefulAdapter
|
||||
import androidx.viewpager2.widget.ViewPager2
|
||||
import com.bluelinelabs.conductor.Controller
|
||||
import com.bluelinelabs.conductor.Router
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
/**
|
||||
* An ViewPager2 adapter that uses Routers as pages
|
||||
*/
|
||||
abstract class RouterStateAdapter(private val host: Controller) :
|
||||
RecyclerView.Adapter<RouterViewHolder>(), StatefulAdapter {
|
||||
|
||||
private var savedPages = LongSparseArray<Bundle>()
|
||||
internal var savedPageHistory = mutableListOf<Long>()
|
||||
private var maxPagesToStateSave = Int.MAX_VALUE
|
||||
private val visibleRouters = SparseArray<Router>()
|
||||
private var currentPrimaryRouterPosition = 0
|
||||
private var primaryItemCallback: PrimaryItemCallback? = null
|
||||
|
||||
init {
|
||||
super.setHasStableIds(true)
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
abstract fun configureRouter(router: Router, position: Int)
|
||||
|
||||
/**
|
||||
* Sets the maximum number of pages that will have their states saved. When this number is exceeded,
|
||||
* the page that was state saved least recently will have its state removed from the save data.
|
||||
*/
|
||||
open fun setMaxPagesToStateSave(maxPagesToStateSave: Int) {
|
||||
require(maxPagesToStateSave >= 0) { "Only positive integers may be passed for maxPagesToStateSave." }
|
||||
this.maxPagesToStateSave = maxPagesToStateSave
|
||||
ensurePagesSaved()
|
||||
}
|
||||
|
||||
private fun inferViewPager(recyclerView: RecyclerView): ViewPager2 {
|
||||
return recyclerView.parent as? ViewPager2
|
||||
?: error("Expected ViewPager2 instance. Got: ${recyclerView.parent}")
|
||||
}
|
||||
|
||||
override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {
|
||||
val viewPager = inferViewPager(recyclerView)
|
||||
primaryItemCallback = PrimaryItemCallback().also {
|
||||
viewPager.registerOnPageChangeCallback(it)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) {
|
||||
val viewPager = inferViewPager(recyclerView)
|
||||
primaryItemCallback?.let {
|
||||
viewPager.unregisterOnPageChangeCallback(it)
|
||||
}
|
||||
primaryItemCallback = null
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RouterViewHolder {
|
||||
return RouterViewHolder(parent)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: RouterViewHolder, position: Int) {
|
||||
holder.currentItemPosition = position
|
||||
|
||||
attachRouter(holder, position)
|
||||
}
|
||||
|
||||
override fun onViewAttachedToWindow(holder: RouterViewHolder) {
|
||||
super.onViewAttachedToWindow(holder)
|
||||
|
||||
if (!holder.attached) {
|
||||
attachRouter(holder, holder.currentItemPosition)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onViewDetachedFromWindow(holder: RouterViewHolder) {
|
||||
super.onViewDetachedFromWindow(holder)
|
||||
|
||||
detachRouter(holder)
|
||||
|
||||
// Controller has fully detached and destroyed its view reference by now. Remove the leftover
|
||||
// view from the container.
|
||||
holder.container.removeAllViews()
|
||||
}
|
||||
|
||||
override fun onViewRecycled(holder: RouterViewHolder) {
|
||||
super.onViewRecycled(holder)
|
||||
|
||||
detachRouter(holder)
|
||||
|
||||
holder.currentRouter?.let { router ->
|
||||
host.removeChildRouter(router)
|
||||
holder.currentRouter = null
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFailedToRecycleView(holder: RouterViewHolder): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
override fun getItemId(position: Int): Long {
|
||||
return position.toLong()
|
||||
}
|
||||
|
||||
override fun setHasStableIds(hasStableIds: Boolean) {
|
||||
throw UnsupportedOperationException("Stable Ids are required for the adapter to function properly")
|
||||
}
|
||||
|
||||
override fun saveState(): Parcelable {
|
||||
// Ensure all visible pages are saved, starting at the outermost pages and working our way in
|
||||
val visiblePositions = (0 until visibleRouters.size())
|
||||
.map { visibleRouters.keyAt(it) }.toMutableList()
|
||||
while (visiblePositions.isNotEmpty()) {
|
||||
val lastPosition = visiblePositions.removeAt(visiblePositions.lastIndex)
|
||||
savePage(getItemId(lastPosition), visibleRouters[lastPosition])
|
||||
|
||||
if (visiblePositions.isNotEmpty()) {
|
||||
val firstPosition = visiblePositions.removeAt(0)
|
||||
savePage(getItemId(firstPosition), visibleRouters[firstPosition])
|
||||
}
|
||||
}
|
||||
|
||||
return SavedState(
|
||||
savedPagesKeys = (0 until savedPages.size()).map { savedPages.keyAt(it) },
|
||||
savedPagesValues = (0 until savedPages.size()).map { savedPages.valueAt(it) },
|
||||
savedPageHistory = savedPageHistory,
|
||||
maxPagesToStateSave = maxPagesToStateSave
|
||||
)
|
||||
}
|
||||
|
||||
override fun restoreState(state: Parcelable) {
|
||||
if (state !is SavedState) return
|
||||
|
||||
savedPages = LongSparseArray()
|
||||
state.savedPagesKeys.indices.forEach { index ->
|
||||
savedPages.put(state.savedPagesKeys[index], state.savedPagesValues[index])
|
||||
}
|
||||
|
||||
savedPageHistory = state.savedPageHistory.toMutableList()
|
||||
maxPagesToStateSave = state.maxPagesToStateSave
|
||||
}
|
||||
|
||||
private fun attachRouter(holder: RouterViewHolder, position: Int) {
|
||||
val itemId = getItemId(position)
|
||||
val router = host.getChildRouter(holder.container, "$itemId", true, false)!!
|
||||
|
||||
// This should have already been handled by onViewRecycled, but it seems like this wasn't
|
||||
// always reliably called
|
||||
if (router != holder.currentRouter) {
|
||||
holder.currentRouter?.let { host.removeChildRouter(it) }
|
||||
}
|
||||
|
||||
holder.currentRouter = router
|
||||
holder.currentItemId = itemId
|
||||
|
||||
if (!router.hasRootController()) {
|
||||
val routerSavedState = savedPages[itemId]
|
||||
if (routerSavedState != null) {
|
||||
router.restoreInstanceState(routerSavedState)
|
||||
savedPages.remove(itemId)
|
||||
savedPageHistory.remove(itemId)
|
||||
}
|
||||
}
|
||||
|
||||
router.rebindIfNeeded()
|
||||
configureRouter(router, position)
|
||||
|
||||
if (position != currentPrimaryRouterPosition) {
|
||||
for (transaction in router.backstack) {
|
||||
transaction.controller.setOptionsMenuHidden(true)
|
||||
}
|
||||
}
|
||||
|
||||
visibleRouters.put(position, router)
|
||||
|
||||
holder.attached = true
|
||||
}
|
||||
|
||||
private fun detachRouter(holder: RouterViewHolder) {
|
||||
if (!holder.attached) {
|
||||
return
|
||||
}
|
||||
|
||||
holder.currentRouter?.let { router ->
|
||||
router.prepareForHostDetach()
|
||||
|
||||
savePage(holder.currentItemId, router)
|
||||
|
||||
if (visibleRouters[holder.currentItemPosition] == router) {
|
||||
visibleRouters.remove(holder.currentItemPosition)
|
||||
}
|
||||
}
|
||||
|
||||
holder.attached = false
|
||||
}
|
||||
|
||||
private fun savePage(itemId: Long, router: Router) {
|
||||
val savedState = Bundle()
|
||||
router.saveInstanceState(savedState)
|
||||
savedPages.put(itemId, savedState)
|
||||
|
||||
savedPageHistory.remove(itemId)
|
||||
savedPageHistory.add(itemId)
|
||||
|
||||
ensurePagesSaved()
|
||||
}
|
||||
|
||||
private fun ensurePagesSaved() {
|
||||
while (savedPages.size() > maxPagesToStateSave) {
|
||||
val routerIdToRemove = savedPageHistory.removeAt(0)
|
||||
savedPages.remove(routerIdToRemove)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the already instantiated Router in the specified position or `null` if there
|
||||
* is no router associated with this position.
|
||||
*/
|
||||
fun getRouter(position: Int): Router? {
|
||||
return visibleRouters[position]
|
||||
}
|
||||
|
||||
inner class PrimaryItemCallback : ViewPager2.OnPageChangeCallback() {
|
||||
override fun onPageSelected(position: Int) {
|
||||
val router = visibleRouters[position]
|
||||
if (position != currentPrimaryRouterPosition) {
|
||||
val previousRouter = visibleRouters[currentPrimaryRouterPosition]
|
||||
|
||||
previousRouter?.backstack?.forEach { it.controller.setOptionsMenuHidden(true) }
|
||||
router?.backstack?.forEach { it.controller.setOptionsMenuHidden(false) }
|
||||
currentPrimaryRouterPosition = position
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Parcelize
|
||||
private data class SavedState(
|
||||
val savedPagesKeys: List<Long>,
|
||||
val savedPagesValues: List<Bundle>,
|
||||
val savedPageHistory: List<Long>,
|
||||
val maxPagesToStateSave: Int
|
||||
) : Parcelable
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
package com.bluelinelabs.conductor.viewpager2
|
||||
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.recyclerview.widget.RecyclerView.ViewHolder
|
||||
import com.bluelinelabs.conductor.ChangeHandlerFrameLayout
|
||||
import com.bluelinelabs.conductor.Router
|
||||
|
||||
class RouterViewHolder private constructor(val container: ChangeHandlerFrameLayout) : ViewHolder(container) {
|
||||
var currentRouter: Router? = null
|
||||
var currentItemPosition = 0
|
||||
var currentItemId = 0L
|
||||
var attached = false
|
||||
|
||||
companion object {
|
||||
operator fun invoke(parent: ViewGroup): RouterViewHolder {
|
||||
val container = ChangeHandlerFrameLayout(parent.context)
|
||||
container.id = ViewCompat.generateViewId()
|
||||
container.layoutParams = ViewGroup.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.MATCH_PARENT
|
||||
)
|
||||
container.isSaveEnabled = false
|
||||
return RouterViewHolder(container)
|
||||
}
|
||||
}
|
||||
}
|
||||
+119
@@ -0,0 +1,119 @@
|
||||
package com.bluelinelabs.conductor.viewpager2
|
||||
|
||||
import android.app.Activity
|
||||
import android.os.Looper.getMainLooper
|
||||
import android.widget.FrameLayout
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.viewpager2.widget.ViewPager2
|
||||
import com.bluelinelabs.conductor.Conductor
|
||||
import com.bluelinelabs.conductor.Router
|
||||
import com.bluelinelabs.conductor.RouterTransaction.Companion.with
|
||||
import com.bluelinelabs.conductor.viewpager2.util.TestController
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.Robolectric
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import org.robolectric.Shadows.shadowOf
|
||||
import org.robolectric.annotation.Config
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
@Config(manifest = Config.NONE)
|
||||
class StateSaveTests {
|
||||
|
||||
private val pager: ViewPager2
|
||||
private val adapter: RouterStateAdapter
|
||||
private val destroyedItems = mutableListOf<Int>()
|
||||
|
||||
init {
|
||||
val activityController = Robolectric.buildActivity(Activity::class.java).setup()
|
||||
val layout = FrameLayout(activityController.get())
|
||||
activityController.get().setContentView(layout)
|
||||
val router = Conductor.attachRouter(activityController.get(), FrameLayout(activityController.get()), null)
|
||||
val controller = TestController()
|
||||
router.setRoot(with(controller))
|
||||
pager = ViewPager2(activityController.get()).also {
|
||||
it.id = ViewCompat.generateViewId()
|
||||
}
|
||||
layout.addView(pager)
|
||||
pager.offscreenPageLimit = 1
|
||||
adapter = object : RouterStateAdapter(controller) {
|
||||
override fun configureRouter(router: Router, position: Int) {
|
||||
if (!router.hasRootController()) {
|
||||
router.setRoot(with(TestController()))
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
return 20
|
||||
}
|
||||
|
||||
override fun onViewDetachedFromWindow(holder: RouterViewHolder) {
|
||||
super.onViewDetachedFromWindow(holder)
|
||||
|
||||
destroyedItems.add(holder.currentItemPosition)
|
||||
}
|
||||
}
|
||||
pager.adapter = adapter
|
||||
shadowOf(getMainLooper()).idle()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNoMaxSaves() {
|
||||
// Load all pages
|
||||
for (i in 0 until adapter.itemCount) {
|
||||
pager.setCurrentItem(i, false)
|
||||
shadowOf(getMainLooper()).idle()
|
||||
}
|
||||
|
||||
// Ensure all non-visible pages are saved
|
||||
assertEquals(
|
||||
destroyedItems.size,
|
||||
adapter.savedPageHistory.size
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testMaxSavedSet() {
|
||||
val maxPages = 3
|
||||
adapter.setMaxPagesToStateSave(maxPages)
|
||||
|
||||
// Load all pages
|
||||
for (i in 0 until adapter.itemCount) {
|
||||
pager.setCurrentItem(i, false)
|
||||
shadowOf(getMainLooper()).idle()
|
||||
}
|
||||
|
||||
val firstSelectedItem = adapter.itemCount / 2
|
||||
for (i in adapter.itemCount downTo firstSelectedItem) {
|
||||
pager.setCurrentItem(i, false)
|
||||
shadowOf(getMainLooper()).idle()
|
||||
}
|
||||
|
||||
var savedPages = adapter.savedPageHistory
|
||||
|
||||
// Ensure correct number of pages are saved
|
||||
assertEquals(maxPages, savedPages.size)
|
||||
|
||||
// Ensure correct pages are saved
|
||||
assertEquals(destroyedItems[destroyedItems.lastIndex], savedPages[savedPages.lastIndex].toInt())
|
||||
assertEquals(destroyedItems[destroyedItems.lastIndex - 1], savedPages[savedPages.lastIndex - 1].toInt())
|
||||
assertEquals(destroyedItems[destroyedItems.lastIndex - 2], savedPages[savedPages.lastIndex - 2].toInt())
|
||||
|
||||
val secondSelectedItem = 1
|
||||
for (i in adapter.itemCount downTo secondSelectedItem) {
|
||||
pager.setCurrentItem(i, false)
|
||||
shadowOf(getMainLooper()).idle()
|
||||
}
|
||||
|
||||
savedPages = adapter.savedPageHistory
|
||||
|
||||
// Ensure correct number of pages are saved
|
||||
assertEquals(maxPages, savedPages.size)
|
||||
|
||||
// Ensure correct pages are saved
|
||||
assertEquals(destroyedItems[destroyedItems.lastIndex], savedPages[savedPages.lastIndex].toInt())
|
||||
assertEquals(destroyedItems[destroyedItems.lastIndex - 1], savedPages[savedPages.lastIndex - 1].toInt())
|
||||
assertEquals(destroyedItems[destroyedItems.lastIndex - 2], savedPages[savedPages.lastIndex - 2].toInt())
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.bluelinelabs.conductor.viewpager.util;
|
||||
package com.bluelinelabs.conductor.viewpager2.util;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
@@ -14,8 +14,12 @@ android {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation savedState
|
||||
testImplementation rootProject.ext.junit
|
||||
testImplementation rootProject.ext.roboelectric
|
||||
testImplementation rootProject.ext.robolectric
|
||||
testImplementation kotestAssertions
|
||||
|
||||
implementation archComponentsLifecycle
|
||||
|
||||
api rootProject.ext.androidxAnnotations
|
||||
api kotlinStd
|
||||
@@ -23,7 +27,7 @@ dependencies {
|
||||
lintPublish project(':conductor-lint')
|
||||
}
|
||||
|
||||
ext.artifactId = 'conductor'
|
||||
|
||||
apply from: rootProject.file('dependencies.gradle')
|
||||
|
||||
ext.artifactId = 'conductor'
|
||||
apply plugin: "com.vanniktech.maven.publish"
|
||||
|
||||
@@ -5,9 +5,10 @@ import android.content.Intent;
|
||||
import android.content.IntentSender;
|
||||
import android.content.IntentSender.SendIntentException;
|
||||
import android.os.Bundle;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.bluelinelabs.conductor.ControllerChangeHandler.ControllerChangeListener;
|
||||
import com.bluelinelabs.conductor.internal.LifecycleHandler;
|
||||
@@ -57,9 +58,12 @@ public class ActivityHostedRouter extends Router {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityDestroyed(@NonNull Activity activity) {
|
||||
super.onActivityDestroyed(activity);
|
||||
lifecycleHandler = null;
|
||||
public void onActivityDestroyed(@NonNull Activity activity, boolean isConfigurationChange) {
|
||||
super.onActivityDestroyed(activity, isConfigurationChange);
|
||||
|
||||
if (!isConfigurationChange) {
|
||||
lifecycleHandler = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,118 +0,0 @@
|
||||
package com.bluelinelabs.conductor;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Deque;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
class Backstack implements Iterable<RouterTransaction> {
|
||||
|
||||
private static final String KEY_ENTRIES = "Backstack.entries";
|
||||
|
||||
private final Deque<RouterTransaction> backstack = new ArrayDeque<>();
|
||||
|
||||
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
|
||||
boolean isEmpty() {
|
||||
return backstack.isEmpty();
|
||||
}
|
||||
|
||||
int size() {
|
||||
return backstack.size();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
RouterTransaction root() {
|
||||
return backstack.size() > 0 ? backstack.getLast() : null;
|
||||
}
|
||||
|
||||
@Override @NonNull
|
||||
public Iterator<RouterTransaction> iterator() {
|
||||
return backstack.iterator();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
Iterator<RouterTransaction> reverseIterator() {
|
||||
return backstack.descendingIterator();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
List<RouterTransaction> popTo(@NonNull RouterTransaction transaction) {
|
||||
List<RouterTransaction> popped = new ArrayList<>();
|
||||
if (backstack.contains(transaction)) {
|
||||
while (backstack.peek() != transaction) {
|
||||
RouterTransaction poppedTransaction = pop();
|
||||
popped.add(poppedTransaction);
|
||||
}
|
||||
} else {
|
||||
throw new RuntimeException("Tried to pop to a transaction that was not on the back stack");
|
||||
}
|
||||
return popped;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
RouterTransaction pop() {
|
||||
RouterTransaction popped = backstack.pop();
|
||||
popped.controller().destroy();
|
||||
return popped;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
RouterTransaction peek() {
|
||||
return backstack.peek();
|
||||
}
|
||||
|
||||
void push(@NonNull RouterTransaction transaction) {
|
||||
backstack.push(transaction);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
List<RouterTransaction> popAll() {
|
||||
List<RouterTransaction> list = new ArrayList<>();
|
||||
while (!isEmpty()) {
|
||||
list.add(pop());
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
void setBackstack(@NonNull List<RouterTransaction> backstack) {
|
||||
this.backstack.clear();
|
||||
for (RouterTransaction transaction : backstack) {
|
||||
this.backstack.push(transaction);
|
||||
}
|
||||
}
|
||||
|
||||
boolean contains(@NonNull Controller controller) {
|
||||
for (RouterTransaction transaction : backstack) {
|
||||
if (controller == transaction.controller()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package com.bluelinelabs.conductor
|
||||
|
||||
import android.os.Bundle
|
||||
import java.util.*
|
||||
|
||||
internal class Backstack : Iterable<RouterTransaction> {
|
||||
|
||||
private val backstack: Deque<RouterTransaction> = ArrayDeque()
|
||||
|
||||
val isEmpty: Boolean get() = backstack.isEmpty()
|
||||
|
||||
val size: Int get() = backstack.size
|
||||
|
||||
fun root(): RouterTransaction? = backstack.lastOrNull()
|
||||
|
||||
override fun iterator(): MutableIterator<RouterTransaction> {
|
||||
return backstack.iterator()
|
||||
}
|
||||
|
||||
fun reverseIterator(): Iterator<RouterTransaction> = backstack.descendingIterator()
|
||||
|
||||
fun popTo(transaction: RouterTransaction): List<RouterTransaction> {
|
||||
if (transaction in backstack) {
|
||||
val popped: MutableList<RouterTransaction> = ArrayList()
|
||||
while (backstack.peek() != transaction) {
|
||||
val poppedTransaction = pop()
|
||||
popped.add(poppedTransaction)
|
||||
}
|
||||
return popped
|
||||
} else {
|
||||
throw RuntimeException("Tried to pop to a transaction that was not on the back stack")
|
||||
}
|
||||
}
|
||||
|
||||
fun pop(): RouterTransaction {
|
||||
return backstack.pop().also {
|
||||
it.controller.destroy()
|
||||
}
|
||||
}
|
||||
|
||||
fun peek(): RouterTransaction? = backstack.peek()
|
||||
|
||||
fun push(transaction: RouterTransaction) {
|
||||
backstack.push(transaction)
|
||||
}
|
||||
|
||||
fun popAll(): List<RouterTransaction> {
|
||||
val list: MutableList<RouterTransaction> = ArrayList()
|
||||
while (!isEmpty) {
|
||||
list.add(pop())
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
fun setBackstack(backstack: List<RouterTransaction>) {
|
||||
this.backstack.clear()
|
||||
backstack.forEach { transaction ->
|
||||
this.backstack.push(transaction)
|
||||
}
|
||||
}
|
||||
|
||||
operator fun contains(controller: Controller): Boolean {
|
||||
return backstack.any {
|
||||
it.controller == controller
|
||||
}
|
||||
}
|
||||
|
||||
fun saveInstanceState(outState: Bundle) {
|
||||
val entryBundles = ArrayList<Bundle>(backstack.size)
|
||||
backstack.mapTo(entryBundles) {
|
||||
it.saveInstanceState()
|
||||
}
|
||||
outState.putParcelableArrayList(KEY_ENTRIES, entryBundles)
|
||||
}
|
||||
|
||||
fun restoreInstanceState(savedInstanceState: Bundle) {
|
||||
val entryBundles = savedInstanceState.getParcelableArrayList<Bundle?>(KEY_ENTRIES)
|
||||
if (entryBundles != null) {
|
||||
entryBundles.reverse()
|
||||
for (transactionBundle in entryBundles) {
|
||||
backstack.push(RouterTransaction(transactionBundle!!))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val KEY_ENTRIES = "Backstack.entries"
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,6 @@ import android.content.res.Resources;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcelable;
|
||||
import android.text.TextUtils;
|
||||
import android.util.SparseArray;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
@@ -23,6 +22,7 @@ import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.bluelinelabs.conductor.internal.ClassUtils;
|
||||
import com.bluelinelabs.conductor.internal.OwnViewTreeLifecycleAndRegistry;
|
||||
import com.bluelinelabs.conductor.internal.RouterRequiringFunc;
|
||||
import com.bluelinelabs.conductor.internal.ViewAttachHandler;
|
||||
import com.bluelinelabs.conductor.internal.ViewAttachHandler.ViewAttachListener;
|
||||
@@ -32,6 +32,7 @@ import java.lang.reflect.Constructor;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
@@ -141,6 +142,7 @@ public abstract class Controller {
|
||||
this.args = args != null ? args : new Bundle(getClass().getClassLoader());
|
||||
instanceId = UUID.randomUUID().toString();
|
||||
ensureRequiredConstructor();
|
||||
OwnViewTreeLifecycleAndRegistry.Companion.own(this);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -212,6 +214,23 @@ public abstract class Controller {
|
||||
*/
|
||||
@Nullable
|
||||
public final Router getChildRouter(@NonNull ViewGroup container, @Nullable String tag, boolean createIfNeeded) {
|
||||
return getChildRouter(container, tag, createIfNeeded, 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).
|
||||
* The only time this method will return {@code null} is when the child router does not exist prior
|
||||
* to calling this method and the createIfNeeded parameter is set to false.
|
||||
*
|
||||
* @param container The ViewGroup that hosts the child Router
|
||||
* @param tag The router's tag or {@code null} if none is needed
|
||||
* @param createIfNeeded If true, a router will be created if one does not yet exist. Else {@code null} will be returned in this case.
|
||||
* @param boundToHostContainerId If true, a router will only ever rebind with a container with the same view id on state restoration. Note that this must be set to true if the tag is null.
|
||||
*/
|
||||
@Nullable
|
||||
public final Router getChildRouter(@NonNull ViewGroup container, @Nullable String tag, boolean createIfNeeded, boolean boundToHostContainerId) {
|
||||
@IdRes final int containerId = container.getId();
|
||||
if (containerId == View.NO_ID) {
|
||||
throw new IllegalStateException("You must set an id on your container.");
|
||||
@@ -219,7 +238,7 @@ public abstract class Controller {
|
||||
|
||||
ControllerHostedRouter childRouter = null;
|
||||
for (ControllerHostedRouter router : childRouters) {
|
||||
if (router.getHostId() == containerId && TextUtils.equals(tag, router.getTag())) {
|
||||
if (router.matches(containerId, tag)) {
|
||||
childRouter = router;
|
||||
break;
|
||||
}
|
||||
@@ -227,8 +246,8 @@ public abstract class Controller {
|
||||
|
||||
if (childRouter == null) {
|
||||
if (createIfNeeded) {
|
||||
childRouter = new ControllerHostedRouter(container.getId(), tag);
|
||||
childRouter.setHost(this, container);
|
||||
childRouter = new ControllerHostedRouter(container.getId(), tag, boundToHostContainerId);
|
||||
childRouter.setHostContainer(this, container);
|
||||
childRouters.add(childRouter);
|
||||
|
||||
if (isPerformingExitTransition) {
|
||||
@@ -236,7 +255,7 @@ public abstract class Controller {
|
||||
}
|
||||
}
|
||||
} else if (!childRouter.hasHost()) {
|
||||
childRouter.setHost(this, container);
|
||||
childRouter.setHostContainer(this, container);
|
||||
childRouter.rebindIfNeeded();
|
||||
}
|
||||
|
||||
@@ -522,21 +541,36 @@ public abstract class Controller {
|
||||
* Calls startActivity(Intent) from this Controller's host Activity.
|
||||
*/
|
||||
public final void startActivity(@NonNull final Intent intent) {
|
||||
executeWithRouter(() -> router.startActivity(intent));
|
||||
executeWithRouter(new RouterRequiringFunc() {
|
||||
@Override
|
||||
public void execute() {
|
||||
router.startActivity(intent);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls startActivityForResult(Intent, int) from this Controller's host Activity.
|
||||
*/
|
||||
public final void startActivityForResult(@NonNull final Intent intent, final int requestCode) {
|
||||
executeWithRouter(() -> router.startActivityForResult(instanceId, intent, requestCode));
|
||||
executeWithRouter(new RouterRequiringFunc() {
|
||||
@Override
|
||||
public void execute() {
|
||||
router.startActivityForResult(instanceId, intent, requestCode);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls startActivityForResult(Intent, int, Bundle) from this Controller's host Activity.
|
||||
*/
|
||||
public final void startActivityForResult(@NonNull final Intent intent, final int requestCode, @Nullable final Bundle options) {
|
||||
executeWithRouter(() -> router.startActivityForResult(instanceId, intent, requestCode, options));
|
||||
executeWithRouter(new RouterRequiringFunc() {
|
||||
@Override
|
||||
public void execute() {
|
||||
router.startActivityForResult(instanceId, intent, requestCode, options);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -554,7 +588,12 @@ public abstract class Controller {
|
||||
* @param requestCode The request code being registered for.
|
||||
*/
|
||||
public final void registerForActivityResult(final int requestCode) {
|
||||
executeWithRouter(() -> router.registerForActivityResult(instanceId, requestCode));
|
||||
executeWithRouter(new RouterRequiringFunc() {
|
||||
@Override
|
||||
public void execute() {
|
||||
router.registerForActivityResult(instanceId, requestCode);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -577,7 +616,12 @@ public abstract class Controller {
|
||||
public final void requestPermissions(@NonNull final String[] permissions, final int requestCode) {
|
||||
requestedPermissions.addAll(Arrays.asList(permissions));
|
||||
|
||||
executeWithRouter(() -> router.requestPermissions(instanceId, permissions, requestCode));
|
||||
executeWithRouter(new RouterRequiringFunc() {
|
||||
@Override
|
||||
public void execute() {
|
||||
router.requestPermissions(instanceId, permissions, requestCode);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -612,7 +656,12 @@ public abstract class Controller {
|
||||
childTransactions.addAll(childRouter.getBackstack());
|
||||
}
|
||||
|
||||
Collections.sort(childTransactions, (o1, o2) -> o2.getTransactionIndex() - o1.getTransactionIndex());
|
||||
Collections.sort(childTransactions, new Comparator<RouterTransaction>() {
|
||||
@Override
|
||||
public int compare(RouterTransaction o1, RouterTransaction o2) {
|
||||
return o2.getTransactionIndex() - o1.getTransactionIndex();
|
||||
}
|
||||
});
|
||||
|
||||
for (RouterTransaction transaction : childTransactions) {
|
||||
Controller childController = transaction.controller();
|
||||
@@ -823,6 +872,27 @@ public abstract class Controller {
|
||||
}
|
||||
}
|
||||
|
||||
final void onContextUnavailable(@NonNull Context context) {
|
||||
if (isContextAvailable) {
|
||||
for (Router childRouter : childRouters) {
|
||||
childRouter.onContextUnavailable(context);
|
||||
}
|
||||
|
||||
List<LifecycleListener> listeners = new ArrayList<>(lifecycleListeners);
|
||||
for (LifecycleListener lifecycleListener : listeners) {
|
||||
lifecycleListener.preContextUnavailable(this, context);
|
||||
}
|
||||
|
||||
isContextAvailable = false;
|
||||
onContextUnavailable();
|
||||
|
||||
listeners = new ArrayList<>(lifecycleListeners);
|
||||
for (LifecycleListener lifecycleListener : listeners) {
|
||||
lifecycleListener.postContextUnavailable(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final void executeWithRouter(@NonNull RouterRequiringFunc listener) {
|
||||
if (router != null) {
|
||||
listener.execute();
|
||||
@@ -875,20 +945,7 @@ public abstract class Controller {
|
||||
destroy(true);
|
||||
}
|
||||
|
||||
if (isContextAvailable) {
|
||||
List<LifecycleListener> listeners = new ArrayList<>(lifecycleListeners);
|
||||
for (LifecycleListener lifecycleListener : listeners) {
|
||||
lifecycleListener.preContextUnavailable(this, activity);
|
||||
}
|
||||
|
||||
isContextAvailable = false;
|
||||
onContextUnavailable();
|
||||
|
||||
listeners = new ArrayList<>(lifecycleListeners);
|
||||
for (LifecycleListener lifecycleListener : listeners) {
|
||||
lifecycleListener.postContextUnavailable(this);
|
||||
}
|
||||
}
|
||||
onContextUnavailable(activity);
|
||||
}
|
||||
|
||||
void attach(@NonNull View view) {
|
||||
@@ -987,7 +1044,11 @@ public abstract class Controller {
|
||||
|
||||
onDestroyView(view);
|
||||
|
||||
viewAttachHandler.unregisterAttachListener(view);
|
||||
// viewAttachHandler may be null iff the controller was popped before we got here
|
||||
if (viewAttachHandler != null) {
|
||||
viewAttachHandler.unregisterAttachListener(view);
|
||||
}
|
||||
|
||||
viewAttachHandler = null;
|
||||
viewIsAttached = false;
|
||||
|
||||
@@ -1036,33 +1097,35 @@ public abstract class Controller {
|
||||
|
||||
restoreViewState(view);
|
||||
|
||||
viewAttachHandler = new ViewAttachHandler(new ViewAttachListener() {
|
||||
@Override
|
||||
public void onAttached() {
|
||||
viewIsAttached = true;
|
||||
viewWasDetached = false;
|
||||
attach(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetached(boolean fromActivityStop) {
|
||||
viewIsAttached = false;
|
||||
viewWasDetached = true;
|
||||
|
||||
if (!isDetachFrozen) {
|
||||
detach(view, false, fromActivityStop);
|
||||
if (!isBeingDestroyed) {
|
||||
viewAttachHandler = new ViewAttachHandler(new ViewAttachListener() {
|
||||
@Override
|
||||
public void onAttached() {
|
||||
viewIsAttached = true;
|
||||
viewWasDetached = false;
|
||||
attach(view);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewDetachAfterStop() {
|
||||
if (!isDetachFrozen) {
|
||||
detach(view, false, false);
|
||||
@Override
|
||||
public void onDetached(boolean fromActivityStop) {
|
||||
viewIsAttached = false;
|
||||
viewWasDetached = true;
|
||||
|
||||
if (!isDetachFrozen) {
|
||||
detach(view, false, fromActivityStop);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
viewAttachHandler.listenForAttach(view);
|
||||
} else if (retainViewMode == RetainViewMode.RETAIN_DETACH) {
|
||||
|
||||
@Override
|
||||
public void onViewDetachAfterStop() {
|
||||
if (!isDetachFrozen) {
|
||||
detach(view, false, false);
|
||||
}
|
||||
}
|
||||
});
|
||||
viewAttachHandler.listenForAttach(view);
|
||||
}
|
||||
} else {
|
||||
restoreChildControllerHosts();
|
||||
}
|
||||
|
||||
@@ -1074,8 +1137,8 @@ public abstract class Controller {
|
||||
if (!childRouter.hasHost()) {
|
||||
View containerView = view.findViewById(childRouter.getHostId());
|
||||
|
||||
if (containerView != null && containerView instanceof ViewGroup) {
|
||||
childRouter.setHost(this, (ViewGroup) containerView);
|
||||
if (containerView instanceof ViewGroup) {
|
||||
childRouter.setHostContainer(this, (ViewGroup) containerView);
|
||||
childRouter.rebindIfNeeded();
|
||||
}
|
||||
}
|
||||
@@ -1084,18 +1147,7 @@ public abstract class Controller {
|
||||
|
||||
private void performDestroy() {
|
||||
if (isContextAvailable) {
|
||||
List<LifecycleListener> listeners = new ArrayList<>(lifecycleListeners);
|
||||
for (LifecycleListener lifecycleListener : listeners) {
|
||||
lifecycleListener.preContextUnavailable(this, getActivity());
|
||||
}
|
||||
|
||||
isContextAvailable = false;
|
||||
onContextUnavailable();
|
||||
|
||||
listeners = new ArrayList<>(lifecycleListeners);
|
||||
for (LifecycleListener lifecycleListener : listeners) {
|
||||
lifecycleListener.postContextUnavailable(this);
|
||||
}
|
||||
onContextUnavailable(getActivity());
|
||||
}
|
||||
|
||||
if (!destroyed) {
|
||||
@@ -1234,6 +1286,7 @@ public abstract class Controller {
|
||||
List<Bundle> childBundles = savedInstanceState.getParcelableArrayList(KEY_CHILD_ROUTERS);
|
||||
for (Bundle childBundle : childBundles) {
|
||||
ControllerHostedRouter childRouter = new ControllerHostedRouter();
|
||||
childRouter.setHostController(this);
|
||||
childRouter.restoreInstanceState(childBundle);
|
||||
childRouters.add(childRouter);
|
||||
}
|
||||
@@ -1382,66 +1435,66 @@ public abstract class Controller {
|
||||
/**
|
||||
* Allows external classes to listen for lifecycle events in a Controller
|
||||
*/
|
||||
public interface LifecycleListener {
|
||||
public static abstract class LifecycleListener {
|
||||
|
||||
default void onChangeStart(@NonNull Controller controller, @NonNull ControllerChangeHandler changeHandler, @NonNull ControllerChangeType changeType) {
|
||||
public void onChangeStart(@NonNull Controller controller, @NonNull ControllerChangeHandler changeHandler, @NonNull ControllerChangeType changeType) {
|
||||
}
|
||||
|
||||
default void onChangeEnd(@NonNull Controller controller, @NonNull ControllerChangeHandler changeHandler, @NonNull ControllerChangeType changeType) {
|
||||
public void onChangeEnd(@NonNull Controller controller, @NonNull ControllerChangeHandler changeHandler, @NonNull ControllerChangeType changeType) {
|
||||
}
|
||||
|
||||
default void preCreateView(@NonNull Controller controller) {
|
||||
public void preCreateView(@NonNull Controller controller) {
|
||||
}
|
||||
|
||||
default void postCreateView(@NonNull Controller controller, @NonNull View view) {
|
||||
public void postCreateView(@NonNull Controller controller, @NonNull View view) {
|
||||
}
|
||||
|
||||
default void preAttach(@NonNull Controller controller, @NonNull View view) {
|
||||
public void preAttach(@NonNull Controller controller, @NonNull View view) {
|
||||
}
|
||||
|
||||
default void postAttach(@NonNull Controller controller, @NonNull View view) {
|
||||
public void postAttach(@NonNull Controller controller, @NonNull View view) {
|
||||
}
|
||||
|
||||
default void preDetach(@NonNull Controller controller, @NonNull View view) {
|
||||
public void preDetach(@NonNull Controller controller, @NonNull View view) {
|
||||
}
|
||||
|
||||
default void postDetach(@NonNull Controller controller, @NonNull View view) {
|
||||
public void postDetach(@NonNull Controller controller, @NonNull View view) {
|
||||
}
|
||||
|
||||
default void preDestroyView(@NonNull Controller controller, @NonNull View view) {
|
||||
public void preDestroyView(@NonNull Controller controller, @NonNull View view) {
|
||||
}
|
||||
|
||||
default void postDestroyView(@NonNull Controller controller) {
|
||||
public void postDestroyView(@NonNull Controller controller) {
|
||||
}
|
||||
|
||||
default void preDestroy(@NonNull Controller controller) {
|
||||
public void preDestroy(@NonNull Controller controller) {
|
||||
}
|
||||
|
||||
default void postDestroy(@NonNull Controller controller) {
|
||||
public void postDestroy(@NonNull Controller controller) {
|
||||
}
|
||||
|
||||
default void preContextAvailable(@NonNull Controller controller) {
|
||||
public void preContextAvailable(@NonNull Controller controller) {
|
||||
}
|
||||
|
||||
default void postContextAvailable(@NonNull Controller controller, @NonNull Context context) {
|
||||
public void postContextAvailable(@NonNull Controller controller, @NonNull Context context) {
|
||||
}
|
||||
|
||||
default void preContextUnavailable(@NonNull Controller controller, @NonNull Context context) {
|
||||
public void preContextUnavailable(@NonNull Controller controller, @NonNull Context context) {
|
||||
}
|
||||
|
||||
default void postContextUnavailable(@NonNull Controller controller) {
|
||||
public void postContextUnavailable(@NonNull Controller controller) {
|
||||
}
|
||||
|
||||
default void onSaveInstanceState(@NonNull Controller controller, @NonNull Bundle outState) {
|
||||
public void onSaveInstanceState(@NonNull Controller controller, @NonNull Bundle outState) {
|
||||
}
|
||||
|
||||
default void onRestoreInstanceState(@NonNull Controller controller, @NonNull Bundle savedInstanceState) {
|
||||
public void onRestoreInstanceState(@NonNull Controller controller, @NonNull Bundle savedInstanceState) {
|
||||
}
|
||||
|
||||
default void onSaveViewState(@NonNull Controller controller, @NonNull Bundle outState) {
|
||||
public void onSaveViewState(@NonNull Controller controller, @NonNull Bundle outState) {
|
||||
}
|
||||
|
||||
default void onRestoreViewState(@NonNull Controller controller, @NonNull Bundle savedViewState) {
|
||||
public void onRestoreViewState(@NonNull Controller controller, @NonNull Bundle savedViewState) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -206,29 +206,32 @@ public abstract class ControllerChangeHandler {
|
||||
fromView = null;
|
||||
}
|
||||
|
||||
handler.performChange(container, fromView, toView, isPush, () -> {
|
||||
if (from != null) {
|
||||
from.changeEnded(handler, fromChangeType);
|
||||
}
|
||||
|
||||
if (to != null) {
|
||||
inProgressChangeHandlers.remove(to.getInstanceId());
|
||||
to.changeEnded(handler, toChangeType);
|
||||
}
|
||||
|
||||
for (ControllerChangeListener listener : listeners) {
|
||||
listener.onChangeCompleted(to, from, isPush, container, handler);
|
||||
}
|
||||
|
||||
if (handler.forceRemoveViewOnPush && fromView != null) {
|
||||
ViewParent fromParent = fromView.getParent();
|
||||
if (fromParent != null && fromParent instanceof ViewGroup) {
|
||||
((ViewGroup) fromParent).removeView(fromView);
|
||||
handler.performChange(container, fromView, toView, isPush, new ControllerChangeCompletedListener() {
|
||||
@Override
|
||||
public void onChangeCompleted() {
|
||||
if (from != null) {
|
||||
from.changeEnded(handler, fromChangeType);
|
||||
}
|
||||
}
|
||||
|
||||
if (handler.removesFromViewOnPush() && from != null) {
|
||||
from.setNeedsAttach(false);
|
||||
if (to != null) {
|
||||
inProgressChangeHandlers.remove(to.getInstanceId());
|
||||
to.changeEnded(handler, toChangeType);
|
||||
}
|
||||
|
||||
for (ControllerChangeListener listener : listeners) {
|
||||
listener.onChangeCompleted(to, from, isPush, container, handler);
|
||||
}
|
||||
|
||||
if (handler.forceRemoveViewOnPush && fromView != null) {
|
||||
ViewParent fromParent = fromView.getParent();
|
||||
if (fromParent != null && fromParent instanceof ViewGroup) {
|
||||
((ViewGroup) fromParent).removeView(fromView);
|
||||
}
|
||||
}
|
||||
|
||||
if (handler.removesFromViewOnPush() && from != null) {
|
||||
from.setNeedsAttach(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -258,8 +261,7 @@ public abstract class ControllerChangeHandler {
|
||||
* @param container The containing ViewGroup
|
||||
* @param handler The change handler being used.
|
||||
*/
|
||||
default void onChangeStarted(@Nullable Controller to, @Nullable Controller from, boolean isPush, @NonNull ViewGroup container, @NonNull 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
|
||||
@@ -270,8 +272,7 @@ public abstract class ControllerChangeHandler {
|
||||
* @param container The containing ViewGroup
|
||||
* @param handler The change handler that was used.
|
||||
*/
|
||||
default void onChangeCompleted(@Nullable Controller to, @Nullable Controller from, boolean isPush, @NonNull ViewGroup container, @NonNull ControllerChangeHandler handler) {
|
||||
}
|
||||
void onChangeCompleted(@Nullable Controller to, @Nullable Controller from, boolean isPush, @NonNull ViewGroup container, @NonNull ControllerChangeHandler handler);
|
||||
}
|
||||
|
||||
static class ChangeTransaction {
|
||||
|
||||
@@ -5,6 +5,7 @@ import android.content.Intent;
|
||||
import android.content.IntentSender;
|
||||
import android.content.IntentSender.SendIntentException;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.IdRes;
|
||||
@@ -22,26 +23,38 @@ class ControllerHostedRouter extends Router {
|
||||
|
||||
private final String KEY_HOST_ID = "ControllerHostedRouter.hostId";
|
||||
private final String KEY_TAG = "ControllerHostedRouter.tag";
|
||||
private final String KEY_BOUND_TO_CONTAINER = "ControllerHostedRouter.boundToContainer";
|
||||
|
||||
private Controller hostController;
|
||||
|
||||
@IdRes private int hostId;
|
||||
private String tag;
|
||||
private boolean isDetachFrozen;
|
||||
private boolean boundToContainer;
|
||||
|
||||
ControllerHostedRouter() { }
|
||||
|
||||
ControllerHostedRouter(int hostId, @Nullable String tag) {
|
||||
ControllerHostedRouter(int hostId, @Nullable String tag, boolean boundToContainer) {
|
||||
if (!boundToContainer && tag == null) {
|
||||
throw new IllegalStateException("ControllerHostedRouter can't be created without a tag if not bounded to its container");
|
||||
}
|
||||
this.hostId = hostId;
|
||||
this.tag = tag;
|
||||
this.boundToContainer = boundToContainer;
|
||||
}
|
||||
|
||||
final void setHost(@NonNull Controller controller, @NonNull ViewGroup container) {
|
||||
final void setHostController(@NonNull Controller controller) {
|
||||
if (hostController == null) {
|
||||
hostController = controller;
|
||||
}
|
||||
}
|
||||
|
||||
final void setHostContainer(@NonNull Controller controller, @NonNull ViewGroup container) {
|
||||
if (hostController != controller || this.container != container) {
|
||||
removeHost();
|
||||
|
||||
if (container instanceof ControllerChangeListener) {
|
||||
addChangeListener((ControllerChangeListener)container);
|
||||
addChangeListener((ControllerChangeListener) container);
|
||||
}
|
||||
|
||||
hostController = controller;
|
||||
@@ -57,7 +70,7 @@ class ControllerHostedRouter extends Router {
|
||||
|
||||
final void removeHost() {
|
||||
if (container != null && container instanceof ControllerChangeListener) {
|
||||
removeChangeListener((ControllerChangeListener)container);
|
||||
removeChangeListener((ControllerChangeListener) container);
|
||||
}
|
||||
|
||||
final List<Controller> controllersToDestroy = new ArrayList<>(destroyingControllers);
|
||||
@@ -73,7 +86,6 @@ class ControllerHostedRouter extends Router {
|
||||
}
|
||||
|
||||
prepareForContainerRemoval();
|
||||
hostController = null;
|
||||
container = null;
|
||||
}
|
||||
|
||||
@@ -108,14 +120,29 @@ class ControllerHostedRouter extends Router {
|
||||
super.setBackstack(newBackstack, changeHandler);
|
||||
}
|
||||
|
||||
@Override
|
||||
void performControllerChange(@Nullable RouterTransaction to, @Nullable RouterTransaction from, boolean isPush) {
|
||||
super.performControllerChange(to, from, isPush);
|
||||
|
||||
// If we're pushing a transaction that will detach controllers to an unattached child
|
||||
// router, we need mark all other controllers as NOT needing to be reattached.
|
||||
if (to != null && !hostController.isAttached()) {
|
||||
if (to.pushChangeHandler() == null || to.pushChangeHandler().removesFromViewOnPush()) {
|
||||
for (RouterTransaction transaction : backstack) {
|
||||
transaction.controller().setNeedsAttach(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override @Nullable
|
||||
public Activity getActivity() {
|
||||
return hostController != null ? hostController.getActivity() : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityDestroyed(@NonNull Activity activity) {
|
||||
super.onActivityDestroyed(activity);
|
||||
public void onActivityDestroyed(@NonNull Activity activity, boolean isConfigurationChange) {
|
||||
super.onActivityDestroyed(activity, isConfigurationChange);
|
||||
|
||||
removeHost();
|
||||
}
|
||||
@@ -185,7 +212,7 @@ class ControllerHostedRouter extends Router {
|
||||
|
||||
@Override
|
||||
boolean hasHost() {
|
||||
return hostController != null;
|
||||
return hostController != null && container != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -193,6 +220,7 @@ class ControllerHostedRouter extends Router {
|
||||
super.saveInstanceState(outState);
|
||||
|
||||
outState.putInt(KEY_HOST_ID, hostId);
|
||||
outState.putBoolean(KEY_BOUND_TO_CONTAINER, boundToContainer);
|
||||
outState.putString(KEY_TAG, tag);
|
||||
}
|
||||
|
||||
@@ -201,22 +229,32 @@ class ControllerHostedRouter extends Router {
|
||||
super.restoreInstanceState(savedInstanceState);
|
||||
|
||||
hostId = savedInstanceState.getInt(KEY_HOST_ID);
|
||||
boundToContainer = savedInstanceState.getBoolean(KEY_BOUND_TO_CONTAINER);
|
||||
tag = savedInstanceState.getString(KEY_TAG);
|
||||
}
|
||||
|
||||
@Override
|
||||
void setControllerRouter(@NonNull Controller controller) {
|
||||
void setRouterOnController(@NonNull Controller controller) {
|
||||
controller.setParentController(hostController);
|
||||
super.setControllerRouter(controller);
|
||||
super.setRouterOnController(controller);
|
||||
}
|
||||
|
||||
int getHostId() {
|
||||
return hostId;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
String getTag() {
|
||||
return tag;
|
||||
boolean matches(int hostId, @Nullable String tag) {
|
||||
if (!boundToContainer && container == null) {
|
||||
if (this.tag == null) {
|
||||
throw new IllegalStateException("Host ID can't be variable with a null tag");
|
||||
}
|
||||
if (this.tag.equals(tag)) {
|
||||
this.hostId = hostId;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return this.hostId == hostId && TextUtils.equals(tag, this.tag);
|
||||
}
|
||||
|
||||
@Override @NonNull
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.bluelinelabs.conductor;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentSender;
|
||||
import android.os.Bundle;
|
||||
@@ -34,14 +35,14 @@ import java.util.List;
|
||||
public abstract class Router {
|
||||
|
||||
private static final String KEY_BACKSTACK = "Router.backstack";
|
||||
private static final String KEY_POPS_LAST_VIEW = "Router.popsLastView";
|
||||
private static final String KEY_POP_ROOT_CONTROLLER_MODE = "Router.popRootControllerMode";
|
||||
|
||||
final Backstack backstack = new Backstack();
|
||||
private final List<ControllerChangeListener> changeListeners = new ArrayList<>();
|
||||
private final List<ChangeTransaction> pendingControllerChanges = new ArrayList<>();
|
||||
final List<Controller> destroyingControllers = new ArrayList<>();
|
||||
|
||||
private boolean popsLastView = false;
|
||||
private PopRootControllerMode popRootControllerMode = PopRootControllerMode.POP_ROOT_CONTROLLER_BUT_NOT_VIEW;
|
||||
boolean containerFullyAttached = false;
|
||||
boolean isActivityStopped = false;
|
||||
|
||||
@@ -94,7 +95,7 @@ public abstract class Router {
|
||||
//noinspection ConstantConditions
|
||||
if (backstack.peek().controller().handleBack()) {
|
||||
return true;
|
||||
} else if (popCurrentController()) {
|
||||
} else if ((backstack.getSize() > 1 || popRootControllerMode != PopRootControllerMode.NEVER) && popCurrentController()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -161,7 +162,7 @@ public abstract class Router {
|
||||
}
|
||||
}
|
||||
|
||||
if (popsLastView) {
|
||||
if (popRootControllerMode == PopRootControllerMode.POP_ROOT_CONTROLLER_AND_VIEW) {
|
||||
return topTransaction != null;
|
||||
} else {
|
||||
return !backstack.isEmpty();
|
||||
@@ -205,7 +206,7 @@ public abstract class Router {
|
||||
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())) {
|
||||
for (RouterTransaction visibleTransaction : getVisibleTransactions(backstack.iterator(), true)) {
|
||||
performControllerChange(null, visibleTransaction, true, handler);
|
||||
}
|
||||
}
|
||||
@@ -220,7 +221,7 @@ public abstract class Router {
|
||||
}
|
||||
|
||||
void destroy(boolean popViews) {
|
||||
popsLastView = true;
|
||||
popRootControllerMode = PopRootControllerMode.POP_ROOT_CONTROLLER_AND_VIEW;
|
||||
final List<RouterTransaction> poppedControllers = backstack.popAll();
|
||||
trackDestroyingControllers(poppedControllers);
|
||||
|
||||
@@ -250,10 +251,24 @@ public abstract class Router {
|
||||
* If set to true, this router will handle back presses by performing a change handler on the last controller and view
|
||||
* 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.
|
||||
*
|
||||
* Note: This method has been deprecated and should be replaced with setPopRootControllerMode.
|
||||
*/
|
||||
@NonNull
|
||||
@Deprecated
|
||||
public Router setPopsLastView(boolean popsLastView) {
|
||||
this.popsLastView = popsLastView;
|
||||
this.popRootControllerMode = popsLastView ? PopRootControllerMode.POP_ROOT_CONTROLLER_AND_VIEW : PopRootControllerMode.POP_ROOT_CONTROLLER_BUT_NOT_VIEW;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the method this router will use to handle back presses when there is only one controller left in the backstack.
|
||||
* Defaults to POP_ROOT_CONTROLLER_LEAVING_VIEW so that the developer can either finish its containing Activity or
|
||||
* otherwise hide its parent view without any strange artifacting.
|
||||
*/
|
||||
@NonNull
|
||||
public Router setPopRootControllerMode(@NonNull PopRootControllerMode popRootControllerMode) {
|
||||
this.popRootControllerMode = popRootControllerMode;
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -280,7 +295,7 @@ public abstract class Router {
|
||||
public boolean popToRoot(@Nullable ControllerChangeHandler changeHandler) {
|
||||
ThreadUtils.ensureMainThread();
|
||||
|
||||
if (backstack.size() > 1) {
|
||||
if (backstack.getSize() > 1) {
|
||||
//noinspection ConstantConditions
|
||||
popToTransaction(backstack.root(), changeHandler);
|
||||
return true;
|
||||
@@ -375,7 +390,7 @@ public abstract class Router {
|
||||
*/
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public int getBackstackSize() {
|
||||
return backstack.size();
|
||||
return backstack.getSize();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -383,7 +398,7 @@ public abstract class Router {
|
||||
*/
|
||||
@NonNull
|
||||
public List<RouterTransaction> getBackstack() {
|
||||
List<RouterTransaction> list = new ArrayList<>(backstack.size());
|
||||
List<RouterTransaction> list = new ArrayList<>(backstack.getSize());
|
||||
Iterator<RouterTransaction> backstackIterator = backstack.reverseIterator();
|
||||
while (backstackIterator.hasNext()) {
|
||||
list.add(backstackIterator.next());
|
||||
@@ -404,7 +419,7 @@ public abstract class Router {
|
||||
ThreadUtils.ensureMainThread();
|
||||
|
||||
List<RouterTransaction> oldTransactions = getBackstack();
|
||||
List<RouterTransaction> oldVisibleTransactions = getVisibleTransactions(backstack.iterator());
|
||||
List<RouterTransaction> oldVisibleTransactions = getVisibleTransactions(backstack.iterator(), false);
|
||||
|
||||
removeAllExceptVisibleAndUnowned();
|
||||
ensureOrderedTransactionIndices(newBackstack);
|
||||
@@ -434,13 +449,13 @@ public abstract class Router {
|
||||
while (backstackIterator.hasNext()) {
|
||||
RouterTransaction transaction = backstackIterator.next();
|
||||
transaction.onAttachedToRouter();
|
||||
setControllerRouter(transaction.controller());
|
||||
setRouterOnController(transaction.controller());
|
||||
}
|
||||
|
||||
if (newBackstack.size() > 0) {
|
||||
List<RouterTransaction> reverseNewBackstack = new ArrayList<>(newBackstack);
|
||||
Collections.reverse(reverseNewBackstack);
|
||||
List<RouterTransaction> newVisibleTransactions = getVisibleTransactions(reverseNewBackstack.iterator());
|
||||
List<RouterTransaction> newVisibleTransactions = getVisibleTransactions(reverseNewBackstack.iterator(), false);
|
||||
boolean newRootRequiresPush = !(newVisibleTransactions.size() > 0 && oldTransactions.contains(newVisibleTransactions.get(0)));
|
||||
|
||||
boolean visibleTransactionsChanged = !backstacksAreEqual(newVisibleTransactions, oldVisibleTransactions);
|
||||
@@ -464,7 +479,10 @@ public abstract class Router {
|
||||
ControllerChangeHandler localHandler = changeHandler != null ? changeHandler.copy() : new SimpleSwapChangeHandler();
|
||||
localHandler.setForceRemoveViewOnPush(true);
|
||||
ControllerChangeHandler.completeHandlerImmediately(transaction.controller().getInstanceId());
|
||||
performControllerChange(null, transaction, newRootRequiresPush, localHandler);
|
||||
|
||||
if (transaction.controller().view != null) {
|
||||
performControllerChange(null, transaction, newRootRequiresPush, localHandler);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -549,7 +567,7 @@ public abstract class Router {
|
||||
if (transaction.controller().getNeedsAttach()) {
|
||||
performControllerChange(transaction, null, true, new SimpleSwapChangeHandler(false));
|
||||
} else {
|
||||
setControllerRouter(transaction.controller());
|
||||
setRouterOnController(transaction.controller());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -605,7 +623,7 @@ public abstract class Router {
|
||||
isActivityStopped = true;
|
||||
}
|
||||
|
||||
public void onActivityDestroyed(@NonNull Activity activity) {
|
||||
public void onActivityDestroyed(@NonNull Activity activity, boolean isConfigurationChange) {
|
||||
prepareForContainerRemoval();
|
||||
changeListeners.clear();
|
||||
|
||||
@@ -613,7 +631,7 @@ public abstract class Router {
|
||||
transaction.controller().activityDestroyed(activity);
|
||||
|
||||
for (Router childRouter : transaction.controller().getChildRouters()) {
|
||||
childRouter.onActivityDestroyed(activity);
|
||||
childRouter.onActivityDestroyed(activity, isConfigurationChange);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -622,7 +640,7 @@ public abstract class Router {
|
||||
controller.activityDestroyed(activity);
|
||||
|
||||
for (Router childRouter : controller.getChildRouters()) {
|
||||
childRouter.onActivityDestroyed(activity);
|
||||
childRouter.onActivityDestroyed(activity, isConfigurationChange);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -646,18 +664,18 @@ public abstract class Router {
|
||||
backstack.saveInstanceState(backstackState);
|
||||
|
||||
outState.putParcelable(KEY_BACKSTACK, backstackState);
|
||||
outState.putBoolean(KEY_POPS_LAST_VIEW, popsLastView);
|
||||
outState.putInt(KEY_POP_ROOT_CONTROLLER_MODE, popRootControllerMode.ordinal());
|
||||
}
|
||||
|
||||
public void restoreInstanceState(@NonNull Bundle savedInstanceState) {
|
||||
Bundle backstackBundle = savedInstanceState.getParcelable(KEY_BACKSTACK);
|
||||
//noinspection ConstantConditions
|
||||
backstack.restoreInstanceState(backstackBundle);
|
||||
popsLastView = savedInstanceState.getBoolean(KEY_POPS_LAST_VIEW);
|
||||
popRootControllerMode = PopRootControllerMode.values()[savedInstanceState.getInt(KEY_POP_ROOT_CONTROLLER_MODE)];
|
||||
|
||||
Iterator<RouterTransaction> backstackIterator = backstack.reverseIterator();
|
||||
while (backstackIterator.hasNext()) {
|
||||
setControllerRouter(backstackIterator.next().controller());
|
||||
setRouterOnController(backstackIterator.next().controller());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -697,7 +715,7 @@ public abstract class Router {
|
||||
}
|
||||
|
||||
private void popToTransaction(@NonNull RouterTransaction transaction, @Nullable ControllerChangeHandler changeHandler) {
|
||||
if (backstack.size() > 0) {
|
||||
if (backstack.getSize() > 0) {
|
||||
RouterTransaction topTransaction = backstack.peek();
|
||||
|
||||
List<RouterTransaction> updatedBackstack = new ArrayList<>();
|
||||
@@ -720,7 +738,12 @@ public abstract class Router {
|
||||
}
|
||||
|
||||
void watchContainerAttach() {
|
||||
container.post(() -> containerFullyAttached = true);
|
||||
container.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
containerFullyAttached = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void prepareForContainerRemoval() {
|
||||
@@ -737,9 +760,18 @@ public abstract class Router {
|
||||
}
|
||||
}
|
||||
|
||||
void onContextUnavailable(@NonNull Context context) {
|
||||
for (RouterTransaction transaction : backstack) {
|
||||
transaction.controller().onContextUnavailable(context);
|
||||
}
|
||||
for (Controller controller : destroyingControllers) {
|
||||
controller.onContextUnavailable(context);
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
final List<Controller> getControllers() {
|
||||
List<Controller> controllers = new ArrayList<>(backstack.size());
|
||||
List<Controller> controllers = new ArrayList<>(backstack.getSize());
|
||||
|
||||
Iterator<RouterTransaction> backstackIterator = backstack.reverseIterator();
|
||||
while (backstackIterator.hasNext()) {
|
||||
@@ -759,7 +791,7 @@ public abstract class Router {
|
||||
return null;
|
||||
}
|
||||
|
||||
private void performControllerChange(@Nullable RouterTransaction to, @Nullable RouterTransaction from, boolean isPush) {
|
||||
void performControllerChange(@Nullable RouterTransaction to, @Nullable RouterTransaction from, boolean isPush) {
|
||||
if (isPush && to != null) {
|
||||
to.onAttachedToRouter();
|
||||
}
|
||||
@@ -784,8 +816,8 @@ public abstract class Router {
|
||||
|
||||
if (to != null) {
|
||||
to.ensureValidIndex(getTransactionIndexer());
|
||||
setControllerRouter(toController);
|
||||
} else if (backstack.size() == 0 && !popsLastView) {
|
||||
setRouterOnController(toController);
|
||||
} else if (backstack.getSize() == 0 && popRootControllerMode == PopRootControllerMode.POP_ROOT_CONTROLLER_BUT_NOT_VIEW) {
|
||||
// We're emptying out the backstack. Views get weird if you transition them out, so just no-op it. The host
|
||||
// Activity or controller should be handling this by finishing or at least hiding this view.
|
||||
changeHandler = new NoOpControllerChangeHandler();
|
||||
@@ -802,7 +834,7 @@ public abstract class Router {
|
||||
if (fromController.getView() != null) {
|
||||
fromController.detach(fromController.getView(), true, false);
|
||||
} else {
|
||||
from.controller().destroy();
|
||||
fromController.destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -829,7 +861,12 @@ public abstract class Router {
|
||||
to.setNeedsAttach(true);
|
||||
}
|
||||
pendingControllerChanges.add(transaction);
|
||||
container.post(this::performPendingControllerChanges);
|
||||
container.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
performPendingControllerChanges();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
ControllerChangeHandler.executeChange(transaction);
|
||||
}
|
||||
@@ -873,7 +910,7 @@ public abstract class Router {
|
||||
private void removeAllExceptVisibleAndUnowned() {
|
||||
List<View> views = new ArrayList<>();
|
||||
|
||||
for (RouterTransaction transaction : getVisibleTransactions(backstack.iterator())) {
|
||||
for (RouterTransaction transaction : getVisibleTransactions(backstack.iterator(), false)) {
|
||||
if (transaction.controller().getView() != null) {
|
||||
views.add(transaction.controller().getView());
|
||||
}
|
||||
@@ -933,14 +970,20 @@ public abstract class Router {
|
||||
}
|
||||
}
|
||||
|
||||
private List<RouterTransaction> getVisibleTransactions(@NonNull Iterator<RouterTransaction> backstackIterator) {
|
||||
private List<RouterTransaction> getVisibleTransactions(@NonNull Iterator<RouterTransaction> backstackIterator, boolean onlyTop) {
|
||||
boolean visible = true;
|
||||
|
||||
List<RouterTransaction> transactions = new ArrayList<>();
|
||||
while (backstackIterator.hasNext()) {
|
||||
RouterTransaction transaction = backstackIterator.next();
|
||||
transactions.add(transaction);
|
||||
|
||||
//noinspection ConstantConditions
|
||||
if (transaction.pushChangeHandler() == null || transaction.pushChangeHandler().removesFromViewOnPush()) {
|
||||
if (visible) {
|
||||
transactions.add(transaction);
|
||||
}
|
||||
|
||||
visible = transaction.pushChangeHandler() != null && !transaction.pushChangeHandler().removesFromViewOnPush();
|
||||
|
||||
if (onlyTop && !visible) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -963,7 +1006,7 @@ public abstract class Router {
|
||||
return true;
|
||||
}
|
||||
|
||||
void setControllerRouter(@NonNull Controller controller) {
|
||||
void setRouterOnController(@NonNull Controller controller) {
|
||||
controller.setRouter(this);
|
||||
controller.onContextAvailable();
|
||||
}
|
||||
@@ -982,4 +1025,25 @@ public abstract class Router {
|
||||
@NonNull abstract Router getRootRouter();
|
||||
@NonNull abstract TransactionIndexer getTransactionIndexer();
|
||||
|
||||
/**
|
||||
* Defines the way a Router will handle back button or pop events when there is only one controller
|
||||
* left in the backstack.
|
||||
*/
|
||||
public enum PopRootControllerMode {
|
||||
/**
|
||||
* The Router will not pop the final controller left on the backstack when the back button is pressed
|
||||
* or when pop events are called. This mode should generally be used for Activity-hosted routers.
|
||||
*/
|
||||
NEVER,
|
||||
/**
|
||||
* The Router will pop the final controller, but will leave its view in the hierarchy. This is useful
|
||||
* when the developer wishes to allow its containing Activity to finish or otherwise hide its parent
|
||||
* view without any strange artifacting.
|
||||
*/
|
||||
POP_ROOT_CONTROLLER_BUT_NOT_VIEW,
|
||||
/**
|
||||
* The Router will pop both the final controller as well as its view.
|
||||
*/
|
||||
POP_ROOT_CONTROLLER_AND_VIEW
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,8 @@ private constructor(
|
||||
private var pushControllerChangeHandler: ControllerChangeHandler? = null,
|
||||
private var popControllerChangeHandler: ControllerChangeHandler? = null,
|
||||
private var attachedToRouter: Boolean = false,
|
||||
@RestrictTo(LIBRARY)
|
||||
@get:RestrictTo(LIBRARY)
|
||||
@set:RestrictTo(LIBRARY)
|
||||
var transactionIndex: Int = INVALID_INDEX
|
||||
) {
|
||||
|
||||
|
||||
@@ -101,7 +101,7 @@ public class LifecycleHandler extends Fragment implements ActivityLifecycleCallb
|
||||
|
||||
@NonNull
|
||||
public List<Router> getRouters() {
|
||||
return new ArrayList<>(routerMap.values());
|
||||
return new ArrayList<Router>(routerMap.values());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -133,13 +133,13 @@ public class LifecycleHandler extends Fragment implements ActivityLifecycleCallb
|
||||
|
||||
if (savedInstanceState != null) {
|
||||
StringSparseArrayParceler permissionParcel = savedInstanceState.getParcelable(KEY_PERMISSION_REQUEST_CODES);
|
||||
permissionRequestMap = permissionParcel != null ? permissionParcel.getStringSparseArray() : new SparseArray<>();
|
||||
permissionRequestMap = permissionParcel != null ? permissionParcel.getStringSparseArray() : new SparseArray<String>();
|
||||
|
||||
StringSparseArrayParceler activityParcel = savedInstanceState.getParcelable(KEY_ACTIVITY_REQUEST_CODES);
|
||||
activityRequestMap = activityParcel != null ? activityParcel.getStringSparseArray() : new SparseArray<>();
|
||||
activityRequestMap = activityParcel != null ? activityParcel.getStringSparseArray() : new SparseArray<String>();
|
||||
|
||||
ArrayList<PendingPermissionRequest> pendingRequests = savedInstanceState.getParcelableArrayList(KEY_PENDING_PERMISSION_REQUESTS);
|
||||
pendingPermissionRequests = pendingRequests != null ? pendingRequests : new ArrayList<>();
|
||||
pendingPermissionRequests = pendingRequests != null ? pendingRequests : new ArrayList<PendingPermissionRequest>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,13 +159,16 @@ public class LifecycleHandler extends Fragment implements ActivityLifecycleCallb
|
||||
if (activity != null) {
|
||||
activity.getApplication().unregisterActivityLifecycleCallbacks(this);
|
||||
activeLifecycleHandlers.remove(activity);
|
||||
destroyRouters();
|
||||
destroyRouters(false);
|
||||
activity = null;
|
||||
}
|
||||
|
||||
routerMap.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Activity activity) {
|
||||
this.activity = activity;
|
||||
super.onAttach(activity);
|
||||
destroyed = false;
|
||||
setAttached();
|
||||
@@ -173,6 +176,10 @@ public class LifecycleHandler extends Fragment implements ActivityLifecycleCallb
|
||||
|
||||
@Override
|
||||
public void onAttach(Context context) {
|
||||
if (context instanceof Activity) {
|
||||
this.activity = (Activity) context;
|
||||
}
|
||||
|
||||
super.onAttach(context);
|
||||
destroyed = false;
|
||||
setAttached();
|
||||
@@ -183,7 +190,10 @@ public class LifecycleHandler extends Fragment implements ActivityLifecycleCallb
|
||||
super.onDetach();
|
||||
|
||||
attached = false;
|
||||
destroyRouters();
|
||||
|
||||
if (activity != null) {
|
||||
destroyRouters(activity.isChangingConfigurations());
|
||||
}
|
||||
}
|
||||
|
||||
private void setAttached() {
|
||||
@@ -194,20 +204,20 @@ public class LifecycleHandler extends Fragment implements ActivityLifecycleCallb
|
||||
PendingPermissionRequest request = pendingPermissionRequests.remove(i);
|
||||
requestPermissions(request.instanceId, request.permissions, request.requestCode);
|
||||
}
|
||||
}
|
||||
|
||||
for (ActivityHostedRouter router : new ArrayList<>(routerMap.values())) {
|
||||
router.onContextAvailable();
|
||||
for (ActivityHostedRouter router : new ArrayList<>(routerMap.values())) {
|
||||
router.onContextAvailable();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void destroyRouters() {
|
||||
private void destroyRouters(boolean configurationChange) {
|
||||
if (!destroyed) {
|
||||
destroyed = true;
|
||||
|
||||
if (activity != null) {
|
||||
for (Router router : getRouters()) {
|
||||
router.onActivityDestroyed(activity);
|
||||
router.onActivityDestroyed(activity, configurationChange);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -319,7 +329,7 @@ public class LifecycleHandler extends Fragment implements ActivityLifecycleCallb
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
|
||||
if (this.activity == null && findInActivity(activity) == LifecycleHandler.this) {
|
||||
if (findInActivity(activity) == LifecycleHandler.this) {
|
||||
this.activity = activity;
|
||||
|
||||
for (ActivityHostedRouter router : new ArrayList<>(routerMap.values())) {
|
||||
|
||||
+164
@@ -0,0 +1,164 @@
|
||||
package com.bluelinelabs.conductor.internal
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.LifecycleRegistry
|
||||
import androidx.lifecycle.ViewTreeLifecycleOwner
|
||||
import androidx.savedstate.SavedStateRegistryController
|
||||
import androidx.savedstate.SavedStateRegistryOwner
|
||||
import androidx.savedstate.ViewTreeSavedStateRegistryOwner
|
||||
import com.bluelinelabs.conductor.Controller
|
||||
import com.bluelinelabs.conductor.ControllerChangeHandler
|
||||
import com.bluelinelabs.conductor.ControllerChangeType
|
||||
import com.bluelinelabs.conductor.R
|
||||
|
||||
/**
|
||||
* This class sets the [ViewTreeLifecycleOwner] and [ViewTreeSavedStateRegistryOwner] which is
|
||||
* necessary for Jetpack Compose. By setting these, the view state restoration and compose lifecycle
|
||||
* play together with the lifecycle of the [Controller].
|
||||
*/
|
||||
internal class OwnViewTreeLifecycleAndRegistry private constructor(
|
||||
controller: Controller
|
||||
) : LifecycleOwner, SavedStateRegistryOwner {
|
||||
|
||||
private lateinit var lifecycleRegistry: LifecycleRegistry
|
||||
private lateinit var savedStateRegistryController: SavedStateRegistryController
|
||||
|
||||
private var hasSavedState = false
|
||||
private var savedRegistryState = Bundle.EMPTY
|
||||
|
||||
init {
|
||||
controller.addLifecycleListener(object : Controller.LifecycleListener() {
|
||||
override fun preCreateView(controller: Controller) {
|
||||
hasSavedState = false
|
||||
|
||||
lifecycleRegistry = LifecycleRegistry(this@OwnViewTreeLifecycleAndRegistry)
|
||||
savedStateRegistryController = SavedStateRegistryController.create(
|
||||
this@OwnViewTreeLifecycleAndRegistry
|
||||
)
|
||||
savedStateRegistryController.performRestore(savedRegistryState)
|
||||
|
||||
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE)
|
||||
}
|
||||
|
||||
override fun postCreateView(controller: Controller, view: View) {
|
||||
/**
|
||||
* If the consumer of the library already has its own [ViewTreeLifecycleOwner] or
|
||||
* [ViewTreeSavedStateRegistryOwner] set, don't overwrite it but assume that they're doing
|
||||
* it on purpose.
|
||||
*/
|
||||
if (
|
||||
view.getTag(R.id.view_tree_lifecycle_owner) == null &&
|
||||
view.getTag(R.id.view_tree_saved_state_registry_owner) == null
|
||||
) {
|
||||
ViewTreeLifecycleOwner.set(view, this@OwnViewTreeLifecycleAndRegistry)
|
||||
ViewTreeSavedStateRegistryOwner.set(
|
||||
view,
|
||||
this@OwnViewTreeLifecycleAndRegistry
|
||||
)
|
||||
}
|
||||
|
||||
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START)
|
||||
}
|
||||
|
||||
override fun postAttach(controller: Controller, view: View) {
|
||||
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_RESUME)
|
||||
}
|
||||
|
||||
override fun onChangeEnd(
|
||||
changeController: Controller,
|
||||
changeHandler: ControllerChangeHandler,
|
||||
changeType: ControllerChangeType
|
||||
) {
|
||||
// Should only happen if pushing another controller over this one was aborted
|
||||
if (
|
||||
controller === changeController &&
|
||||
changeType.isEnter &&
|
||||
changeHandler.removesFromViewOnPush() &&
|
||||
changeController.view?.windowToken != null &&
|
||||
lifecycleRegistry.currentState == Lifecycle.State.STARTED
|
||||
) {
|
||||
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_RESUME)
|
||||
}
|
||||
}
|
||||
|
||||
// AbstractComposeView adds its own OnAttachStateChangeListener by default. Since it
|
||||
// does this on init, its detach callbacks get called before ours, which prevents us
|
||||
// from saving state in onDetach. The if statement in here should detect upcoming
|
||||
// detachment.
|
||||
override fun onChangeStart(
|
||||
changeController: Controller,
|
||||
changeHandler: ControllerChangeHandler,
|
||||
changeType: ControllerChangeType
|
||||
) {
|
||||
if (
|
||||
controller === changeController &&
|
||||
!changeType.isEnter &&
|
||||
changeHandler.removesFromViewOnPush() &&
|
||||
changeController.view != null &&
|
||||
lifecycleRegistry.currentState == Lifecycle.State.RESUMED
|
||||
) {
|
||||
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_PAUSE)
|
||||
|
||||
savedRegistryState = Bundle()
|
||||
savedStateRegistryController.performSave(savedRegistryState)
|
||||
|
||||
hasSavedState = true
|
||||
}
|
||||
}
|
||||
|
||||
override fun preDetach(controller: Controller, view: View) {
|
||||
// Should only happen if pushing this controller was aborted
|
||||
if (lifecycleRegistry.currentState == Lifecycle.State.RESUMED) {
|
||||
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_PAUSE)
|
||||
}
|
||||
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP)
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(controller: Controller, outState: Bundle) {
|
||||
outState.putBundle(KEY_SAVED_STATE, savedRegistryState)
|
||||
}
|
||||
|
||||
override fun onSaveViewState(controller: Controller, outState: Bundle) {
|
||||
if (!hasSavedState) {
|
||||
savedRegistryState = Bundle()
|
||||
savedStateRegistryController.performSave(savedRegistryState)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onRestoreInstanceState(controller: Controller, savedInstanceState: Bundle) {
|
||||
savedRegistryState = savedInstanceState.getBundle(KEY_SAVED_STATE)
|
||||
}
|
||||
|
||||
override fun preDestroyView(controller: Controller, view: View) {
|
||||
if (controller.isBeingDestroyed && controller.router.backstackSize == 0) {
|
||||
val parent = view.parent as? View
|
||||
parent?.addOnAttachStateChangeListener(object :
|
||||
View.OnAttachStateChangeListener {
|
||||
override fun onViewAttachedToWindow(v: View?) = Unit
|
||||
override fun onViewDetachedFromWindow(v: View?) {
|
||||
parent.removeOnAttachStateChangeListener(this)
|
||||
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
override fun getLifecycle() = lifecycleRegistry
|
||||
|
||||
override fun getSavedStateRegistry() = savedStateRegistryController.savedStateRegistry
|
||||
|
||||
companion object {
|
||||
private const val KEY_SAVED_STATE = "Registry.savedState"
|
||||
|
||||
fun own(target: Controller) {
|
||||
OwnViewTreeLifecycleAndRegistry(target)
|
||||
}
|
||||
}
|
||||
}
|
||||
-62
@@ -1,62 +0,0 @@
|
||||
package com.bluelinelabs.conductor.internal;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import androidx.annotation.NonNull;
|
||||
import android.util.SparseArray;
|
||||
|
||||
public class StringSparseArrayParceler implements Parcelable {
|
||||
|
||||
private final SparseArray<String> stringSparseArray;
|
||||
|
||||
public StringSparseArrayParceler(@NonNull SparseArray<String> stringSparseArray) {
|
||||
this.stringSparseArray = stringSparseArray;
|
||||
}
|
||||
|
||||
StringSparseArrayParceler(@NonNull Parcel in) {
|
||||
stringSparseArray = new SparseArray<>();
|
||||
|
||||
final int size = in.readInt();
|
||||
|
||||
for (int i = 0; i < size; i++) {
|
||||
stringSparseArray.put(in.readInt(), in.readString());
|
||||
}
|
||||
}
|
||||
|
||||
@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();
|
||||
|
||||
out.writeInt(size);
|
||||
|
||||
for (int i = 0; i < size; i++) {
|
||||
int key = stringSparseArray.keyAt(i);
|
||||
|
||||
out.writeInt(key);
|
||||
out.writeString(stringSparseArray.get(key));
|
||||
}
|
||||
}
|
||||
|
||||
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];
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
package com.bluelinelabs.conductor.internal
|
||||
|
||||
import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
import android.util.SparseArray
|
||||
|
||||
internal class StringSparseArrayParceler(val stringSparseArray: SparseArray<String>) : Parcelable {
|
||||
|
||||
override fun describeContents(): Int = 0
|
||||
|
||||
override fun writeToParcel(out: Parcel, flags: Int) {
|
||||
val size = stringSparseArray.size()
|
||||
out.writeInt(size)
|
||||
for (i in 0 until size) {
|
||||
val key = stringSparseArray.keyAt(i)
|
||||
out.writeInt(key)
|
||||
out.writeString(stringSparseArray[key])
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
@Suppress("unused")
|
||||
@JvmField
|
||||
val CREATOR: Parcelable.Creator<StringSparseArrayParceler> =
|
||||
object : Parcelable.Creator<StringSparseArrayParceler> {
|
||||
override fun createFromParcel(parcel: Parcel): StringSparseArrayParceler {
|
||||
val stringSparseArray = SparseArray<String>()
|
||||
val size = parcel.readInt()
|
||||
for (i in 0 until size) {
|
||||
stringSparseArray.put(parcel.readInt(), parcel.readString())
|
||||
}
|
||||
return StringSparseArrayParceler(stringSparseArray)
|
||||
}
|
||||
|
||||
override fun newArray(size: Int): Array<StringSparseArrayParceler?> = arrayOfNulls(size)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -40,10 +40,12 @@ public class ViewAttachHandler implements OnAttachStateChangeListener {
|
||||
}
|
||||
|
||||
rootAttached = true;
|
||||
listenForDeepestChildAttach(v, () -> {
|
||||
childrenAttached = true;
|
||||
reportAttached();
|
||||
|
||||
listenForDeepestChildAttach(v, new ChildAttachListener() {
|
||||
@Override
|
||||
public void onAttached() {
|
||||
childrenAttached = true;
|
||||
reportAttached();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -65,6 +67,7 @@ public class ViewAttachHandler implements OnAttachStateChangeListener {
|
||||
|
||||
if (childOnAttachStateChangeListener != null && view instanceof ViewGroup) {
|
||||
findDeepestChild((ViewGroup)view).removeOnAttachStateChangeListener(childOnAttachStateChangeListener);
|
||||
childOnAttachStateChangeListener = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,7 +121,7 @@ public class ViewAttachHandler implements OnAttachStateChangeListener {
|
||||
|
||||
@Override
|
||||
public void onViewAttachedToWindow(View v) {
|
||||
if (!attached) {
|
||||
if (!attached && childOnAttachStateChangeListener != null) {
|
||||
attached = true;
|
||||
attachListener.onAttached();
|
||||
v.removeOnAttachStateChangeListener(this);
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
-keepclassmembers public class * extends com.bluelinelabs.conductor.Controller {
|
||||
public <init>();
|
||||
public <init>(android.os.Bundle);
|
||||
}
|
||||
|
||||
-keepclassmembers public class * extends com.bluelinelabs.conductor.ControllerChangeHandler {
|
||||
public <init>();
|
||||
}
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
package com.bluelinelabs.conductor;
|
||||
|
||||
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;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
backstack = new Backstack();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPush() {
|
||||
assertEquals(0, backstack.size());
|
||||
backstack.push(RouterTransaction.with(new TestController()));
|
||||
assertEquals(1, backstack.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPop() {
|
||||
backstack.push(RouterTransaction.with(new TestController()));
|
||||
backstack.push(RouterTransaction.with(new TestController()));
|
||||
assertEquals(2, backstack.size());
|
||||
backstack.pop();
|
||||
assertEquals(1, backstack.size());
|
||||
backstack.pop();
|
||||
assertEquals(0, backstack.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPeek() {
|
||||
RouterTransaction transaction1 = RouterTransaction.with(new TestController());
|
||||
RouterTransaction transaction2 = RouterTransaction.with(new TestController());
|
||||
|
||||
backstack.push(transaction1);
|
||||
assertEquals(transaction1, backstack.peek());
|
||||
|
||||
backstack.push(transaction2);
|
||||
assertEquals(transaction2, backstack.peek());
|
||||
|
||||
backstack.pop();
|
||||
assertEquals(transaction1, backstack.peek());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPopTo() {
|
||||
RouterTransaction transaction1 = RouterTransaction.with(new TestController());
|
||||
RouterTransaction transaction2 = RouterTransaction.with(new TestController());
|
||||
RouterTransaction transaction3 = RouterTransaction.with(new TestController());
|
||||
|
||||
backstack.push(transaction1);
|
||||
backstack.push(transaction2);
|
||||
backstack.push(transaction3);
|
||||
|
||||
assertEquals(3, backstack.size());
|
||||
|
||||
backstack.popTo(transaction1);
|
||||
|
||||
assertEquals(1, backstack.size());
|
||||
assertEquals(transaction1, backstack.peek());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package com.bluelinelabs.conductor
|
||||
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
|
||||
class BackstackTests {
|
||||
|
||||
private val backstack = Backstack()
|
||||
|
||||
@Test
|
||||
fun testPush() {
|
||||
assertEquals(0, backstack.size.toLong())
|
||||
backstack.push(TestController().asTransaction())
|
||||
assertEquals(1, backstack.size.toLong())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testPop() {
|
||||
backstack.push(TestController().asTransaction())
|
||||
backstack.push(TestController().asTransaction())
|
||||
assertEquals(2, backstack.size.toLong())
|
||||
|
||||
backstack.pop()
|
||||
assertEquals(1, backstack.size.toLong())
|
||||
|
||||
backstack.pop()
|
||||
assertEquals(0, backstack.size.toLong())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testPeek() {
|
||||
val transaction1 = TestController().asTransaction()
|
||||
val transaction2 = TestController().asTransaction()
|
||||
|
||||
backstack.push(transaction1)
|
||||
assertEquals(transaction1, backstack.peek())
|
||||
|
||||
backstack.push(transaction2)
|
||||
assertEquals(transaction2, backstack.peek())
|
||||
|
||||
backstack.pop()
|
||||
assertEquals(transaction1, backstack.peek())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testPopTo() {
|
||||
val transaction1 = TestController().asTransaction()
|
||||
val transaction2 = TestController().asTransaction()
|
||||
val transaction3 = TestController().asTransaction()
|
||||
|
||||
backstack.push(transaction1)
|
||||
backstack.push(transaction2)
|
||||
backstack.push(transaction3)
|
||||
assertEquals(3, backstack.size.toLong())
|
||||
|
||||
backstack.popTo(transaction1)
|
||||
assertEquals(1, backstack.size.toLong())
|
||||
assertEquals(transaction1, backstack.peek())
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,6 @@ 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.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
-269
@@ -1,269 +0,0 @@
|
||||
package com.bluelinelabs.conductor;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
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 java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@Config(manifest = Config.NONE)
|
||||
public class ControllerLifecycleActivityReferenceTests {
|
||||
|
||||
private Router router;
|
||||
|
||||
private ActivityProxy activityProxy;
|
||||
|
||||
public void createActivityController(Bundle savedInstanceState, boolean includeStartAndResume) {
|
||||
activityProxy = new ActivityProxy().create(savedInstanceState);
|
||||
|
||||
if (includeStartAndResume) {
|
||||
activityProxy.start().resume();
|
||||
}
|
||||
|
||||
router = Conductor.attachRouter(activityProxy.getActivity(), activityProxy.getView(), savedInstanceState);
|
||||
router.setPopsLastView(true);
|
||||
if (!router.hasRootController()) {
|
||||
router.setRoot(RouterTransaction.with(new TestController()));
|
||||
}
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
createActivityController(null, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSingleControllerActivityOnPush() {
|
||||
Controller controller = new TestController();
|
||||
|
||||
assertNull(controller.getActivity());
|
||||
|
||||
ActivityReferencingLifecycleListener listener = new ActivityReferencingLifecycleListener();
|
||||
controller.addLifecycleListener(listener);
|
||||
|
||||
router.pushController(RouterTransaction.with(controller)
|
||||
.pushChangeHandler(MockChangeHandler.defaultHandler())
|
||||
.popChangeHandler(MockChangeHandler.defaultHandler()));
|
||||
|
||||
assertEquals(Collections.singletonList(true), listener.changeEndReferences);
|
||||
assertEquals(Collections.singletonList(true), listener.postCreateViewReferences);
|
||||
assertEquals(Collections.singletonList(true), listener.postAttachReferences);
|
||||
assertEquals(Collections.emptyList(), listener.postDetachReferences);
|
||||
assertEquals(Collections.emptyList(), listener.postDestroyViewReferences);
|
||||
assertEquals(Collections.emptyList(), listener.postDestroyReferences);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testChildControllerActivityOnPush() {
|
||||
Controller parent = new TestController();
|
||||
router.pushController(RouterTransaction.with(parent)
|
||||
.pushChangeHandler(MockChangeHandler.defaultHandler())
|
||||
.popChangeHandler(MockChangeHandler.defaultHandler()));
|
||||
|
||||
TestController child = new TestController();
|
||||
|
||||
assertNull(child.getActivity());
|
||||
|
||||
ActivityReferencingLifecycleListener listener = new ActivityReferencingLifecycleListener();
|
||||
child.addLifecycleListener(listener);
|
||||
|
||||
Router childRouter = parent.getChildRouter(parent.getView().findViewById(TestController.VIEW_ID));
|
||||
childRouter.pushController(RouterTransaction.with(child)
|
||||
.pushChangeHandler(MockChangeHandler.defaultHandler())
|
||||
.popChangeHandler(MockChangeHandler.defaultHandler()));
|
||||
|
||||
assertEquals(Collections.singletonList(true), listener.changeEndReferences);
|
||||
assertEquals(Collections.singletonList(true), listener.postCreateViewReferences);
|
||||
assertEquals(Collections.singletonList(true), listener.postAttachReferences);
|
||||
assertEquals(Collections.emptyList(), listener.postDetachReferences);
|
||||
assertEquals(Collections.emptyList(), listener.postDestroyViewReferences);
|
||||
assertEquals(Collections.emptyList(), listener.postDestroyReferences);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSingleControllerActivityOnPop() {
|
||||
Controller controller = new TestController();
|
||||
|
||||
ActivityReferencingLifecycleListener listener = new ActivityReferencingLifecycleListener();
|
||||
controller.addLifecycleListener(listener);
|
||||
|
||||
router.pushController(RouterTransaction.with(controller)
|
||||
.pushChangeHandler(MockChangeHandler.defaultHandler())
|
||||
.popChangeHandler(MockChangeHandler.defaultHandler()));
|
||||
|
||||
router.popCurrentController();
|
||||
|
||||
assertEquals(Arrays.asList(true, true), listener.changeEndReferences);
|
||||
assertEquals(Collections.singletonList(true), listener.postCreateViewReferences);
|
||||
assertEquals(Collections.singletonList(true), listener.postAttachReferences);
|
||||
assertEquals(Collections.singletonList(true), listener.postDetachReferences);
|
||||
assertEquals(Collections.singletonList(true), listener.postDestroyViewReferences);
|
||||
assertEquals(Collections.singletonList(true), listener.postDestroyReferences);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testChildControllerActivityOnPop() {
|
||||
Controller parent = new TestController();
|
||||
|
||||
router.pushController(RouterTransaction.with(parent)
|
||||
.pushChangeHandler(MockChangeHandler.defaultHandler())
|
||||
.popChangeHandler(MockChangeHandler.defaultHandler()));
|
||||
|
||||
TestController child = new TestController();
|
||||
|
||||
ActivityReferencingLifecycleListener listener = new ActivityReferencingLifecycleListener();
|
||||
child.addLifecycleListener(listener);
|
||||
|
||||
Router childRouter = parent.getChildRouter(parent.getView().findViewById(TestController.VIEW_ID));
|
||||
childRouter.setPopsLastView(true);
|
||||
childRouter.pushController(RouterTransaction.with(child)
|
||||
.pushChangeHandler(MockChangeHandler.defaultHandler())
|
||||
.popChangeHandler(MockChangeHandler.defaultHandler()));
|
||||
|
||||
childRouter.popCurrentController();
|
||||
|
||||
assertEquals(Arrays.asList(true, true), listener.changeEndReferences);
|
||||
assertEquals(Collections.singletonList(true), listener.postCreateViewReferences);
|
||||
assertEquals(Collections.singletonList(true), listener.postAttachReferences);
|
||||
assertEquals(Collections.singletonList(true), listener.postDetachReferences);
|
||||
assertEquals(Collections.singletonList(true), listener.postDestroyViewReferences);
|
||||
assertEquals(Collections.singletonList(true), listener.postDestroyReferences);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testChildControllerActivityOnParentPop() {
|
||||
Controller parent = new TestController();
|
||||
|
||||
router.pushController(RouterTransaction.with(parent)
|
||||
.pushChangeHandler(MockChangeHandler.defaultHandler())
|
||||
.popChangeHandler(MockChangeHandler.defaultHandler()));
|
||||
|
||||
TestController child = new TestController();
|
||||
|
||||
ActivityReferencingLifecycleListener listener = new ActivityReferencingLifecycleListener();
|
||||
child.addLifecycleListener(listener);
|
||||
|
||||
Router childRouter = parent.getChildRouter(parent.getView().findViewById(TestController.VIEW_ID));
|
||||
childRouter.setPopsLastView(true);
|
||||
childRouter.pushController(RouterTransaction.with(child)
|
||||
.pushChangeHandler(MockChangeHandler.defaultHandler())
|
||||
.popChangeHandler(MockChangeHandler.defaultHandler()));
|
||||
|
||||
router.popCurrentController();
|
||||
|
||||
assertEquals(Collections.singletonList(true), listener.changeEndReferences);
|
||||
assertEquals(Collections.singletonList(true), listener.postCreateViewReferences);
|
||||
assertEquals(Collections.singletonList(true), listener.postAttachReferences);
|
||||
assertEquals(Collections.singletonList(true), listener.postDetachReferences);
|
||||
assertEquals(Collections.singletonList(true), listener.postDestroyViewReferences);
|
||||
assertEquals(Collections.singletonList(true), listener.postDestroyReferences);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSingleControllerActivityOnDestroy() {
|
||||
Controller controller = new TestController();
|
||||
|
||||
ActivityReferencingLifecycleListener listener = new ActivityReferencingLifecycleListener();
|
||||
controller.addLifecycleListener(listener);
|
||||
|
||||
router.pushController(RouterTransaction.with(controller)
|
||||
.pushChangeHandler(MockChangeHandler.defaultHandler())
|
||||
.popChangeHandler(MockChangeHandler.defaultHandler()));
|
||||
|
||||
activityProxy.pause().stop(false).destroy();
|
||||
|
||||
assertEquals(Collections.singletonList(true), listener.changeEndReferences);
|
||||
assertEquals(Collections.singletonList(true), listener.postCreateViewReferences);
|
||||
assertEquals(Collections.singletonList(true), listener.postAttachReferences);
|
||||
assertEquals(Collections.singletonList(true), listener.postDetachReferences);
|
||||
assertEquals(Collections.singletonList(true), listener.postDestroyViewReferences);
|
||||
assertEquals(Collections.singletonList(true), listener.postDestroyReferences);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testChildControllerActivityOnDestroy() {
|
||||
Controller parent = new TestController();
|
||||
|
||||
router.pushController(RouterTransaction.with(parent)
|
||||
.pushChangeHandler(MockChangeHandler.defaultHandler())
|
||||
.popChangeHandler(MockChangeHandler.defaultHandler()));
|
||||
|
||||
TestController child = new TestController();
|
||||
|
||||
ActivityReferencingLifecycleListener listener = new ActivityReferencingLifecycleListener();
|
||||
child.addLifecycleListener(listener);
|
||||
|
||||
Router childRouter = parent.getChildRouter(parent.getView().findViewById(TestController.VIEW_ID));
|
||||
childRouter.setPopsLastView(true);
|
||||
childRouter.pushController(RouterTransaction.with(child)
|
||||
.pushChangeHandler(MockChangeHandler.defaultHandler())
|
||||
.popChangeHandler(MockChangeHandler.defaultHandler()));
|
||||
|
||||
activityProxy.pause().stop(false).destroy();
|
||||
|
||||
assertEquals(Collections.singletonList(true), listener.changeEndReferences);
|
||||
assertEquals(Collections.singletonList(true), listener.postCreateViewReferences);
|
||||
assertEquals(Collections.singletonList(true), listener.postAttachReferences);
|
||||
assertEquals(Collections.singletonList(true), listener.postDetachReferences);
|
||||
assertEquals(Collections.singletonList(true), listener.postDestroyViewReferences);
|
||||
assertEquals(Collections.singletonList(true), listener.postDestroyReferences);
|
||||
}
|
||||
|
||||
static class ActivityReferencingLifecycleListener implements Controller.LifecycleListener {
|
||||
final List<Boolean> changeEndReferences = new ArrayList<>();
|
||||
final List<Boolean> postCreateViewReferences = new ArrayList<>();
|
||||
final List<Boolean> postAttachReferences = new ArrayList<>();
|
||||
final List<Boolean> postDetachReferences = new ArrayList<>();
|
||||
final List<Boolean> postDestroyViewReferences = new ArrayList<>();
|
||||
final List<Boolean> postDestroyReferences = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public void onChangeEnd(@NonNull Controller controller, @NonNull ControllerChangeHandler changeHandler, @NonNull ControllerChangeType changeType) {
|
||||
changeEndReferences.add(controller.getActivity() != null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postCreateView(@NonNull Controller controller, @NonNull View view) {
|
||||
postCreateViewReferences.add(controller.getActivity() != null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postAttach(@NonNull Controller controller, @NonNull View view) {
|
||||
postAttachReferences.add(controller.getActivity() != null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postDetach(@NonNull Controller controller, @NonNull View view) {
|
||||
postDetachReferences.add(controller.getActivity() != null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postDestroyView(@NonNull Controller controller) {
|
||||
postDestroyViewReferences.add(controller.getActivity() != null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postDestroy(@NonNull Controller controller) {
|
||||
postDestroyReferences.add(controller.getActivity() != null);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+255
@@ -0,0 +1,255 @@
|
||||
package com.bluelinelabs.conductor
|
||||
|
||||
import android.os.Looper.getMainLooper
|
||||
import android.view.View
|
||||
import com.bluelinelabs.conductor.Controller.LifecycleListener
|
||||
import com.bluelinelabs.conductor.util.MockChangeHandler
|
||||
import com.bluelinelabs.conductor.util.TestActivity
|
||||
import org.junit.Assert
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.Robolectric
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import org.robolectric.Shadows.shadowOf
|
||||
import org.robolectric.annotation.Config
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
@Config(manifest = Config.NONE)
|
||||
class ControllerLifecycleActivityReferenceTests {
|
||||
|
||||
private val activityController = Robolectric.buildActivity(TestActivity::class.java).setup()
|
||||
private val activity = activityController.get()
|
||||
|
||||
@Test
|
||||
fun testSingleControllerActivityOnPush() {
|
||||
val controller = TestController()
|
||||
Assert.assertNull(controller.activity)
|
||||
|
||||
val listener = ActivityReferencingLifecycleListener()
|
||||
controller.addLifecycleListener(listener)
|
||||
|
||||
activity.router.pushController(
|
||||
controller.asTransaction(
|
||||
pushChangeHandler = MockChangeHandler.defaultHandler(),
|
||||
popChangeHandler = MockChangeHandler.defaultHandler()
|
||||
)
|
||||
)
|
||||
|
||||
Assert.assertEquals(listOf(true), listener.changeEndReferences)
|
||||
Assert.assertEquals(listOf(true), listener.postCreateViewReferences)
|
||||
Assert.assertEquals(listOf(true), listener.postAttachReferences)
|
||||
Assert.assertEquals(emptyList<Any>(), listener.postDetachReferences)
|
||||
Assert.assertEquals(emptyList<Any>(), listener.postDestroyViewReferences)
|
||||
Assert.assertEquals(emptyList<Any>(), listener.postDestroyReferences)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testChildControllerActivityOnPush() {
|
||||
val parent = TestController()
|
||||
activity.router.pushController(
|
||||
parent.asTransaction(
|
||||
pushChangeHandler = MockChangeHandler.defaultHandler(),
|
||||
popChangeHandler = MockChangeHandler.defaultHandler()
|
||||
)
|
||||
)
|
||||
|
||||
val child = TestController()
|
||||
Assert.assertNull(child.activity)
|
||||
|
||||
val listener = ActivityReferencingLifecycleListener()
|
||||
child.addLifecycleListener(listener)
|
||||
|
||||
val childRouter = parent.getChildRouter((parent.view!!.findViewById(TestController.VIEW_ID)))
|
||||
childRouter.pushController(
|
||||
child.asTransaction(
|
||||
pushChangeHandler = MockChangeHandler.defaultHandler(),
|
||||
popChangeHandler = MockChangeHandler.defaultHandler()
|
||||
)
|
||||
)
|
||||
|
||||
Assert.assertEquals(listOf(true), listener.changeEndReferences)
|
||||
Assert.assertEquals(listOf(true), listener.postCreateViewReferences)
|
||||
Assert.assertEquals(listOf(true), listener.postAttachReferences)
|
||||
Assert.assertEquals(emptyList<Any>(), listener.postDetachReferences)
|
||||
Assert.assertEquals(emptyList<Any>(), listener.postDestroyViewReferences)
|
||||
Assert.assertEquals(emptyList<Any>(), listener.postDestroyReferences)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSingleControllerActivityOnPop() {
|
||||
val controller = TestController()
|
||||
val listener = ActivityReferencingLifecycleListener()
|
||||
controller.addLifecycleListener(listener)
|
||||
|
||||
activity.router.pushController(
|
||||
controller.asTransaction(
|
||||
pushChangeHandler = MockChangeHandler.defaultHandler(),
|
||||
popChangeHandler = MockChangeHandler.defaultHandler()
|
||||
)
|
||||
)
|
||||
activity.router.popCurrentController()
|
||||
|
||||
Assert.assertEquals(listOf(true, true), listener.changeEndReferences)
|
||||
Assert.assertEquals(listOf(true), listener.postCreateViewReferences)
|
||||
Assert.assertEquals(listOf(true), listener.postAttachReferences)
|
||||
Assert.assertEquals(listOf(true), listener.postDetachReferences)
|
||||
Assert.assertEquals(listOf(true), listener.postDestroyViewReferences)
|
||||
Assert.assertEquals(listOf(true), listener.postDestroyReferences)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testChildControllerActivityOnPop() {
|
||||
val parent = TestController()
|
||||
activity.router.pushController(
|
||||
parent.asTransaction(
|
||||
pushChangeHandler = MockChangeHandler.defaultHandler(),
|
||||
popChangeHandler = MockChangeHandler.defaultHandler()
|
||||
)
|
||||
)
|
||||
|
||||
val child = TestController()
|
||||
val listener = ActivityReferencingLifecycleListener()
|
||||
child.addLifecycleListener(listener)
|
||||
|
||||
val childRouter = parent.getChildRouter(parent.view!!.findViewById(TestController.VIEW_ID))
|
||||
childRouter.setPopRootControllerMode(Router.PopRootControllerMode.POP_ROOT_CONTROLLER_AND_VIEW)
|
||||
childRouter.pushController(
|
||||
child.asTransaction(
|
||||
pushChangeHandler = MockChangeHandler.defaultHandler(),
|
||||
popChangeHandler = MockChangeHandler.defaultHandler()
|
||||
)
|
||||
)
|
||||
childRouter.popCurrentController()
|
||||
|
||||
shadowOf(getMainLooper()).idle()
|
||||
|
||||
Assert.assertEquals(listOf(true, true), listener.changeEndReferences)
|
||||
Assert.assertEquals(listOf(true), listener.postCreateViewReferences)
|
||||
Assert.assertEquals(listOf(true), listener.postAttachReferences)
|
||||
Assert.assertEquals(listOf(true), listener.postDetachReferences)
|
||||
Assert.assertEquals(listOf(true), listener.postDestroyViewReferences)
|
||||
Assert.assertEquals(listOf(true), listener.postDestroyReferences)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testChildControllerActivityOnParentPop() {
|
||||
val parent = TestController()
|
||||
activity.router.pushController(
|
||||
parent.asTransaction(
|
||||
pushChangeHandler = MockChangeHandler.defaultHandler(),
|
||||
popChangeHandler = MockChangeHandler.defaultHandler()
|
||||
)
|
||||
)
|
||||
|
||||
val child = TestController()
|
||||
val listener = ActivityReferencingLifecycleListener()
|
||||
child.addLifecycleListener(listener)
|
||||
|
||||
val childRouter = parent.getChildRouter(parent.view!!.findViewById(TestController.VIEW_ID))
|
||||
childRouter.setPopRootControllerMode(Router.PopRootControllerMode.POP_ROOT_CONTROLLER_AND_VIEW)
|
||||
childRouter.pushController(
|
||||
child.asTransaction(
|
||||
pushChangeHandler = MockChangeHandler.defaultHandler(),
|
||||
popChangeHandler = MockChangeHandler.defaultHandler()
|
||||
)
|
||||
)
|
||||
activity.router.popCurrentController()
|
||||
|
||||
Assert.assertEquals(listOf(true), listener.changeEndReferences)
|
||||
Assert.assertEquals(listOf(true), listener.postCreateViewReferences)
|
||||
Assert.assertEquals(listOf(true), listener.postAttachReferences)
|
||||
Assert.assertEquals(listOf(true), listener.postDetachReferences)
|
||||
Assert.assertEquals(listOf(true), listener.postDestroyViewReferences)
|
||||
Assert.assertEquals(listOf(true), listener.postDestroyReferences)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSingleControllerActivityOnDestroy() {
|
||||
val controller = TestController()
|
||||
val listener = ActivityReferencingLifecycleListener()
|
||||
controller.addLifecycleListener(listener)
|
||||
|
||||
activity.router.pushController(
|
||||
controller.asTransaction(
|
||||
pushChangeHandler = MockChangeHandler.defaultHandler(),
|
||||
popChangeHandler = MockChangeHandler.defaultHandler()
|
||||
)
|
||||
)
|
||||
activityController.pause().stop().destroy()
|
||||
|
||||
Assert.assertEquals(listOf(true), listener.changeEndReferences)
|
||||
Assert.assertEquals(listOf(true), listener.postCreateViewReferences)
|
||||
Assert.assertEquals(listOf(true), listener.postAttachReferences)
|
||||
Assert.assertEquals(listOf(true), listener.postDetachReferences)
|
||||
Assert.assertEquals(listOf(true), listener.postDestroyViewReferences)
|
||||
Assert.assertEquals(listOf(true), listener.postDestroyReferences)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testChildControllerActivityOnDestroy() {
|
||||
val parent = TestController()
|
||||
activity.router.pushController(
|
||||
parent.asTransaction(
|
||||
pushChangeHandler = MockChangeHandler.defaultHandler(),
|
||||
popChangeHandler = MockChangeHandler.defaultHandler()
|
||||
)
|
||||
)
|
||||
|
||||
val child = TestController()
|
||||
val listener = ActivityReferencingLifecycleListener()
|
||||
child.addLifecycleListener(listener)
|
||||
val childRouter = parent.getChildRouter(parent.view!!.findViewById(TestController.VIEW_ID))
|
||||
childRouter.setPopRootControllerMode(Router.PopRootControllerMode.POP_ROOT_CONTROLLER_AND_VIEW)
|
||||
childRouter.pushController(
|
||||
child.asTransaction(
|
||||
pushChangeHandler = MockChangeHandler.defaultHandler(),
|
||||
popChangeHandler = MockChangeHandler.defaultHandler()
|
||||
)
|
||||
)
|
||||
activityController.pause().stop().destroy()
|
||||
|
||||
Assert.assertEquals(listOf(true), listener.changeEndReferences)
|
||||
Assert.assertEquals(listOf(true), listener.postCreateViewReferences)
|
||||
Assert.assertEquals(listOf(true), listener.postAttachReferences)
|
||||
Assert.assertEquals(listOf(true), listener.postDetachReferences)
|
||||
Assert.assertEquals(listOf(true), listener.postDestroyViewReferences)
|
||||
Assert.assertEquals(listOf(true), listener.postDestroyReferences)
|
||||
}
|
||||
|
||||
internal class ActivityReferencingLifecycleListener : LifecycleListener() {
|
||||
val changeEndReferences = mutableListOf<Boolean>()
|
||||
val postCreateViewReferences = mutableListOf<Boolean>()
|
||||
val postAttachReferences = mutableListOf<Boolean>()
|
||||
val postDetachReferences = mutableListOf<Boolean>()
|
||||
val postDestroyViewReferences = mutableListOf<Boolean>()
|
||||
val postDestroyReferences = mutableListOf<Boolean>()
|
||||
|
||||
override fun onChangeEnd(
|
||||
controller: Controller,
|
||||
changeHandler: ControllerChangeHandler,
|
||||
changeType: ControllerChangeType
|
||||
) {
|
||||
changeEndReferences.add(controller.activity != null)
|
||||
}
|
||||
|
||||
override fun postCreateView(controller: Controller, view: View) {
|
||||
postCreateViewReferences.add(controller.activity != null)
|
||||
}
|
||||
|
||||
override fun postAttach(controller: Controller, view: View) {
|
||||
postAttachReferences.add(controller.activity != null)
|
||||
}
|
||||
|
||||
override fun postDetach(controller: Controller, view: View) {
|
||||
postDetachReferences.add(controller.activity != null)
|
||||
}
|
||||
|
||||
override fun postDestroyView(controller: Controller) {
|
||||
postDestroyViewReferences.add(controller.activity != null)
|
||||
}
|
||||
|
||||
override fun postDestroy(controller: Controller) {
|
||||
postDestroyReferences.add(controller.activity != null)
|
||||
}
|
||||
}
|
||||
}
|
||||
-716
@@ -1,716 +0,0 @@
|
||||
package com.bluelinelabs.conductor;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import androidx.annotation.NonNull;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.bluelinelabs.conductor.Controller.LifecycleListener;
|
||||
import com.bluelinelabs.conductor.Controller.RetainViewMode;
|
||||
import com.bluelinelabs.conductor.changehandler.SimpleSwapChangeHandler;
|
||||
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 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;
|
||||
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 ControllerLifecycleCallbacksTests {
|
||||
|
||||
private Router router;
|
||||
|
||||
private ActivityProxy activityProxy;
|
||||
private CallState currentCallState;
|
||||
|
||||
public void createActivityController(Bundle savedInstanceState, boolean includeStartAndResume) {
|
||||
activityProxy = new ActivityProxy().create(savedInstanceState);
|
||||
|
||||
if (includeStartAndResume) {
|
||||
activityProxy.start().resume();
|
||||
}
|
||||
|
||||
router = Conductor.attachRouter(activityProxy.getActivity(), activityProxy.getView(), savedInstanceState);
|
||||
if (!router.hasRootController()) {
|
||||
router.setRoot(RouterTransaction.with(new TestController()));
|
||||
}
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
createActivityController(null, true);
|
||||
|
||||
currentCallState = new CallState(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNormalLifecycle() {
|
||||
TestController controller = new TestController();
|
||||
attachLifecycleListener(controller);
|
||||
|
||||
CallState expectedCallState = new CallState(false);
|
||||
|
||||
assertCalls(expectedCallState, controller);
|
||||
router.pushController(RouterTransaction.with(controller)
|
||||
.pushChangeHandler(getPushHandler(expectedCallState, controller))
|
||||
.popChangeHandler(getPopHandler(expectedCallState, controller)));
|
||||
|
||||
assertCalls(expectedCallState, controller);
|
||||
|
||||
router.popCurrentController();
|
||||
|
||||
assertNull(controller.getView());
|
||||
|
||||
assertCalls(expectedCallState, controller);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLifecycleWithActivityStop() {
|
||||
TestController controller = new TestController();
|
||||
attachLifecycleListener(controller);
|
||||
|
||||
CallState expectedCallState = new CallState(false);
|
||||
|
||||
assertCalls(expectedCallState, controller);
|
||||
router.pushController(RouterTransaction.with(controller)
|
||||
.pushChangeHandler(getPushHandler(expectedCallState, controller)));
|
||||
|
||||
assertCalls(expectedCallState, controller);
|
||||
|
||||
activityProxy.getActivity().isDestroying = true;
|
||||
activityProxy.pause();
|
||||
|
||||
assertCalls(expectedCallState, controller);
|
||||
|
||||
activityProxy.stop(false);
|
||||
|
||||
expectedCallState.detachCalls++;
|
||||
assertCalls(expectedCallState, controller);
|
||||
|
||||
assertNotNull(controller.getView());
|
||||
ViewUtils.reportAttached(controller.getView(), false);
|
||||
|
||||
expectedCallState.saveViewStateCalls++;
|
||||
expectedCallState.destroyViewCalls++;
|
||||
assertCalls(expectedCallState, controller);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLifecycleWithActivityDestroy() {
|
||||
TestController controller = new TestController();
|
||||
attachLifecycleListener(controller);
|
||||
|
||||
CallState expectedCallState = new CallState(false);
|
||||
|
||||
assertCalls(expectedCallState, controller);
|
||||
router.pushController(RouterTransaction.with(controller)
|
||||
.pushChangeHandler(getPushHandler(expectedCallState, controller)));
|
||||
|
||||
assertCalls(expectedCallState, controller);
|
||||
|
||||
activityProxy.getActivity().isDestroying = true;
|
||||
activityProxy.pause();
|
||||
|
||||
assertCalls(expectedCallState, controller);
|
||||
|
||||
activityProxy.stop(true);
|
||||
|
||||
expectedCallState.saveViewStateCalls++;
|
||||
expectedCallState.detachCalls++;
|
||||
expectedCallState.destroyViewCalls++;
|
||||
assertCalls(expectedCallState, controller);
|
||||
|
||||
activityProxy.destroy();
|
||||
|
||||
expectedCallState.contextUnavailableCalls++;
|
||||
expectedCallState.destroyCalls++;
|
||||
assertCalls(expectedCallState, controller);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLifecycleWithActivityConfigurationChange() {
|
||||
TestController controller = new TestController();
|
||||
attachLifecycleListener(controller);
|
||||
|
||||
CallState expectedCallState = new CallState(false);
|
||||
|
||||
assertCalls(expectedCallState, controller);
|
||||
router.pushController(RouterTransaction.with(controller)
|
||||
.pushChangeHandler(getPushHandler(expectedCallState, controller))
|
||||
.tag("root"));
|
||||
|
||||
assertCalls(expectedCallState, controller);
|
||||
|
||||
activityProxy.getActivity().isChangingConfigurations = true;
|
||||
|
||||
Bundle bundle = new Bundle();
|
||||
activityProxy.saveInstanceState(bundle);
|
||||
|
||||
expectedCallState.saveViewStateCalls++;
|
||||
expectedCallState.saveInstanceStateCalls++;
|
||||
assertCalls(expectedCallState, controller);
|
||||
|
||||
activityProxy.pause();
|
||||
assertCalls(expectedCallState, controller);
|
||||
|
||||
activityProxy.stop(true);
|
||||
expectedCallState.detachCalls++;
|
||||
expectedCallState.destroyViewCalls++;
|
||||
assertCalls(expectedCallState, controller);
|
||||
|
||||
activityProxy.destroy();
|
||||
expectedCallState.contextUnavailableCalls++;
|
||||
assertCalls(expectedCallState, controller);
|
||||
|
||||
createActivityController(bundle, false);
|
||||
controller = (TestController)router.getControllerWithTag("root");
|
||||
|
||||
expectedCallState.contextAvailableCalls++;
|
||||
expectedCallState.restoreInstanceStateCalls++;
|
||||
expectedCallState.restoreViewStateCalls++;
|
||||
expectedCallState.changeStartCalls++;
|
||||
expectedCallState.createViewCalls++;
|
||||
|
||||
// Lifecycle listener isn't attached during restore, grab the current views from the controller for this stuff...
|
||||
currentCallState.restoreInstanceStateCalls = controller.currentCallState.restoreInstanceStateCalls;
|
||||
currentCallState.restoreViewStateCalls = controller.currentCallState.restoreViewStateCalls;
|
||||
currentCallState.changeStartCalls = controller.currentCallState.changeStartCalls;
|
||||
currentCallState.changeEndCalls = controller.currentCallState.changeEndCalls;
|
||||
currentCallState.createViewCalls = controller.currentCallState.createViewCalls;
|
||||
currentCallState.attachCalls = controller.currentCallState.attachCalls;
|
||||
currentCallState.contextAvailableCalls = controller.currentCallState.contextAvailableCalls;
|
||||
|
||||
assertCalls(expectedCallState, controller);
|
||||
|
||||
activityProxy.start().resume();
|
||||
currentCallState.changeEndCalls = controller.currentCallState.changeEndCalls;
|
||||
currentCallState.attachCalls = controller.currentCallState.attachCalls;
|
||||
expectedCallState.changeEndCalls++;
|
||||
expectedCallState.attachCalls++;
|
||||
|
||||
assertCalls(expectedCallState, controller);
|
||||
|
||||
activityProxy.resume();
|
||||
assertCalls(expectedCallState, controller);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLifecycleWithActivityBackground() {
|
||||
TestController controller = new TestController();
|
||||
attachLifecycleListener(controller);
|
||||
|
||||
CallState expectedCallState = new CallState(false);
|
||||
|
||||
assertCalls(expectedCallState, controller);
|
||||
router.pushController(RouterTransaction.with(controller)
|
||||
.pushChangeHandler(getPushHandler(expectedCallState, controller)));
|
||||
|
||||
assertCalls(expectedCallState, controller);
|
||||
|
||||
activityProxy.pause();
|
||||
|
||||
Bundle bundle = new Bundle();
|
||||
activityProxy.saveInstanceState(bundle);
|
||||
|
||||
expectedCallState.saveInstanceStateCalls++;
|
||||
expectedCallState.saveViewStateCalls++;
|
||||
assertCalls(expectedCallState, controller);
|
||||
|
||||
activityProxy.resume();
|
||||
|
||||
assertCalls(expectedCallState, controller);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLifecycleCallOrder() {
|
||||
final TestController testController = new TestController();
|
||||
final CallState callState = new CallState(false);
|
||||
|
||||
testController.addLifecycleListener(new LifecycleListener() {
|
||||
@Override
|
||||
public void preCreateView(@NonNull Controller controller) {
|
||||
callState.createViewCalls++;
|
||||
assertEquals(1, callState.createViewCalls);
|
||||
assertEquals(0, testController.currentCallState.createViewCalls);
|
||||
|
||||
assertEquals(0, callState.attachCalls);
|
||||
assertEquals(0, testController.currentCallState.attachCalls);
|
||||
|
||||
assertEquals(0, callState.detachCalls);
|
||||
assertEquals(0, testController.currentCallState.detachCalls);
|
||||
|
||||
assertEquals(0, callState.destroyViewCalls);
|
||||
assertEquals(0, testController.currentCallState.destroyViewCalls);
|
||||
|
||||
assertEquals(0, callState.destroyCalls);
|
||||
assertEquals(0, testController.currentCallState.destroyCalls);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postCreateView(@NonNull Controller controller, @NonNull View view) {
|
||||
callState.createViewCalls++;
|
||||
assertEquals(2, callState.createViewCalls);
|
||||
assertEquals(1, testController.currentCallState.createViewCalls);
|
||||
|
||||
assertEquals(0, callState.attachCalls);
|
||||
assertEquals(0, testController.currentCallState.attachCalls);
|
||||
|
||||
assertEquals(0, callState.detachCalls);
|
||||
assertEquals(0, testController.currentCallState.detachCalls);
|
||||
|
||||
assertEquals(0, callState.destroyViewCalls);
|
||||
assertEquals(0, testController.currentCallState.destroyViewCalls);
|
||||
|
||||
assertEquals(0, callState.destroyCalls);
|
||||
assertEquals(0, testController.currentCallState.destroyCalls);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preAttach(@NonNull Controller controller, @NonNull View view) {
|
||||
callState.attachCalls++;
|
||||
assertEquals(2, callState.createViewCalls);
|
||||
assertEquals(1, testController.currentCallState.createViewCalls);
|
||||
|
||||
assertEquals(1, callState.attachCalls);
|
||||
assertEquals(0, testController.currentCallState.attachCalls);
|
||||
|
||||
assertEquals(0, callState.detachCalls);
|
||||
assertEquals(0, testController.currentCallState.detachCalls);
|
||||
|
||||
assertEquals(0, callState.destroyViewCalls);
|
||||
assertEquals(0, testController.currentCallState.destroyViewCalls);
|
||||
|
||||
assertEquals(0, callState.destroyCalls);
|
||||
assertEquals(0, testController.currentCallState.destroyCalls);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postAttach(@NonNull Controller controller, @NonNull View view) {
|
||||
callState.attachCalls++;
|
||||
assertEquals(2, callState.createViewCalls);
|
||||
assertEquals(1, testController.currentCallState.createViewCalls);
|
||||
|
||||
assertEquals(2, callState.attachCalls);
|
||||
assertEquals(1, testController.currentCallState.attachCalls);
|
||||
|
||||
assertEquals(0, callState.detachCalls);
|
||||
assertEquals(0, testController.currentCallState.detachCalls);
|
||||
|
||||
assertEquals(0, callState.destroyViewCalls);
|
||||
assertEquals(0, testController.currentCallState.destroyViewCalls);
|
||||
|
||||
assertEquals(0, callState.destroyCalls);
|
||||
assertEquals(0, testController.currentCallState.destroyCalls);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preDetach(@NonNull Controller controller, @NonNull View view) {
|
||||
callState.detachCalls++;
|
||||
assertEquals(2, callState.createViewCalls);
|
||||
assertEquals(1, testController.currentCallState.createViewCalls);
|
||||
|
||||
assertEquals(2, callState.attachCalls);
|
||||
assertEquals(1, testController.currentCallState.attachCalls);
|
||||
|
||||
assertEquals(1, callState.detachCalls);
|
||||
assertEquals(0, testController.currentCallState.detachCalls);
|
||||
|
||||
assertEquals(0, callState.destroyViewCalls);
|
||||
assertEquals(0, testController.currentCallState.destroyViewCalls);
|
||||
|
||||
assertEquals(0, callState.destroyCalls);
|
||||
assertEquals(0, testController.currentCallState.destroyCalls);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postDetach(@NonNull Controller controller, @NonNull View view) {
|
||||
callState.detachCalls++;
|
||||
assertEquals(2, callState.createViewCalls);
|
||||
assertEquals(1, testController.currentCallState.createViewCalls);
|
||||
|
||||
assertEquals(2, callState.attachCalls);
|
||||
assertEquals(1, testController.currentCallState.attachCalls);
|
||||
|
||||
assertEquals(2, callState.detachCalls);
|
||||
assertEquals(1, testController.currentCallState.detachCalls);
|
||||
|
||||
assertEquals(0, callState.destroyViewCalls);
|
||||
assertEquals(0, testController.currentCallState.destroyViewCalls);
|
||||
|
||||
assertEquals(0, callState.destroyCalls);
|
||||
assertEquals(0, testController.currentCallState.destroyCalls);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preDestroyView(@NonNull Controller controller, @NonNull View view) {
|
||||
callState.destroyViewCalls++;
|
||||
assertEquals(2, callState.createViewCalls);
|
||||
assertEquals(1, testController.currentCallState.createViewCalls);
|
||||
|
||||
assertEquals(2, callState.attachCalls);
|
||||
assertEquals(1, testController.currentCallState.attachCalls);
|
||||
|
||||
assertEquals(2, callState.detachCalls);
|
||||
assertEquals(1, testController.currentCallState.detachCalls);
|
||||
|
||||
assertEquals(1, callState.destroyViewCalls);
|
||||
assertEquals(0, testController.currentCallState.destroyViewCalls);
|
||||
|
||||
assertEquals(0, callState.destroyCalls);
|
||||
assertEquals(0, testController.currentCallState.destroyCalls);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postDestroyView(@NonNull Controller controller) {
|
||||
callState.destroyViewCalls++;
|
||||
assertEquals(2, callState.createViewCalls);
|
||||
assertEquals(1, testController.currentCallState.createViewCalls);
|
||||
|
||||
assertEquals(2, callState.attachCalls);
|
||||
assertEquals(1, testController.currentCallState.attachCalls);
|
||||
|
||||
assertEquals(2, callState.detachCalls);
|
||||
assertEquals(1, testController.currentCallState.detachCalls);
|
||||
|
||||
assertEquals(2, callState.destroyViewCalls);
|
||||
assertEquals(1, testController.currentCallState.destroyViewCalls);
|
||||
|
||||
assertEquals(0, callState.destroyCalls);
|
||||
assertEquals(0, testController.currentCallState.destroyCalls);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preDestroy(@NonNull Controller controller) {
|
||||
callState.destroyCalls++;
|
||||
assertEquals(2, callState.createViewCalls);
|
||||
assertEquals(1, testController.currentCallState.createViewCalls);
|
||||
|
||||
assertEquals(2, callState.attachCalls);
|
||||
assertEquals(1, testController.currentCallState.attachCalls);
|
||||
|
||||
assertEquals(2, callState.detachCalls);
|
||||
assertEquals(1, testController.currentCallState.detachCalls);
|
||||
|
||||
assertEquals(2, callState.destroyViewCalls);
|
||||
assertEquals(1, testController.currentCallState.destroyViewCalls);
|
||||
|
||||
assertEquals(1, callState.destroyCalls);
|
||||
assertEquals(0, testController.currentCallState.destroyCalls);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postDestroy(@NonNull Controller controller) {
|
||||
callState.destroyCalls++;
|
||||
assertEquals(2, callState.createViewCalls);
|
||||
assertEquals(1, testController.currentCallState.createViewCalls);
|
||||
|
||||
assertEquals(2, callState.attachCalls);
|
||||
assertEquals(1, testController.currentCallState.attachCalls);
|
||||
|
||||
assertEquals(2, callState.detachCalls);
|
||||
assertEquals(1, testController.currentCallState.detachCalls);
|
||||
|
||||
assertEquals(2, callState.destroyViewCalls);
|
||||
assertEquals(1, testController.currentCallState.destroyViewCalls);
|
||||
|
||||
assertEquals(2, callState.destroyCalls);
|
||||
assertEquals(1, testController.currentCallState.destroyCalls);
|
||||
}
|
||||
});
|
||||
|
||||
router.pushController(RouterTransaction.with(testController)
|
||||
.pushChangeHandler(MockChangeHandler.defaultHandler())
|
||||
.popChangeHandler(MockChangeHandler.defaultHandler()));
|
||||
|
||||
router.popController(testController);
|
||||
|
||||
assertEquals(2, callState.createViewCalls);
|
||||
assertEquals(2, callState.attachCalls);
|
||||
assertEquals(2, callState.detachCalls);
|
||||
assertEquals(2, callState.destroyViewCalls);
|
||||
assertEquals(2, callState.destroyCalls);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLifecycleWhenPopNonCurrentController() {
|
||||
String controller1Tag = "controller1";
|
||||
String controller2Tag = "controller2";
|
||||
String controller3Tag = "controller3";
|
||||
|
||||
TestController controller1 = new TestController();
|
||||
TestController controller2 = new TestController();
|
||||
TestController controller3 = new TestController();
|
||||
|
||||
router.pushController(RouterTransaction.with(controller1)
|
||||
.tag(controller1Tag));
|
||||
|
||||
router.pushController(RouterTransaction.with(controller2)
|
||||
.tag(controller2Tag));
|
||||
|
||||
router.pushController(RouterTransaction.with(controller3)
|
||||
.tag(controller3Tag));
|
||||
|
||||
router.popController(controller2);
|
||||
|
||||
assertEquals(1, controller2.currentCallState.attachCalls);
|
||||
assertEquals(1, controller2.currentCallState.createViewCalls);
|
||||
assertEquals(1, controller2.currentCallState.detachCalls);
|
||||
assertEquals(1, controller2.currentCallState.destroyViewCalls);
|
||||
assertEquals(1, controller2.currentCallState.destroyCalls);
|
||||
assertEquals(1, controller2.currentCallState.contextAvailableCalls);
|
||||
assertEquals(1, controller2.currentCallState.contextUnavailableCalls);
|
||||
assertEquals(1, controller2.currentCallState.saveViewStateCalls);
|
||||
assertEquals(0, controller2.currentCallState.restoreViewStateCalls);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testChildLifecycle() {
|
||||
Controller parent = new TestController();
|
||||
router.pushController(RouterTransaction.with(parent)
|
||||
.pushChangeHandler(MockChangeHandler.defaultHandler()));
|
||||
|
||||
TestController child = new TestController();
|
||||
attachLifecycleListener(child);
|
||||
|
||||
CallState expectedCallState = new CallState(false);
|
||||
|
||||
assertCalls(expectedCallState, child);
|
||||
|
||||
Router childRouter = parent.getChildRouter((ViewGroup)parent.getView().findViewById(TestController.VIEW_ID));
|
||||
childRouter
|
||||
.setRoot(RouterTransaction.with(child)
|
||||
.pushChangeHandler(getPushHandler(expectedCallState, child))
|
||||
.popChangeHandler(getPopHandler(expectedCallState, child)));
|
||||
|
||||
assertCalls(expectedCallState, child);
|
||||
|
||||
parent.removeChildRouter(childRouter);
|
||||
|
||||
assertCalls(expectedCallState, child);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testChildLifecycle2() {
|
||||
Controller parent = new TestController();
|
||||
router.pushController(RouterTransaction.with(parent)
|
||||
.pushChangeHandler(MockChangeHandler.defaultHandler())
|
||||
.popChangeHandler(MockChangeHandler.defaultHandler()));
|
||||
|
||||
TestController child = new TestController();
|
||||
attachLifecycleListener(child);
|
||||
|
||||
CallState expectedCallState = new CallState(false);
|
||||
|
||||
assertCalls(expectedCallState, child);
|
||||
|
||||
Router childRouter = parent.getChildRouter((ViewGroup)parent.getView().findViewById(TestController.VIEW_ID));
|
||||
childRouter
|
||||
.setRoot(RouterTransaction.with(child)
|
||||
.pushChangeHandler(getPushHandler(expectedCallState, child))
|
||||
.popChangeHandler(getPopHandler(expectedCallState, child)));
|
||||
|
||||
assertCalls(expectedCallState, child);
|
||||
|
||||
router.popCurrentController();
|
||||
|
||||
expectedCallState.detachCalls++;
|
||||
expectedCallState.destroyViewCalls++;
|
||||
expectedCallState.contextUnavailableCalls++;
|
||||
expectedCallState.destroyCalls++;
|
||||
|
||||
assertCalls(expectedCallState, child);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testChildLifecycleOrderingAfterUnexpectedAttach() {
|
||||
Controller parent = new TestController();
|
||||
parent.setRetainViewMode(RetainViewMode.RETAIN_DETACH);
|
||||
router.pushController(RouterTransaction.with(parent)
|
||||
.pushChangeHandler(MockChangeHandler.defaultHandler())
|
||||
.popChangeHandler(MockChangeHandler.defaultHandler()));
|
||||
|
||||
TestController child = new TestController();
|
||||
child.setRetainViewMode(RetainViewMode.RETAIN_DETACH);
|
||||
Router childRouter = parent.getChildRouter((ViewGroup)parent.getView().findViewById(TestController.VIEW_ID));
|
||||
childRouter
|
||||
.setRoot(RouterTransaction.with(child)
|
||||
.pushChangeHandler(new SimpleSwapChangeHandler())
|
||||
.popChangeHandler(new SimpleSwapChangeHandler()));
|
||||
|
||||
assertTrue(parent.isAttached());
|
||||
assertTrue(child.isAttached());
|
||||
|
||||
ViewUtils.reportAttached(parent.getView(), false, true);
|
||||
assertFalse(parent.isAttached());
|
||||
assertFalse(child.isAttached());
|
||||
|
||||
ViewUtils.reportAttached(child.getView(), true);
|
||||
assertFalse(parent.isAttached());
|
||||
assertFalse(child.isAttached());
|
||||
|
||||
ViewUtils.reportAttached(parent.getView(), true);
|
||||
assertTrue(parent.isAttached());
|
||||
assertTrue(child.isAttached());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testChildLifecycleAfterPushAndPop() {
|
||||
Controller parent = new TestController();
|
||||
parent.setRetainViewMode(RetainViewMode.RETAIN_DETACH);
|
||||
router.pushController(RouterTransaction.with(parent)
|
||||
.pushChangeHandler(MockChangeHandler.defaultHandler())
|
||||
.popChangeHandler(MockChangeHandler.defaultHandler()));
|
||||
|
||||
TestController child = new TestController();
|
||||
Router childRouter = parent.getChildRouter((ViewGroup)parent.getView().findViewById(TestController.VIEW_ID));
|
||||
childRouter
|
||||
.setRoot(RouterTransaction.with(child)
|
||||
.pushChangeHandler(new SimpleSwapChangeHandler())
|
||||
.popChangeHandler(new SimpleSwapChangeHandler()));
|
||||
|
||||
Controller nextController = new TestController();
|
||||
router.pushController(RouterTransaction.with(nextController));
|
||||
router.popCurrentController();
|
||||
|
||||
assertTrue(parent.isAttached());
|
||||
assertTrue(child.isAttached());
|
||||
}
|
||||
|
||||
private MockChangeHandler getPushHandler(final CallState expectedCallState, final TestController controller) {
|
||||
return MockChangeHandler.listeningChangeHandler(new ChangeHandlerListener() {
|
||||
@Override
|
||||
public void willStartChange() {
|
||||
expectedCallState.contextAvailableCalls++;
|
||||
expectedCallState.changeStartCalls++;
|
||||
expectedCallState.createViewCalls++;
|
||||
assertCalls(expectedCallState, controller);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void didAttachOrDetach() {
|
||||
expectedCallState.attachCalls++;
|
||||
assertCalls(expectedCallState, controller);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void didEndChange() {
|
||||
expectedCallState.changeEndCalls++;
|
||||
assertCalls(expectedCallState, controller);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private MockChangeHandler getPopHandler(final CallState expectedCallState, final TestController controller) {
|
||||
return MockChangeHandler.listeningChangeHandler(new ChangeHandlerListener() {
|
||||
@Override
|
||||
public void willStartChange() {
|
||||
expectedCallState.changeStartCalls++;
|
||||
assertCalls(expectedCallState, controller);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void didAttachOrDetach() {
|
||||
expectedCallState.destroyViewCalls++;
|
||||
expectedCallState.detachCalls++;
|
||||
expectedCallState.contextUnavailableCalls++;
|
||||
expectedCallState.destroyCalls++;
|
||||
assertCalls(expectedCallState, controller);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void didEndChange() {
|
||||
expectedCallState.changeEndCalls++;
|
||||
assertCalls(expectedCallState, controller);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void assertCalls(CallState callState, TestController controller) {
|
||||
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) {
|
||||
controller.addLifecycleListener(new LifecycleListener() {
|
||||
@Override
|
||||
public void onChangeStart(@NonNull Controller controller, @NonNull ControllerChangeHandler changeHandler, @NonNull ControllerChangeType changeType) {
|
||||
currentCallState.changeStartCalls++;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChangeEnd(@NonNull Controller controller, @NonNull ControllerChangeHandler changeHandler, @NonNull ControllerChangeType changeType) {
|
||||
currentCallState.changeEndCalls++;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postContextAvailable(@NonNull Controller controller, @NonNull Context context) {
|
||||
currentCallState.contextAvailableCalls++;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postContextUnavailable(@NonNull Controller controller) {
|
||||
currentCallState.contextUnavailableCalls++;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postCreateView(@NonNull Controller controller, @NonNull View view) {
|
||||
currentCallState.createViewCalls++;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postAttach(@NonNull Controller controller, @NonNull View view) {
|
||||
currentCallState.attachCalls++;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postDestroyView(@NonNull Controller controller) {
|
||||
currentCallState.destroyViewCalls++;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postDetach(@NonNull Controller controller, @NonNull View view) {
|
||||
currentCallState.detachCalls++;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postDestroy(@NonNull Controller controller) {
|
||||
currentCallState.destroyCalls++;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(@NonNull Controller controller, @NonNull Bundle outState) {
|
||||
currentCallState.saveInstanceStateCalls++;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRestoreInstanceState(@NonNull Controller controller, @NonNull Bundle savedInstanceState) {
|
||||
currentCallState.restoreInstanceStateCalls++;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveViewState(@NonNull Controller controller, @NonNull Bundle outState) {
|
||||
currentCallState.saveViewStateCalls++;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRestoreViewState(@NonNull Controller controller, @NonNull Bundle savedViewState) {
|
||||
currentCallState.restoreViewStateCalls++;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
+702
@@ -0,0 +1,702 @@
|
||||
package com.bluelinelabs.conductor
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.os.Looper.getMainLooper
|
||||
import android.view.View
|
||||
import com.bluelinelabs.conductor.Controller.LifecycleListener
|
||||
import com.bluelinelabs.conductor.Controller.RetainViewMode
|
||||
import com.bluelinelabs.conductor.changehandler.SimpleSwapChangeHandler
|
||||
import com.bluelinelabs.conductor.util.CallState
|
||||
import com.bluelinelabs.conductor.util.MockChangeHandler
|
||||
import com.bluelinelabs.conductor.util.MockChangeHandler.ChangeHandlerListener
|
||||
import com.bluelinelabs.conductor.util.TestActivity
|
||||
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.Robolectric
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import org.robolectric.Shadows.shadowOf
|
||||
import org.robolectric.android.controller.ActivityController
|
||||
import org.robolectric.annotation.Config
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
@Config(manifest = Config.NONE)
|
||||
class ControllerLifecycleCallbacksTests {
|
||||
|
||||
private lateinit var activityController: ActivityController<TestActivity>
|
||||
private lateinit var currentCallState: CallState
|
||||
|
||||
private fun createActivityController(savedInstanceState: Bundle?, includeStartAndResume: Boolean) {
|
||||
activityController = Robolectric.buildActivity(TestActivity::class.java)
|
||||
|
||||
activityController.create(savedInstanceState)
|
||||
|
||||
if (savedInstanceState != null) {
|
||||
activityController.restoreInstanceState(savedInstanceState)
|
||||
}
|
||||
|
||||
if (includeStartAndResume) {
|
||||
activityController
|
||||
.start()
|
||||
.postCreate(savedInstanceState)
|
||||
.resume()
|
||||
.visible()
|
||||
}
|
||||
|
||||
if (!activityController.get().router.hasRootController()) {
|
||||
activityController.get().router.setRoot(TestController().asTransaction())
|
||||
}
|
||||
}
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
createActivityController(null, true)
|
||||
currentCallState = CallState(false)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNormalLifecycle() {
|
||||
val controller = TestController()
|
||||
attachLifecycleListener(controller)
|
||||
val expectedCallState = CallState(false)
|
||||
assertCalls(expectedCallState, controller)
|
||||
|
||||
activityController.get().router.pushController(
|
||||
controller.asTransaction(
|
||||
pushChangeHandler = getPushHandler(expectedCallState, controller),
|
||||
popChangeHandler = getPopHandler(expectedCallState, controller)
|
||||
)
|
||||
)
|
||||
assertCalls(expectedCallState, controller)
|
||||
|
||||
activityController.get().router.popCurrentController()
|
||||
Assert.assertNull(controller.view)
|
||||
assertCalls(expectedCallState, controller)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testLifecycleWithActivityStop() {
|
||||
val controller = TestController()
|
||||
attachLifecycleListener(controller)
|
||||
val expectedCallState = CallState(false)
|
||||
assertCalls(expectedCallState, controller)
|
||||
|
||||
activityController.get().router.pushController(
|
||||
controller.asTransaction(
|
||||
pushChangeHandler = getPushHandler(expectedCallState, controller)
|
||||
)
|
||||
)
|
||||
assertCalls(expectedCallState, controller)
|
||||
|
||||
activityController.get().destroying = true
|
||||
activityController.pause()
|
||||
assertCalls(expectedCallState, controller)
|
||||
|
||||
activityController.stop()
|
||||
expectedCallState.detachCalls++
|
||||
assertCalls(expectedCallState, controller)
|
||||
Assert.assertNotNull(controller.view)
|
||||
|
||||
ViewUtils.reportAttached(controller.view, false)
|
||||
expectedCallState.saveViewStateCalls++
|
||||
expectedCallState.destroyViewCalls++
|
||||
assertCalls(expectedCallState, controller)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testLifecycleWithActivityDestroy() {
|
||||
val controller = TestController()
|
||||
attachLifecycleListener(controller)
|
||||
val expectedCallState = CallState(false)
|
||||
assertCalls(expectedCallState, controller)
|
||||
|
||||
activityController.get().router.pushController(
|
||||
controller.asTransaction(
|
||||
pushChangeHandler = getPushHandler(expectedCallState, controller)
|
||||
)
|
||||
)
|
||||
assertCalls(expectedCallState, controller)
|
||||
|
||||
activityController.get().destroying = true
|
||||
activityController.pause()
|
||||
assertCalls(expectedCallState, controller)
|
||||
|
||||
activityController.stop()
|
||||
expectedCallState.detachCalls++
|
||||
assertCalls(expectedCallState, controller)
|
||||
|
||||
activityController.destroy()
|
||||
expectedCallState.destroyViewCalls++
|
||||
expectedCallState.contextUnavailableCalls++
|
||||
expectedCallState.destroyCalls++
|
||||
assertCalls(expectedCallState, controller)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testLifecycleWithActivityConfigurationChange() {
|
||||
var controller = TestController()
|
||||
attachLifecycleListener(controller)
|
||||
val expectedCallState = CallState(false)
|
||||
assertCalls(expectedCallState, controller)
|
||||
|
||||
activityController.get().router.pushController(
|
||||
RouterTransaction.with(controller)
|
||||
.pushChangeHandler(getPushHandler(expectedCallState, controller))
|
||||
.tag("root")
|
||||
)
|
||||
assertCalls(expectedCallState, controller)
|
||||
|
||||
activityController.get().changingConfigurations = true
|
||||
val bundle = Bundle()
|
||||
activityController.saveInstanceState(bundle)
|
||||
expectedCallState.saveViewStateCalls++
|
||||
expectedCallState.saveInstanceStateCalls++
|
||||
assertCalls(expectedCallState, controller)
|
||||
|
||||
activityController.pause()
|
||||
assertCalls(expectedCallState, controller)
|
||||
|
||||
activityController.stop()
|
||||
expectedCallState.detachCalls++
|
||||
assertCalls(expectedCallState, controller)
|
||||
|
||||
activityController.destroy()
|
||||
expectedCallState.destroyViewCalls++
|
||||
expectedCallState.contextUnavailableCalls++
|
||||
assertCalls(expectedCallState, controller)
|
||||
|
||||
createActivityController(bundle, false)
|
||||
controller = activityController.get().router.getControllerWithTag("root") as TestController
|
||||
expectedCallState.contextAvailableCalls++
|
||||
expectedCallState.restoreInstanceStateCalls++
|
||||
expectedCallState.restoreViewStateCalls++
|
||||
expectedCallState.changeStartCalls++
|
||||
expectedCallState.createViewCalls++
|
||||
|
||||
// Lifecycle listener isn't attached during restore, grab the current views from the controller for this stuff...
|
||||
currentCallState.restoreInstanceStateCalls = controller.currentCallState.restoreInstanceStateCalls
|
||||
currentCallState.restoreViewStateCalls = controller.currentCallState.restoreViewStateCalls
|
||||
currentCallState.changeStartCalls = controller.currentCallState.changeStartCalls
|
||||
currentCallState.changeEndCalls = controller.currentCallState.changeEndCalls
|
||||
currentCallState.createViewCalls = controller.currentCallState.createViewCalls
|
||||
currentCallState.attachCalls = controller.currentCallState.attachCalls
|
||||
currentCallState.contextAvailableCalls = controller.currentCallState.contextAvailableCalls
|
||||
assertCalls(expectedCallState, controller)
|
||||
|
||||
activityController
|
||||
.start()
|
||||
.postCreate(bundle)
|
||||
.resume()
|
||||
.visible()
|
||||
|
||||
currentCallState.changeEndCalls = controller.currentCallState.changeEndCalls
|
||||
currentCallState.attachCalls = controller.currentCallState.attachCalls
|
||||
expectedCallState.changeEndCalls++
|
||||
expectedCallState.attachCalls++
|
||||
assertCalls(expectedCallState, controller)
|
||||
|
||||
activityController.resume()
|
||||
assertCalls(expectedCallState, controller)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testLifecycleWithActivityBackground() {
|
||||
val controller = TestController()
|
||||
attachLifecycleListener(controller)
|
||||
val expectedCallState = CallState(false)
|
||||
assertCalls(expectedCallState, controller)
|
||||
|
||||
activityController.get().router.pushController(
|
||||
controller.asTransaction(
|
||||
pushChangeHandler = getPushHandler(expectedCallState, controller)
|
||||
)
|
||||
)
|
||||
assertCalls(expectedCallState, controller)
|
||||
|
||||
activityController.pause()
|
||||
val bundle = Bundle()
|
||||
activityController.saveInstanceState(bundle)
|
||||
expectedCallState.saveInstanceStateCalls++
|
||||
expectedCallState.saveViewStateCalls++
|
||||
assertCalls(expectedCallState, controller)
|
||||
|
||||
activityController.resume()
|
||||
assertCalls(expectedCallState, controller)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testLifecycleCallOrder() {
|
||||
val testController = TestController()
|
||||
val callState = CallState(false)
|
||||
testController.addLifecycleListener(object : LifecycleListener() {
|
||||
override fun preCreateView(controller: Controller) {
|
||||
callState.createViewCalls++
|
||||
Assert.assertEquals(1, callState.createViewCalls)
|
||||
Assert.assertEquals(0, testController.currentCallState.createViewCalls)
|
||||
Assert.assertEquals(0, callState.attachCalls)
|
||||
Assert.assertEquals(0, testController.currentCallState.attachCalls)
|
||||
Assert.assertEquals(0, callState.detachCalls)
|
||||
Assert.assertEquals(0, testController.currentCallState.detachCalls)
|
||||
Assert.assertEquals(0, callState.destroyViewCalls)
|
||||
Assert.assertEquals(0, testController.currentCallState.destroyViewCalls)
|
||||
Assert.assertEquals(0, callState.destroyCalls)
|
||||
Assert.assertEquals(0, testController.currentCallState.destroyCalls)
|
||||
}
|
||||
|
||||
override fun postCreateView(controller: Controller, view: View) {
|
||||
callState.createViewCalls++
|
||||
Assert.assertEquals(2, callState.createViewCalls)
|
||||
Assert.assertEquals(1, testController.currentCallState.createViewCalls)
|
||||
Assert.assertEquals(0, callState.attachCalls)
|
||||
Assert.assertEquals(0, testController.currentCallState.attachCalls)
|
||||
Assert.assertEquals(0, callState.detachCalls)
|
||||
Assert.assertEquals(0, testController.currentCallState.detachCalls)
|
||||
Assert.assertEquals(0, callState.destroyViewCalls)
|
||||
Assert.assertEquals(0, testController.currentCallState.destroyViewCalls)
|
||||
Assert.assertEquals(0, callState.destroyCalls)
|
||||
Assert.assertEquals(0, testController.currentCallState.destroyCalls)
|
||||
}
|
||||
|
||||
override fun preAttach(controller: Controller, view: View) {
|
||||
callState.attachCalls++
|
||||
Assert.assertEquals(2, callState.createViewCalls)
|
||||
Assert.assertEquals(1, testController.currentCallState.createViewCalls)
|
||||
Assert.assertEquals(1, callState.attachCalls)
|
||||
Assert.assertEquals(0, testController.currentCallState.attachCalls)
|
||||
Assert.assertEquals(0, callState.detachCalls)
|
||||
Assert.assertEquals(0, testController.currentCallState.detachCalls)
|
||||
Assert.assertEquals(0, callState.destroyViewCalls)
|
||||
Assert.assertEquals(0, testController.currentCallState.destroyViewCalls)
|
||||
Assert.assertEquals(0, callState.destroyCalls)
|
||||
Assert.assertEquals(0, testController.currentCallState.destroyCalls)
|
||||
}
|
||||
|
||||
override fun postAttach(controller: Controller, view: View) {
|
||||
callState.attachCalls++
|
||||
Assert.assertEquals(2, callState.createViewCalls)
|
||||
Assert.assertEquals(1, testController.currentCallState.createViewCalls)
|
||||
Assert.assertEquals(2, callState.attachCalls)
|
||||
Assert.assertEquals(1, testController.currentCallState.attachCalls)
|
||||
Assert.assertEquals(0, callState.detachCalls)
|
||||
Assert.assertEquals(0, testController.currentCallState.detachCalls)
|
||||
Assert.assertEquals(0, callState.destroyViewCalls)
|
||||
Assert.assertEquals(0, testController.currentCallState.destroyViewCalls)
|
||||
Assert.assertEquals(0, callState.destroyCalls)
|
||||
Assert.assertEquals(0, testController.currentCallState.destroyCalls)
|
||||
}
|
||||
|
||||
override fun preDetach(controller: Controller, view: View) {
|
||||
callState.detachCalls++
|
||||
Assert.assertEquals(2, callState.createViewCalls)
|
||||
Assert.assertEquals(1, testController.currentCallState.createViewCalls)
|
||||
Assert.assertEquals(2, callState.attachCalls)
|
||||
Assert.assertEquals(1, testController.currentCallState.attachCalls)
|
||||
Assert.assertEquals(1, callState.detachCalls)
|
||||
Assert.assertEquals(0, testController.currentCallState.detachCalls)
|
||||
Assert.assertEquals(0, callState.destroyViewCalls)
|
||||
Assert.assertEquals(0, testController.currentCallState.destroyViewCalls)
|
||||
Assert.assertEquals(0, callState.destroyCalls)
|
||||
Assert.assertEquals(0, testController.currentCallState.destroyCalls)
|
||||
}
|
||||
|
||||
override fun postDetach(controller: Controller, view: View) {
|
||||
callState.detachCalls++
|
||||
Assert.assertEquals(2, callState.createViewCalls)
|
||||
Assert.assertEquals(1, testController.currentCallState.createViewCalls)
|
||||
Assert.assertEquals(2, callState.attachCalls)
|
||||
Assert.assertEquals(1, testController.currentCallState.attachCalls)
|
||||
Assert.assertEquals(2, callState.detachCalls)
|
||||
Assert.assertEquals(1, testController.currentCallState.detachCalls)
|
||||
Assert.assertEquals(0, callState.destroyViewCalls)
|
||||
Assert.assertEquals(0, testController.currentCallState.destroyViewCalls)
|
||||
Assert.assertEquals(0, callState.destroyCalls)
|
||||
Assert.assertEquals(0, testController.currentCallState.destroyCalls)
|
||||
}
|
||||
|
||||
override fun preDestroyView(controller: Controller, view: View) {
|
||||
callState.destroyViewCalls++
|
||||
Assert.assertEquals(2, callState.createViewCalls)
|
||||
Assert.assertEquals(1, testController.currentCallState.createViewCalls)
|
||||
Assert.assertEquals(2, callState.attachCalls)
|
||||
Assert.assertEquals(1, testController.currentCallState.attachCalls)
|
||||
Assert.assertEquals(2, callState.detachCalls)
|
||||
Assert.assertEquals(1, testController.currentCallState.detachCalls)
|
||||
Assert.assertEquals(1, callState.destroyViewCalls)
|
||||
Assert.assertEquals(0, testController.currentCallState.destroyViewCalls)
|
||||
Assert.assertEquals(0, callState.destroyCalls)
|
||||
Assert.assertEquals(0, testController.currentCallState.destroyCalls)
|
||||
}
|
||||
|
||||
override fun postDestroyView(controller: Controller) {
|
||||
callState.destroyViewCalls++
|
||||
Assert.assertEquals(2, callState.createViewCalls)
|
||||
Assert.assertEquals(1, testController.currentCallState.createViewCalls)
|
||||
Assert.assertEquals(2, callState.attachCalls)
|
||||
Assert.assertEquals(1, testController.currentCallState.attachCalls)
|
||||
Assert.assertEquals(2, callState.detachCalls)
|
||||
Assert.assertEquals(1, testController.currentCallState.detachCalls)
|
||||
Assert.assertEquals(2, callState.destroyViewCalls)
|
||||
Assert.assertEquals(1, testController.currentCallState.destroyViewCalls)
|
||||
Assert.assertEquals(0, callState.destroyCalls)
|
||||
Assert.assertEquals(0, testController.currentCallState.destroyCalls)
|
||||
}
|
||||
|
||||
override fun preDestroy(controller: Controller) {
|
||||
callState.destroyCalls++
|
||||
Assert.assertEquals(2, callState.createViewCalls)
|
||||
Assert.assertEquals(1, testController.currentCallState.createViewCalls)
|
||||
Assert.assertEquals(2, callState.attachCalls)
|
||||
Assert.assertEquals(1, testController.currentCallState.attachCalls)
|
||||
Assert.assertEquals(2, callState.detachCalls)
|
||||
Assert.assertEquals(1, testController.currentCallState.detachCalls)
|
||||
Assert.assertEquals(2, callState.destroyViewCalls)
|
||||
Assert.assertEquals(1, testController.currentCallState.destroyViewCalls)
|
||||
Assert.assertEquals(1, callState.destroyCalls)
|
||||
Assert.assertEquals(0, testController.currentCallState.destroyCalls)
|
||||
}
|
||||
|
||||
override fun postDestroy(controller: Controller) {
|
||||
callState.destroyCalls++
|
||||
Assert.assertEquals(2, callState.createViewCalls)
|
||||
Assert.assertEquals(1, testController.currentCallState.createViewCalls)
|
||||
Assert.assertEquals(2, callState.attachCalls)
|
||||
Assert.assertEquals(1, testController.currentCallState.attachCalls)
|
||||
Assert.assertEquals(2, callState.detachCalls)
|
||||
Assert.assertEquals(1, testController.currentCallState.detachCalls)
|
||||
Assert.assertEquals(2, callState.destroyViewCalls)
|
||||
Assert.assertEquals(1, testController.currentCallState.destroyViewCalls)
|
||||
Assert.assertEquals(2, callState.destroyCalls)
|
||||
Assert.assertEquals(1, testController.currentCallState.destroyCalls)
|
||||
}
|
||||
})
|
||||
activityController.get().router.pushController(
|
||||
testController.asTransaction(
|
||||
pushChangeHandler = MockChangeHandler.defaultHandler(),
|
||||
popChangeHandler = MockChangeHandler.defaultHandler()
|
||||
)
|
||||
)
|
||||
activityController.get().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)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testLifecycleWhenPopNonCurrentController() {
|
||||
val controller1Tag = "controller1"
|
||||
val controller2Tag = "controller2"
|
||||
val controller3Tag = "controller3"
|
||||
val controller1 = TestController()
|
||||
val controller2 = TestController()
|
||||
val controller3 = TestController()
|
||||
activityController.get().router.pushController(
|
||||
RouterTransaction.with(controller1).tag(controller1Tag)
|
||||
)
|
||||
activityController.get().router.pushController(
|
||||
RouterTransaction.with(controller2).tag(controller2Tag)
|
||||
)
|
||||
activityController.get().router.pushController(
|
||||
RouterTransaction.with(controller3).tag(controller3Tag)
|
||||
)
|
||||
activityController.get().router.popController(controller2)
|
||||
Assert.assertEquals(1, controller2.currentCallState.attachCalls)
|
||||
Assert.assertEquals(1, controller2.currentCallState.createViewCalls)
|
||||
Assert.assertEquals(1, controller2.currentCallState.detachCalls)
|
||||
Assert.assertEquals(1, controller2.currentCallState.destroyViewCalls)
|
||||
Assert.assertEquals(1, controller2.currentCallState.destroyCalls)
|
||||
Assert.assertEquals(1, controller2.currentCallState.contextAvailableCalls)
|
||||
Assert.assertEquals(1, controller2.currentCallState.contextUnavailableCalls)
|
||||
Assert.assertEquals(1, controller2.currentCallState.saveViewStateCalls)
|
||||
Assert.assertEquals(0, controller2.currentCallState.restoreViewStateCalls)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testChildLifecycle() {
|
||||
val parent = TestController()
|
||||
activityController.get().router.pushController(
|
||||
parent.asTransaction(pushChangeHandler = MockChangeHandler.defaultHandler())
|
||||
)
|
||||
|
||||
val child = TestController()
|
||||
attachLifecycleListener(child)
|
||||
val expectedCallState = CallState(false)
|
||||
assertCalls(expectedCallState, child)
|
||||
|
||||
val childRouter = parent.getChildRouter(parent.view!!.findViewById(TestController.VIEW_ID))
|
||||
childRouter.setRoot(
|
||||
child.asTransaction(
|
||||
pushChangeHandler = getPushHandler(expectedCallState, child),
|
||||
popChangeHandler = getPopHandler(expectedCallState, child)
|
||||
)
|
||||
)
|
||||
assertCalls(expectedCallState, child)
|
||||
|
||||
parent.removeChildRouter(childRouter)
|
||||
assertCalls(expectedCallState, child)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testChildLifecycle2() {
|
||||
val parent = TestController()
|
||||
activityController.get().router.pushController(
|
||||
parent.asTransaction(
|
||||
pushChangeHandler = MockChangeHandler.defaultHandler(),
|
||||
popChangeHandler = MockChangeHandler.defaultHandler()
|
||||
)
|
||||
)
|
||||
|
||||
val child = TestController()
|
||||
attachLifecycleListener(child)
|
||||
val expectedCallState = CallState(false)
|
||||
assertCalls(expectedCallState, child)
|
||||
|
||||
val childRouter = parent.getChildRouter(parent.view!!.findViewById(TestController.VIEW_ID))
|
||||
childRouter.setRoot(
|
||||
child.asTransaction(
|
||||
pushChangeHandler = getPushHandler(expectedCallState, child),
|
||||
popChangeHandler = getPopHandler(expectedCallState, child)
|
||||
)
|
||||
)
|
||||
assertCalls(expectedCallState, child)
|
||||
|
||||
activityController.get().router.popCurrentController()
|
||||
expectedCallState.detachCalls++
|
||||
expectedCallState.destroyViewCalls++
|
||||
expectedCallState.contextUnavailableCalls++
|
||||
expectedCallState.destroyCalls++
|
||||
assertCalls(expectedCallState, child)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testChildLifecycleOrderingAfterUnexpectedAttach() {
|
||||
val parent = TestController()
|
||||
parent.retainViewMode = RetainViewMode.RETAIN_DETACH
|
||||
activityController.get().router.pushController(
|
||||
parent.asTransaction(
|
||||
pushChangeHandler = MockChangeHandler.defaultHandler(),
|
||||
popChangeHandler = MockChangeHandler.defaultHandler()
|
||||
)
|
||||
)
|
||||
|
||||
val child = TestController()
|
||||
child.retainViewMode = RetainViewMode.RETAIN_DETACH
|
||||
val childRouter = parent.getChildRouter(parent.getView()!!.findViewById(TestController.VIEW_ID))
|
||||
childRouter.setRoot(
|
||||
child.asTransaction(
|
||||
pushChangeHandler = SimpleSwapChangeHandler(),
|
||||
popChangeHandler = SimpleSwapChangeHandler()
|
||||
)
|
||||
)
|
||||
Assert.assertTrue(parent.isAttached)
|
||||
Assert.assertTrue(child.isAttached)
|
||||
|
||||
ViewUtils.reportAttached(parent.view, false, true)
|
||||
Assert.assertFalse(parent.isAttached)
|
||||
Assert.assertFalse(child.isAttached)
|
||||
|
||||
ViewUtils.reportAttached(child.view, true)
|
||||
Assert.assertFalse(parent.isAttached)
|
||||
Assert.assertFalse(child.isAttached)
|
||||
|
||||
ViewUtils.reportAttached(parent.view, true)
|
||||
Assert.assertTrue(parent.isAttached)
|
||||
Assert.assertTrue(child.isAttached)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testChildLifecycleAfterPushAndPop() {
|
||||
val parent = TestController()
|
||||
parent.retainViewMode = RetainViewMode.RETAIN_DETACH
|
||||
activityController.get().router.pushController(
|
||||
parent.asTransaction(
|
||||
pushChangeHandler = MockChangeHandler.defaultHandler(),
|
||||
popChangeHandler = MockChangeHandler.defaultHandler()
|
||||
)
|
||||
)
|
||||
|
||||
val child = TestController()
|
||||
val childRouter = parent.getChildRouter(parent.view!!.findViewById(TestController.VIEW_ID))
|
||||
childRouter.setRoot(
|
||||
child.asTransaction(
|
||||
pushChangeHandler = SimpleSwapChangeHandler(),
|
||||
popChangeHandler = SimpleSwapChangeHandler()
|
||||
)
|
||||
)
|
||||
|
||||
val nextController = TestController()
|
||||
activityController.get().router.pushController(nextController.asTransaction())
|
||||
activityController.get().router.popCurrentController()
|
||||
|
||||
shadowOf(getMainLooper()).idle()
|
||||
|
||||
Assert.assertTrue(parent.isAttached)
|
||||
Assert.assertTrue(child.isAttached)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testChildLifecycleAfterPushPopPush() {
|
||||
val parent = TestController()
|
||||
parent.retainViewMode = RetainViewMode.RETAIN_DETACH
|
||||
activityController.get().router.pushController(
|
||||
parent.asTransaction(
|
||||
pushChangeHandler = MockChangeHandler.defaultHandler(),
|
||||
popChangeHandler = MockChangeHandler.defaultHandler()
|
||||
)
|
||||
)
|
||||
|
||||
val child = TestController()
|
||||
val childRouter = parent.getChildRouter(parent.getView()!!.findViewById(TestController.VIEW_ID))
|
||||
childRouter.setRoot(
|
||||
child.asTransaction(
|
||||
pushChangeHandler = SimpleSwapChangeHandler(),
|
||||
popChangeHandler = SimpleSwapChangeHandler()
|
||||
)
|
||||
)
|
||||
|
||||
val nextController = TestController()
|
||||
activityController.get().router.pushController(nextController.asTransaction())
|
||||
|
||||
val child2 = TestController()
|
||||
childRouter.pushController(child2.asTransaction())
|
||||
activityController.get().router.popCurrentController()
|
||||
|
||||
shadowOf(getMainLooper()).idle()
|
||||
|
||||
Assert.assertTrue(parent.isAttached)
|
||||
Assert.assertFalse(child.isAttached)
|
||||
Assert.assertTrue(child2.isAttached)
|
||||
}
|
||||
|
||||
private fun getPushHandler(
|
||||
expectedCallState: CallState,
|
||||
controller: TestController
|
||||
): MockChangeHandler {
|
||||
return MockChangeHandler.listeningChangeHandler(object : ChangeHandlerListener() {
|
||||
override fun willStartChange() {
|
||||
expectedCallState.contextAvailableCalls++
|
||||
expectedCallState.changeStartCalls++
|
||||
expectedCallState.createViewCalls++
|
||||
assertCalls(expectedCallState, controller)
|
||||
}
|
||||
|
||||
override fun didAttachOrDetach() {
|
||||
expectedCallState.attachCalls++
|
||||
assertCalls(expectedCallState, controller)
|
||||
}
|
||||
|
||||
override fun didEndChange() {
|
||||
expectedCallState.changeEndCalls++
|
||||
assertCalls(expectedCallState, controller)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun getPopHandler(
|
||||
expectedCallState: CallState,
|
||||
controller: TestController
|
||||
): MockChangeHandler {
|
||||
return MockChangeHandler.listeningChangeHandler(object : ChangeHandlerListener() {
|
||||
override fun willStartChange() {
|
||||
expectedCallState.changeStartCalls++
|
||||
assertCalls(expectedCallState, controller)
|
||||
}
|
||||
|
||||
override fun didAttachOrDetach() {
|
||||
expectedCallState.destroyViewCalls++
|
||||
expectedCallState.detachCalls++
|
||||
expectedCallState.contextUnavailableCalls++
|
||||
expectedCallState.destroyCalls++
|
||||
assertCalls(expectedCallState, controller)
|
||||
}
|
||||
|
||||
override fun didEndChange() {
|
||||
expectedCallState.changeEndCalls++
|
||||
assertCalls(expectedCallState, controller)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun assertCalls(callState: CallState, controller: TestController) {
|
||||
shadowOf(getMainLooper()).idle()
|
||||
|
||||
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
|
||||
)
|
||||
}
|
||||
|
||||
private fun attachLifecycleListener(controller: Controller?) {
|
||||
controller!!.addLifecycleListener(object : LifecycleListener() {
|
||||
override fun onChangeStart(
|
||||
controller: Controller,
|
||||
changeHandler: ControllerChangeHandler,
|
||||
changeType: ControllerChangeType
|
||||
) {
|
||||
currentCallState.changeStartCalls++
|
||||
}
|
||||
|
||||
override fun onChangeEnd(
|
||||
controller: Controller,
|
||||
changeHandler: ControllerChangeHandler,
|
||||
changeType: ControllerChangeType
|
||||
) {
|
||||
currentCallState.changeEndCalls++
|
||||
}
|
||||
|
||||
override fun postContextAvailable(controller: Controller, context: Context) {
|
||||
currentCallState.contextAvailableCalls++
|
||||
}
|
||||
|
||||
override fun postContextUnavailable(controller: Controller) {
|
||||
currentCallState.contextUnavailableCalls++
|
||||
}
|
||||
|
||||
override fun postCreateView(controller: Controller, view: View) {
|
||||
currentCallState.createViewCalls++
|
||||
}
|
||||
|
||||
override fun postAttach(controller: Controller, view: View) {
|
||||
currentCallState.attachCalls++
|
||||
}
|
||||
|
||||
override fun postDestroyView(controller: Controller) {
|
||||
currentCallState.destroyViewCalls++
|
||||
}
|
||||
|
||||
override fun postDetach(controller: Controller, view: View) {
|
||||
currentCallState.detachCalls++
|
||||
}
|
||||
|
||||
override fun postDestroy(controller: Controller) {
|
||||
currentCallState.destroyCalls++
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(controller: Controller, outState: Bundle) {
|
||||
currentCallState.saveInstanceStateCalls++
|
||||
}
|
||||
|
||||
override fun onRestoreInstanceState(controller: Controller, savedInstanceState: Bundle) {
|
||||
currentCallState.restoreInstanceStateCalls++
|
||||
}
|
||||
|
||||
override fun onSaveViewState(controller: Controller, outState: Bundle) {
|
||||
currentCallState.saveViewStateCalls++
|
||||
}
|
||||
|
||||
override fun onRestoreViewState(controller: Controller, savedViewState: Bundle) {
|
||||
currentCallState.restoreViewStateCalls++
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,412 +0,0 @@
|
||||
package com.bluelinelabs.conductor;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
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.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;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@Config(manifest = Config.NONE)
|
||||
public class ControllerTests {
|
||||
|
||||
private ActivityProxy activityProxy;
|
||||
private Router router;
|
||||
|
||||
public void createActivityController(Bundle savedInstanceState) {
|
||||
activityProxy = new ActivityProxy().create(savedInstanceState).start().resume();
|
||||
router = Conductor.attachRouter(activityProxy.getActivity(), activityProxy.getView(), savedInstanceState);
|
||||
if (!router.hasRootController()) {
|
||||
router.setRoot(RouterTransaction.with(new TestController()));
|
||||
}
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
createActivityController(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testViewRetention() {
|
||||
Controller controller = new TestController();
|
||||
controller.setRouter(router);
|
||||
|
||||
// Test View getting released w/ RELEASE_DETACH
|
||||
controller.setRetainViewMode(RetainViewMode.RELEASE_DETACH);
|
||||
assertNull(controller.getView());
|
||||
View view = controller.inflate(router.container);
|
||||
assertNotNull(controller.getView());
|
||||
ViewUtils.reportAttached(view, true);
|
||||
assertNotNull(controller.getView());
|
||||
ViewUtils.reportAttached(view, false);
|
||||
assertNull(controller.getView());
|
||||
|
||||
// Test View getting retained w/ RETAIN_DETACH
|
||||
controller.setRetainViewMode(RetainViewMode.RETAIN_DETACH);
|
||||
view = controller.inflate(router.container);
|
||||
assertNotNull(controller.getView());
|
||||
ViewUtils.reportAttached(view, true);
|
||||
assertNotNull(controller.getView());
|
||||
ViewUtils.reportAttached(view, false);
|
||||
assertNotNull(controller.getView());
|
||||
|
||||
// Ensure re-setting RELEASE_DETACH releases
|
||||
controller.setRetainViewMode(RetainViewMode.RELEASE_DETACH);
|
||||
assertNull(controller.getView());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testActivityResult() {
|
||||
TestController controller = new TestController();
|
||||
CallState expectedCallState = new CallState(true);
|
||||
|
||||
router.pushController(RouterTransaction.with(controller));
|
||||
|
||||
// Ensure that calling onActivityResult w/o requesting a result doesn't do anything
|
||||
router.onActivityResult(1, Activity.RESULT_OK, null);
|
||||
assertCalls(expectedCallState, controller);
|
||||
|
||||
// Ensure starting an activity for result gets us the result back
|
||||
controller.startActivityForResult(new Intent("action"), 1);
|
||||
router.onActivityResult(1, Activity.RESULT_OK, null);
|
||||
expectedCallState.onActivityResultCalls++;
|
||||
assertCalls(expectedCallState, controller);
|
||||
|
||||
// Ensure requesting a result w/o calling startActivityForResult works
|
||||
controller.registerForActivityResult(2);
|
||||
router.onActivityResult(2, Activity.RESULT_OK, null);
|
||||
expectedCallState.onActivityResultCalls++;
|
||||
assertCalls(expectedCallState, controller);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testActivityResultForChild() {
|
||||
TestController parent = new TestController();
|
||||
TestController child = new TestController();
|
||||
|
||||
router.pushController(RouterTransaction.with(parent));
|
||||
parent.getChildRouter((ViewGroup)parent.getView().findViewById(TestController.VIEW_ID))
|
||||
.setRoot(RouterTransaction.with(child));
|
||||
|
||||
CallState childExpectedCallState = new CallState(true);
|
||||
CallState parentExpectedCallState = new CallState(true);
|
||||
|
||||
// Ensure that calling onActivityResult w/o requesting a result doesn't do anything
|
||||
router.onActivityResult(1, Activity.RESULT_OK, null);
|
||||
assertCalls(childExpectedCallState, child);
|
||||
assertCalls(parentExpectedCallState, parent);
|
||||
|
||||
// Ensure starting an activity for result gets us the result back
|
||||
child.startActivityForResult(new Intent("action"), 1);
|
||||
router.onActivityResult(1, Activity.RESULT_OK, null);
|
||||
childExpectedCallState.onActivityResultCalls++;
|
||||
assertCalls(childExpectedCallState, child);
|
||||
assertCalls(parentExpectedCallState, parent);
|
||||
|
||||
// Ensure requesting a result w/o calling startActivityForResult works
|
||||
child.registerForActivityResult(2);
|
||||
router.onActivityResult(2, Activity.RESULT_OK, null);
|
||||
childExpectedCallState.onActivityResultCalls++;
|
||||
assertCalls(childExpectedCallState, child);
|
||||
assertCalls(parentExpectedCallState, parent);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPermissionResult() {
|
||||
final String[] requestedPermissions = new String[] {"test"};
|
||||
|
||||
TestController controller = new TestController();
|
||||
CallState expectedCallState = new CallState(true);
|
||||
|
||||
router.pushController(RouterTransaction.with(controller));
|
||||
|
||||
// Ensure that calling handleRequestedPermission w/o requesting a result doesn't do anything
|
||||
router.onRequestPermissionsResult("anotherId", 1, requestedPermissions, new int[] {1});
|
||||
assertCalls(expectedCallState, controller);
|
||||
|
||||
// Ensure requesting the permission gets us the result back
|
||||
try {
|
||||
controller.requestPermissions(requestedPermissions, 1);
|
||||
} catch (NoSuchMethodError ignored) { }
|
||||
|
||||
router.onRequestPermissionsResult(controller.getInstanceId(), 1, requestedPermissions, new int[] {1});
|
||||
expectedCallState.onRequestPermissionsResultCalls++;
|
||||
assertCalls(expectedCallState, controller);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPermissionResultForChild() {
|
||||
final String[] requestedPermissions = new String[] {"test"};
|
||||
|
||||
TestController parent = new TestController();
|
||||
TestController child = new TestController();
|
||||
|
||||
router.pushController(RouterTransaction.with(parent));
|
||||
parent.getChildRouter((ViewGroup)parent.getView().findViewById(TestController.VIEW_ID))
|
||||
.setRoot(RouterTransaction.with(child));
|
||||
|
||||
CallState childExpectedCallState = new CallState(true);
|
||||
CallState parentExpectedCallState = new CallState(true);
|
||||
|
||||
// Ensure that calling handleRequestedPermission w/o requesting a result doesn't do anything
|
||||
router.onRequestPermissionsResult("anotherId", 1, requestedPermissions, new int[] {1});
|
||||
assertCalls(childExpectedCallState, child);
|
||||
assertCalls(parentExpectedCallState, parent);
|
||||
|
||||
// Ensure requesting the permission gets us the result back
|
||||
try {
|
||||
child.requestPermissions(requestedPermissions, 1);
|
||||
} catch (NoSuchMethodError ignored) { }
|
||||
|
||||
router.onRequestPermissionsResult(child.getInstanceId(), 1, requestedPermissions, new int[] {1});
|
||||
childExpectedCallState.onRequestPermissionsResultCalls++;
|
||||
assertCalls(childExpectedCallState, child);
|
||||
assertCalls(parentExpectedCallState, parent);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOptionsMenu() {
|
||||
TestController controller = new TestController();
|
||||
CallState expectedCallState = new CallState(true);
|
||||
|
||||
router.pushController(RouterTransaction.with(controller));
|
||||
|
||||
// Ensure that calling onCreateOptionsMenu w/o declaring that we have one doesn't do anything
|
||||
router.onCreateOptionsMenu(null, null);
|
||||
assertCalls(expectedCallState, controller);
|
||||
|
||||
// Ensure calling onCreateOptionsMenu with a menu works
|
||||
controller.setHasOptionsMenu(true);
|
||||
|
||||
// Ensure it'll still get called back next time onCreateOptionsMenu is called
|
||||
router.onCreateOptionsMenu(null, null);
|
||||
expectedCallState.createOptionsMenuCalls++;
|
||||
assertCalls(expectedCallState, controller);
|
||||
|
||||
// Ensure we stop getting them when we hide it
|
||||
controller.setOptionsMenuHidden(true);
|
||||
router.onCreateOptionsMenu(null, null);
|
||||
assertCalls(expectedCallState, controller);
|
||||
|
||||
// Ensure we get the callback them when we un-hide it
|
||||
controller.setOptionsMenuHidden(false);
|
||||
router.onCreateOptionsMenu(null, null);
|
||||
expectedCallState.createOptionsMenuCalls++;
|
||||
assertCalls(expectedCallState, controller);
|
||||
|
||||
// Ensure we don't get the callback when we no longer have a menu
|
||||
controller.setHasOptionsMenu(false);
|
||||
router.onCreateOptionsMenu(null, null);
|
||||
assertCalls(expectedCallState, controller);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOptionsMenuForChild() {
|
||||
TestController parent = new TestController();
|
||||
TestController child = new TestController();
|
||||
|
||||
router.pushController(RouterTransaction.with(parent));
|
||||
parent.getChildRouter((ViewGroup)parent.getView().findViewById(TestController.VIEW_ID))
|
||||
.setRoot(RouterTransaction.with(child));
|
||||
|
||||
CallState childExpectedCallState = new CallState(true);
|
||||
CallState parentExpectedCallState = new CallState(true);
|
||||
|
||||
// Ensure that calling onCreateOptionsMenu w/o declaring that we have one doesn't do anything
|
||||
router.onCreateOptionsMenu(null, null);
|
||||
assertCalls(childExpectedCallState, child);
|
||||
assertCalls(parentExpectedCallState, parent);
|
||||
|
||||
// Ensure calling onCreateOptionsMenu with a menu works
|
||||
child.setHasOptionsMenu(true);
|
||||
|
||||
// Ensure it'll still get called back next time onCreateOptionsMenu is called
|
||||
router.onCreateOptionsMenu(null, null);
|
||||
childExpectedCallState.createOptionsMenuCalls++;
|
||||
assertCalls(childExpectedCallState, child);
|
||||
assertCalls(parentExpectedCallState, parent);
|
||||
|
||||
// Ensure we stop getting them when we hide it
|
||||
child.setOptionsMenuHidden(true);
|
||||
router.onCreateOptionsMenu(null, null);
|
||||
assertCalls(childExpectedCallState, child);
|
||||
assertCalls(parentExpectedCallState, parent);
|
||||
|
||||
// Ensure we get the callback them when we un-hide it
|
||||
child.setOptionsMenuHidden(false);
|
||||
router.onCreateOptionsMenu(null, null);
|
||||
childExpectedCallState.createOptionsMenuCalls++;
|
||||
assertCalls(childExpectedCallState, child);
|
||||
assertCalls(parentExpectedCallState, parent);
|
||||
|
||||
// Ensure we don't get the callback when we no longer have a menu
|
||||
child.setHasOptionsMenu(false);
|
||||
router.onCreateOptionsMenu(null, null);
|
||||
assertCalls(childExpectedCallState, child);
|
||||
assertCalls(parentExpectedCallState, parent);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddRemoveChildControllers() {
|
||||
TestController parent = new TestController();
|
||||
TestController child1 = new TestController();
|
||||
TestController child2 = new TestController();
|
||||
|
||||
router.pushController(RouterTransaction.with(parent));
|
||||
|
||||
assertEquals(0, parent.getChildRouters().size());
|
||||
assertNull(child1.getParentController());
|
||||
assertNull(child2.getParentController());
|
||||
|
||||
Router childRouter = parent.getChildRouter((ViewGroup)parent.getView().findViewById(TestController.VIEW_ID));
|
||||
childRouter.setPopsLastView(true);
|
||||
childRouter.setRoot(RouterTransaction.with(child1));
|
||||
|
||||
assertEquals(1, parent.getChildRouters().size());
|
||||
assertEquals(childRouter, parent.getChildRouters().get(0));
|
||||
assertEquals(1, childRouter.getBackstackSize());
|
||||
assertEquals(child1, childRouter.getControllers().get(0));
|
||||
assertEquals(parent, child1.getParentController());
|
||||
assertNull(child2.getParentController());
|
||||
|
||||
childRouter = parent.getChildRouter((ViewGroup)parent.getView().findViewById(TestController.VIEW_ID));
|
||||
childRouter.pushController(RouterTransaction.with(child2));
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
assertEquals(1, parent.getChildRouters().size());
|
||||
assertEquals(childRouter, parent.getChildRouters().get(0));
|
||||
assertEquals(0, childRouter.getBackstackSize());
|
||||
assertNull(child1.getParentController());
|
||||
assertNull(child2.getParentController());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddRemoveChildRouters() {
|
||||
TestController parent = new TestController();
|
||||
|
||||
TestController child1 = new TestController();
|
||||
TestController child2 = new TestController();
|
||||
|
||||
router.pushController(RouterTransaction.with(parent));
|
||||
|
||||
assertEquals(0, parent.getChildRouters().size());
|
||||
assertNull(child1.getParentController());
|
||||
assertNull(child2.getParentController());
|
||||
|
||||
Router childRouter1 = parent.getChildRouter((ViewGroup)parent.getView().findViewById(TestController.CHILD_VIEW_ID_1));
|
||||
Router childRouter2 = parent.getChildRouter((ViewGroup)parent.getView().findViewById(TestController.CHILD_VIEW_ID_2));
|
||||
|
||||
childRouter1.setRoot(RouterTransaction.with(child1));
|
||||
childRouter2.setRoot(RouterTransaction.with(child2));
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
assertEquals(0, parent.getChildRouters().size());
|
||||
assertEquals(0, childRouter1.getBackstackSize());
|
||||
assertEquals(0, childRouter2.getBackstackSize());
|
||||
assertNull(child1.getParentController());
|
||||
assertNull(child2.getParentController());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRestoredChildRouterBackstack() {
|
||||
TestController parent = new TestController();
|
||||
router.pushController(RouterTransaction.with(parent));
|
||||
ViewUtils.reportAttached(parent.getView(), true);
|
||||
|
||||
RouterTransaction childTransaction1 = RouterTransaction.with(new TestController());
|
||||
RouterTransaction childTransaction2 = RouterTransaction.with(new TestController());
|
||||
|
||||
Router childRouter = parent.getChildRouter((ViewGroup)parent.getView().findViewById(TestController.CHILD_VIEW_ID_1));
|
||||
childRouter.setPopsLastView(true);
|
||||
childRouter.setRoot(childTransaction1);
|
||||
childRouter.pushController(childTransaction2);
|
||||
|
||||
Bundle savedState = new Bundle();
|
||||
childRouter.saveInstanceState(savedState);
|
||||
parent.removeChildRouter(childRouter);
|
||||
|
||||
childRouter = parent.getChildRouter((ViewGroup)parent.getView().findViewById(TestController.CHILD_VIEW_ID_1));
|
||||
assertEquals(0, childRouter.getBackstackSize());
|
||||
|
||||
childRouter.restoreInstanceState(savedState);
|
||||
childRouter.rebindIfNeeded();
|
||||
|
||||
assertEquals(2, childRouter.getBackstackSize());
|
||||
|
||||
RouterTransaction restoredChildTransaction1 = childRouter.getBackstack().get(0);
|
||||
RouterTransaction restoredChildTransaction2 = childRouter.getBackstack().get(1);
|
||||
|
||||
assertEquals(childTransaction1.getTransactionIndex(), restoredChildTransaction1.getTransactionIndex());
|
||||
assertEquals(childTransaction1.controller().getInstanceId(), restoredChildTransaction1.controller().getInstanceId());
|
||||
assertEquals(childTransaction2.getTransactionIndex(), restoredChildTransaction2.getTransactionIndex());
|
||||
assertEquals(childTransaction2.controller().getInstanceId(), restoredChildTransaction2.controller().getInstanceId());
|
||||
|
||||
assertTrue(parent.handleBack());
|
||||
assertEquals(1, childRouter.getBackstackSize());
|
||||
assertEquals(restoredChildTransaction1, childRouter.getBackstack().get(0));
|
||||
|
||||
assertTrue(parent.handleBack());
|
||||
assertEquals(0, childRouter.getBackstackSize());
|
||||
}
|
||||
|
||||
private void assertCalls(CallState callState, TestController controller) {
|
||||
assertEquals("Expected call counts and controller call counts do not match.", callState, controller.currentCallState);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,524 @@
|
||||
package com.bluelinelabs.conductor
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.os.Looper
|
||||
import android.view.KeyEvent
|
||||
import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.SubMenu
|
||||
import com.bluelinelabs.conductor.Controller.RetainViewMode
|
||||
import com.bluelinelabs.conductor.util.AttachFakingFrameLayout
|
||||
import com.bluelinelabs.conductor.util.CallState
|
||||
import com.bluelinelabs.conductor.util.TestActivity
|
||||
import com.bluelinelabs.conductor.util.ViewUtils
|
||||
import org.junit.Assert
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.Robolectric
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import org.robolectric.Shadows.shadowOf
|
||||
import org.robolectric.annotation.Config
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
@Config(manifest = Config.NONE)
|
||||
class ControllerTests {
|
||||
|
||||
private val router = Robolectric.buildActivity(TestActivity::class.java)
|
||||
.setup()
|
||||
.get()
|
||||
.router
|
||||
|
||||
@Test
|
||||
fun testViewRetention() {
|
||||
val controller = TestController()
|
||||
controller.setRouter(router)
|
||||
|
||||
// Test View getting released w/ RELEASE_DETACH
|
||||
controller.retainViewMode = RetainViewMode.RELEASE_DETACH
|
||||
Assert.assertNull(controller.getView())
|
||||
var view = controller.inflate(router.container)
|
||||
Assert.assertNotNull(controller.getView())
|
||||
ViewUtils.reportAttached(view, true)
|
||||
Assert.assertNotNull(controller.getView())
|
||||
ViewUtils.reportAttached(view, false)
|
||||
Assert.assertNull(controller.getView())
|
||||
|
||||
// Test View getting retained w/ RETAIN_DETACH
|
||||
controller.retainViewMode = RetainViewMode.RETAIN_DETACH
|
||||
view = controller.inflate(router.container)
|
||||
Assert.assertNotNull(controller.getView())
|
||||
ViewUtils.reportAttached(view, true)
|
||||
Assert.assertNotNull(controller.getView())
|
||||
ViewUtils.reportAttached(view, false)
|
||||
Assert.assertNotNull(controller.getView())
|
||||
|
||||
// Ensure re-setting RELEASE_DETACH releases
|
||||
controller.retainViewMode = RetainViewMode.RELEASE_DETACH
|
||||
Assert.assertNull(controller.getView())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testActivityResult() {
|
||||
val controller = TestController()
|
||||
val expectedCallState = CallState(true)
|
||||
router.pushController(controller.asTransaction())
|
||||
|
||||
// Ensure that calling onActivityResult w/o requesting a result doesn't do anything
|
||||
router.onActivityResult(1, Activity.RESULT_OK, null)
|
||||
assertCalls(expectedCallState, controller)
|
||||
|
||||
// Ensure starting an activity for result gets us the result back
|
||||
controller.startActivityForResult(Intent("action"), 1)
|
||||
router.onActivityResult(1, Activity.RESULT_OK, null)
|
||||
expectedCallState.onActivityResultCalls++
|
||||
assertCalls(expectedCallState, controller)
|
||||
|
||||
// Ensure requesting a result w/o calling startActivityForResult works
|
||||
controller.registerForActivityResult(2)
|
||||
router.onActivityResult(2, Activity.RESULT_OK, null)
|
||||
expectedCallState.onActivityResultCalls++
|
||||
assertCalls(expectedCallState, controller)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testActivityResultForChild() {
|
||||
val parent = TestController()
|
||||
val child = TestController()
|
||||
router.pushController(parent.asTransaction())
|
||||
val childContainer = parent.view!!.findViewById<AttachFakingFrameLayout>(TestController.VIEW_ID)
|
||||
childContainer.setAttached(true)
|
||||
parent.getChildRouter(childContainer)
|
||||
.setRoot(child.asTransaction())
|
||||
val childExpectedCallState = CallState(true)
|
||||
val parentExpectedCallState = CallState(true)
|
||||
|
||||
// Ensure that calling onActivityResult w/o requesting a result doesn't do anything
|
||||
router.onActivityResult(1, Activity.RESULT_OK, null)
|
||||
assertCalls(childExpectedCallState, child)
|
||||
assertCalls(parentExpectedCallState, parent)
|
||||
|
||||
// Ensure starting an activity for result gets us the result back
|
||||
child.startActivityForResult(Intent("action"), 1)
|
||||
router.onActivityResult(1, Activity.RESULT_OK, null)
|
||||
childExpectedCallState.onActivityResultCalls++
|
||||
assertCalls(childExpectedCallState, child)
|
||||
assertCalls(parentExpectedCallState, parent)
|
||||
|
||||
// Ensure requesting a result w/o calling startActivityForResult works
|
||||
child.registerForActivityResult(2)
|
||||
router.onActivityResult(2, Activity.RESULT_OK, null)
|
||||
childExpectedCallState.onActivityResultCalls++
|
||||
assertCalls(childExpectedCallState, child)
|
||||
assertCalls(parentExpectedCallState, parent)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testPermissionResult() {
|
||||
val requestedPermissions = arrayOf("test")
|
||||
val controller = TestController()
|
||||
val expectedCallState = CallState(true)
|
||||
router.pushController(controller.asTransaction())
|
||||
|
||||
// Ensure that calling handleRequestedPermission w/o requesting a result doesn't do anything
|
||||
router.onRequestPermissionsResult("anotherId", 1, requestedPermissions, intArrayOf(1))
|
||||
assertCalls(expectedCallState, controller)
|
||||
|
||||
// Ensure requesting the permission gets us the result back
|
||||
try {
|
||||
controller.requestPermissions(requestedPermissions, 1)
|
||||
} catch (ignored: NoSuchMethodError) { }
|
||||
|
||||
router.onRequestPermissionsResult(
|
||||
controller.instanceId,
|
||||
1,
|
||||
requestedPermissions,
|
||||
intArrayOf(1)
|
||||
)
|
||||
expectedCallState.onRequestPermissionsResultCalls++
|
||||
assertCalls(expectedCallState, controller)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testPermissionResultForChild() {
|
||||
val requestedPermissions = arrayOf("test")
|
||||
val parent = TestController()
|
||||
val child = TestController()
|
||||
router.pushController(parent.asTransaction())
|
||||
val childContainer = parent.view!!.findViewById<AttachFakingFrameLayout>(TestController.VIEW_ID)
|
||||
childContainer.setAttached(true)
|
||||
parent.getChildRouter(childContainer)
|
||||
.setRoot(child.asTransaction())
|
||||
val childExpectedCallState = CallState(true)
|
||||
val parentExpectedCallState = CallState(true)
|
||||
|
||||
// Ensure that calling handleRequestedPermission w/o requesting a result doesn't do anything
|
||||
router.onRequestPermissionsResult("anotherId", 1, requestedPermissions, intArrayOf(1))
|
||||
assertCalls(childExpectedCallState, child)
|
||||
assertCalls(parentExpectedCallState, parent)
|
||||
|
||||
// Ensure requesting the permission gets us the result back
|
||||
try {
|
||||
child.requestPermissions(requestedPermissions, 1)
|
||||
} catch (ignored: NoSuchMethodError) { }
|
||||
|
||||
router.onRequestPermissionsResult(child.instanceId, 1, requestedPermissions, intArrayOf(1))
|
||||
childExpectedCallState.onRequestPermissionsResultCalls++
|
||||
assertCalls(childExpectedCallState, child)
|
||||
assertCalls(parentExpectedCallState, parent)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOptionsMenu() {
|
||||
val controller = TestController()
|
||||
val expectedCallState = CallState(true)
|
||||
router.pushController(controller.asTransaction())
|
||||
|
||||
// Ensure that calling onCreateOptionsMenu w/o declaring that we have one doesn't do anything
|
||||
router.onCreateOptionsMenu(menu(), menuInflater(router.activity!!))
|
||||
assertCalls(expectedCallState, controller)
|
||||
|
||||
// Ensure calling onCreateOptionsMenu with a menu works
|
||||
controller.setHasOptionsMenu(true)
|
||||
expectedCallState.createOptionsMenuCalls++
|
||||
assertCalls(expectedCallState, controller)
|
||||
|
||||
// Ensure it'll still get called back next time onCreateOptionsMenu is called
|
||||
router.onCreateOptionsMenu(menu(), menuInflater(router.activity!!))
|
||||
expectedCallState.createOptionsMenuCalls++
|
||||
assertCalls(expectedCallState, controller)
|
||||
|
||||
// Ensure we stop getting them when we hide it
|
||||
controller.setOptionsMenuHidden(true)
|
||||
router.onCreateOptionsMenu(menu(), menuInflater(router.activity!!))
|
||||
assertCalls(expectedCallState, controller)
|
||||
|
||||
// Ensure we get the callback them when we un-hide it
|
||||
controller.setOptionsMenuHidden(false)
|
||||
expectedCallState.createOptionsMenuCalls++
|
||||
assertCalls(expectedCallState, controller)
|
||||
|
||||
router.onCreateOptionsMenu(menu(), menuInflater(router.activity!!))
|
||||
expectedCallState.createOptionsMenuCalls++
|
||||
assertCalls(expectedCallState, controller)
|
||||
|
||||
// Ensure we don't get the callback when we no longer have a menu
|
||||
controller.setHasOptionsMenu(false)
|
||||
router.onCreateOptionsMenu(menu(), menuInflater(router.activity!!))
|
||||
assertCalls(expectedCallState, controller)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOptionsMenuForChild() {
|
||||
val parent = TestController()
|
||||
val child = TestController()
|
||||
router.pushController(parent.asTransaction())
|
||||
val childContainer = parent.view!!.findViewById<AttachFakingFrameLayout>(TestController.VIEW_ID)
|
||||
childContainer.setAttached(true)
|
||||
parent.getChildRouter(childContainer)
|
||||
.setRoot(child.asTransaction())
|
||||
val childExpectedCallState = CallState(true)
|
||||
val parentExpectedCallState = CallState(true)
|
||||
|
||||
// Ensure that calling onCreateOptionsMenu w/o declaring that we have one doesn't do anything
|
||||
router.onCreateOptionsMenu(menu(), menuInflater(router.activity!!))
|
||||
assertCalls(childExpectedCallState, child)
|
||||
assertCalls(parentExpectedCallState, parent)
|
||||
|
||||
// Ensure calling onCreateOptionsMenu with a menu works
|
||||
child.setHasOptionsMenu(true)
|
||||
childExpectedCallState.createOptionsMenuCalls++
|
||||
assertCalls(childExpectedCallState, child)
|
||||
assertCalls(parentExpectedCallState, parent)
|
||||
|
||||
// Ensure it'll still get called back next time onCreateOptionsMenu is called
|
||||
router.onCreateOptionsMenu(menu(), menuInflater(router.activity!!))
|
||||
childExpectedCallState.createOptionsMenuCalls++
|
||||
assertCalls(childExpectedCallState, child)
|
||||
assertCalls(parentExpectedCallState, parent)
|
||||
|
||||
// Ensure we stop getting them when we hide it
|
||||
child.setOptionsMenuHidden(true)
|
||||
router.onCreateOptionsMenu(menu(), menuInflater(router.activity!!))
|
||||
assertCalls(childExpectedCallState, child)
|
||||
assertCalls(parentExpectedCallState, parent)
|
||||
|
||||
// Ensure we get the callback them when we un-hide it
|
||||
child.setOptionsMenuHidden(false)
|
||||
childExpectedCallState.createOptionsMenuCalls++
|
||||
assertCalls(childExpectedCallState, child)
|
||||
assertCalls(parentExpectedCallState, parent)
|
||||
|
||||
router.onCreateOptionsMenu(menu(), menuInflater(router.activity!!))
|
||||
childExpectedCallState.createOptionsMenuCalls++
|
||||
assertCalls(childExpectedCallState, child)
|
||||
assertCalls(parentExpectedCallState, parent)
|
||||
|
||||
// Ensure we don't get the callback when we no longer have a menu
|
||||
child.setHasOptionsMenu(false)
|
||||
router.onCreateOptionsMenu(menu(), menuInflater(router.activity!!))
|
||||
assertCalls(childExpectedCallState, child)
|
||||
assertCalls(parentExpectedCallState, parent)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testAddRemoveChildControllers() {
|
||||
val parent = TestController()
|
||||
val child1 = TestController()
|
||||
val child2 = TestController()
|
||||
router.pushController(parent.asTransaction())
|
||||
Assert.assertEquals(0, parent.childRouters.size)
|
||||
Assert.assertNull(child1.parentController)
|
||||
Assert.assertNull(child2.parentController)
|
||||
|
||||
var childRouter = parent.getChildRouter(parent.view!!.findViewById(TestController.VIEW_ID))
|
||||
childRouter.setPopRootControllerMode(Router.PopRootControllerMode.POP_ROOT_CONTROLLER_AND_VIEW)
|
||||
childRouter.setRoot(child1.asTransaction())
|
||||
Assert.assertEquals(1, parent.childRouters.size)
|
||||
Assert.assertEquals(childRouter, parent.childRouters[0])
|
||||
Assert.assertEquals(1, childRouter.backstackSize)
|
||||
Assert.assertEquals(child1, childRouter.controllers[0])
|
||||
Assert.assertEquals(parent, child1.parentController)
|
||||
Assert.assertNull(child2.parentController)
|
||||
|
||||
childRouter = parent.getChildRouter(parent.view!!.findViewById(TestController.VIEW_ID))
|
||||
childRouter.pushController(child2.asTransaction())
|
||||
Assert.assertEquals(1, parent.childRouters.size)
|
||||
Assert.assertEquals(childRouter, parent.childRouters[0])
|
||||
Assert.assertEquals(2, childRouter.backstackSize)
|
||||
Assert.assertEquals(child1, childRouter.controllers[0])
|
||||
Assert.assertEquals(child2, childRouter.controllers[1])
|
||||
Assert.assertEquals(parent, child1.parentController)
|
||||
Assert.assertEquals(parent, child2.parentController)
|
||||
|
||||
childRouter.popController(child2)
|
||||
Assert.assertEquals(1, parent.childRouters.size)
|
||||
Assert.assertEquals(childRouter, parent.childRouters[0])
|
||||
Assert.assertEquals(1, childRouter.backstackSize)
|
||||
Assert.assertEquals(child1, childRouter.controllers[0])
|
||||
Assert.assertEquals(parent, child1.parentController)
|
||||
Assert.assertNull(child2.parentController)
|
||||
|
||||
childRouter.popController(child1)
|
||||
shadowOf(Looper.getMainLooper()).idle()
|
||||
Assert.assertEquals(1, parent.childRouters.size)
|
||||
Assert.assertEquals(childRouter, parent.childRouters[0])
|
||||
Assert.assertEquals(0, childRouter.backstackSize)
|
||||
Assert.assertNull(child1.parentController)
|
||||
Assert.assertNull(child2.parentController)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testAddRemoveChildRouters() {
|
||||
val parent = TestController()
|
||||
val child1 = TestController()
|
||||
val child2 = TestController()
|
||||
router.pushController(parent.asTransaction())
|
||||
Assert.assertEquals(0, parent.childRouters.size)
|
||||
Assert.assertNull(child1.parentController)
|
||||
Assert.assertNull(child2.parentController)
|
||||
|
||||
val childRouter1 = parent.getChildRouter(parent.view!!.findViewById(TestController.CHILD_VIEW_ID_1))
|
||||
val childRouter2 = parent.getChildRouter(parent.view!!.findViewById(TestController.CHILD_VIEW_ID_2))
|
||||
childRouter1.setRoot(child1.asTransaction())
|
||||
childRouter2.setRoot(child2.asTransaction())
|
||||
Assert.assertEquals(2, parent.childRouters.size)
|
||||
Assert.assertEquals(childRouter1, parent.childRouters[0])
|
||||
Assert.assertEquals(childRouter2, parent.childRouters[1])
|
||||
Assert.assertEquals(1, childRouter1.backstackSize)
|
||||
Assert.assertEquals(1, childRouter2.backstackSize)
|
||||
Assert.assertEquals(child1, childRouter1.controllers[0])
|
||||
Assert.assertEquals(child2, childRouter2.controllers[0])
|
||||
Assert.assertEquals(parent, child1.parentController)
|
||||
Assert.assertEquals(parent, child2.parentController)
|
||||
|
||||
parent.removeChildRouter(childRouter2)
|
||||
shadowOf(Looper.getMainLooper()).idle()
|
||||
Assert.assertEquals(1, parent.childRouters.size)
|
||||
Assert.assertEquals(childRouter1, parent.childRouters[0])
|
||||
Assert.assertEquals(1, childRouter1.backstackSize)
|
||||
Assert.assertEquals(0, childRouter2.backstackSize)
|
||||
Assert.assertEquals(child1, childRouter1.controllers[0])
|
||||
Assert.assertEquals(parent, child1.parentController)
|
||||
Assert.assertNull(child2.parentController)
|
||||
parent.removeChildRouter(childRouter1)
|
||||
Assert.assertEquals(0, parent.childRouters.size)
|
||||
Assert.assertEquals(0, childRouter1.backstackSize)
|
||||
Assert.assertEquals(0, childRouter2.backstackSize)
|
||||
Assert.assertNull(child1.parentController)
|
||||
Assert.assertNull(child2.parentController)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testRestoredChildRouterBackstack() {
|
||||
val parent = TestController()
|
||||
router.pushController(parent.asTransaction())
|
||||
ViewUtils.reportAttached(parent.view, true)
|
||||
|
||||
val childTransaction1 = TestController().asTransaction()
|
||||
val childTransaction2 = TestController().asTransaction()
|
||||
var childRouter = parent.getChildRouter(parent.view!!.findViewById(TestController.CHILD_VIEW_ID_1))
|
||||
childRouter.setPopRootControllerMode(Router.PopRootControllerMode.POP_ROOT_CONTROLLER_AND_VIEW)
|
||||
childRouter.setRoot(childTransaction1)
|
||||
childRouter.pushController(childTransaction2)
|
||||
val savedState = Bundle()
|
||||
childRouter.saveInstanceState(savedState)
|
||||
parent.removeChildRouter(childRouter)
|
||||
childRouter = parent.getChildRouter(parent.view!!.findViewById(TestController.CHILD_VIEW_ID_1))
|
||||
Assert.assertEquals(0, childRouter.backstackSize)
|
||||
|
||||
childRouter.restoreInstanceState(savedState)
|
||||
childRouter.rebindIfNeeded()
|
||||
Assert.assertEquals(2, childRouter.backstackSize)
|
||||
val restoredChildTransaction1 = childRouter.getBackstack()[0]
|
||||
val restoredChildTransaction2 = childRouter.getBackstack()[1]
|
||||
Assert.assertEquals(
|
||||
childTransaction1.transactionIndex,
|
||||
restoredChildTransaction1.transactionIndex
|
||||
)
|
||||
Assert.assertEquals(
|
||||
childTransaction1.controller.getInstanceId(),
|
||||
restoredChildTransaction1.controller.getInstanceId()
|
||||
)
|
||||
Assert.assertEquals(
|
||||
childTransaction2.transactionIndex,
|
||||
restoredChildTransaction2.transactionIndex
|
||||
)
|
||||
Assert.assertEquals(
|
||||
childTransaction2.controller.getInstanceId(),
|
||||
restoredChildTransaction2.controller.getInstanceId()
|
||||
)
|
||||
Assert.assertTrue(parent.handleBack())
|
||||
Assert.assertEquals(1, childRouter.backstackSize)
|
||||
Assert.assertEquals(restoredChildTransaction1, childRouter.getBackstack()[0])
|
||||
Assert.assertTrue(parent.handleBack())
|
||||
Assert.assertEquals(0, childRouter.backstackSize)
|
||||
}
|
||||
|
||||
private fun assertCalls(callState: CallState, controller: TestController) {
|
||||
shadowOf(Looper.getMainLooper()).idle()
|
||||
|
||||
Assert.assertEquals(
|
||||
"Expected call counts and controller call counts do not match.",
|
||||
callState,
|
||||
controller.currentCallState
|
||||
)
|
||||
}
|
||||
|
||||
private fun menu(): Menu {
|
||||
return object : Menu {
|
||||
override fun add(p0: CharSequence?): MenuItem {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun add(p0: Int): MenuItem {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun add(p0: Int, p1: Int, p2: Int, p3: CharSequence?): MenuItem {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun add(p0: Int, p1: Int, p2: Int, p3: Int): MenuItem {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun addSubMenu(p0: CharSequence?): SubMenu {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun addSubMenu(p0: Int): SubMenu {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun addSubMenu(p0: Int, p1: Int, p2: Int, p3: CharSequence?): SubMenu {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun addSubMenu(p0: Int, p1: Int, p2: Int, p3: Int): SubMenu {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun addIntentOptions(
|
||||
p0: Int,
|
||||
p1: Int,
|
||||
p2: Int,
|
||||
p3: ComponentName?,
|
||||
p4: Array<out Intent>?,
|
||||
p5: Intent?,
|
||||
p6: Int,
|
||||
p7: Array<out MenuItem>?
|
||||
): Int {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun removeItem(p0: Int) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun removeGroup(p0: Int) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun clear() {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun setGroupCheckable(p0: Int, p1: Boolean, p2: Boolean) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun setGroupVisible(p0: Int, p1: Boolean) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun setGroupEnabled(p0: Int, p1: Boolean) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun hasVisibleItems(): Boolean {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun findItem(p0: Int): MenuItem {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun size(): Int {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun getItem(p0: Int): MenuItem {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun performShortcut(p0: Int, p1: KeyEvent?, p2: Int): Boolean {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun isShortcutKey(p0: Int, p1: KeyEvent?): Boolean {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun performIdentifierAction(p0: Int, p1: Int): Boolean {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun setQwertyMode(p0: Boolean) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun menuInflater(context: Context): MenuInflater {
|
||||
return MenuInflater(context)
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,6 @@ import android.os.Bundle;
|
||||
|
||||
import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler;
|
||||
import com.bluelinelabs.conductor.changehandler.VerticalChangeHandler;
|
||||
import com.bluelinelabs.conductor.util.TestController;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
@@ -1,358 +0,0 @@
|
||||
package com.bluelinelabs.conductor;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.bluelinelabs.conductor.internal.LifecycleHandler;
|
||||
import com.bluelinelabs.conductor.util.ActivityProxy;
|
||||
import com.bluelinelabs.conductor.util.AttachFakingFrameLayout;
|
||||
import com.bluelinelabs.conductor.util.MockChangeHandler;
|
||||
import com.bluelinelabs.conductor.util.TestController;
|
||||
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.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@Config(manifest = Config.NONE)
|
||||
public class ReattachCaseTests {
|
||||
|
||||
private ActivityProxy activityProxy;
|
||||
private Router router;
|
||||
|
||||
public void createActivityController(Bundle savedInstanceState) {
|
||||
activityProxy = new ActivityProxy().create(savedInstanceState).start().resume();
|
||||
router = Conductor.attachRouter(activityProxy.getActivity(), activityProxy.getView(), savedInstanceState);
|
||||
if (!router.hasRootController()) {
|
||||
router.setRoot(RouterTransaction.with(new TestController()));
|
||||
}
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
createActivityController(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNeedsAttachingOnPauseAndOrientation() {
|
||||
final TestController controllerA = new TestController();
|
||||
final TestController controllerB = new TestController();
|
||||
|
||||
router.pushController(RouterTransaction.with(controllerA)
|
||||
.pushChangeHandler(MockChangeHandler.defaultHandler())
|
||||
.popChangeHandler(MockChangeHandler.defaultHandler()));
|
||||
|
||||
assertTrue(controllerA.isAttached());
|
||||
assertFalse(controllerB.isAttached());
|
||||
|
||||
sleepWakeDevice();
|
||||
|
||||
assertTrue(controllerA.isAttached());
|
||||
assertFalse(controllerB.isAttached());
|
||||
|
||||
router.pushController(RouterTransaction.with(controllerB)
|
||||
.pushChangeHandler(MockChangeHandler.defaultHandler())
|
||||
.popChangeHandler(MockChangeHandler.defaultHandler()));
|
||||
|
||||
assertFalse(controllerA.isAttached());
|
||||
assertTrue(controllerB.isAttached());
|
||||
|
||||
activityProxy.rotate();
|
||||
router.rebindIfNeeded();
|
||||
|
||||
assertFalse(controllerA.isAttached());
|
||||
assertTrue(controllerB.isAttached());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testChildNeedsAttachOnPauseAndOrientation() {
|
||||
final Controller controllerA = new TestController();
|
||||
final Controller childController = new TestController();
|
||||
final Controller controllerB = new TestController();
|
||||
|
||||
router.pushController(RouterTransaction.with(controllerA)
|
||||
.pushChangeHandler(MockChangeHandler.defaultHandler())
|
||||
.popChangeHandler(MockChangeHandler.defaultHandler()));
|
||||
|
||||
Router childRouter = controllerA.getChildRouter((ViewGroup)controllerA.getView().findViewById(TestController.VIEW_ID));
|
||||
childRouter.pushController(RouterTransaction.with(childController)
|
||||
.pushChangeHandler(MockChangeHandler.defaultHandler())
|
||||
.popChangeHandler(MockChangeHandler.defaultHandler()));
|
||||
|
||||
assertTrue(controllerA.isAttached());
|
||||
assertTrue(childController.isAttached());
|
||||
assertFalse(controllerB.isAttached());
|
||||
|
||||
sleepWakeDevice();
|
||||
|
||||
assertTrue(controllerA.isAttached());
|
||||
assertTrue(childController.isAttached());
|
||||
assertFalse(controllerB.isAttached());
|
||||
|
||||
router.pushController(RouterTransaction.with(controllerB)
|
||||
.pushChangeHandler(MockChangeHandler.defaultHandler())
|
||||
.popChangeHandler(MockChangeHandler.defaultHandler()));
|
||||
|
||||
assertFalse(controllerA.isAttached());
|
||||
assertFalse(childController.isAttached());
|
||||
assertTrue(controllerB.isAttached());
|
||||
|
||||
activityProxy.rotate();
|
||||
router.rebindIfNeeded();
|
||||
|
||||
assertFalse(controllerA.isAttached());
|
||||
assertFalse(childController.isAttached());
|
||||
assertTrue(childController.getNeedsAttach());
|
||||
assertTrue(controllerB.isAttached());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testChildHandleBackOnOrientation() {
|
||||
final TestController controllerA = new TestController();
|
||||
final TestController controllerB = new TestController();
|
||||
final TestController childController = new TestController();
|
||||
|
||||
router.pushController(RouterTransaction.with(controllerA)
|
||||
.pushChangeHandler(MockChangeHandler.defaultHandler())
|
||||
.popChangeHandler(MockChangeHandler.defaultHandler()));
|
||||
|
||||
assertTrue(controllerA.isAttached());
|
||||
assertFalse(controllerB.isAttached());
|
||||
assertFalse(childController.isAttached());
|
||||
|
||||
router.pushController(RouterTransaction.with(controllerB)
|
||||
.pushChangeHandler(MockChangeHandler.defaultHandler())
|
||||
.popChangeHandler(MockChangeHandler.defaultHandler()));
|
||||
|
||||
Router childRouter = controllerB.getChildRouter((ViewGroup)controllerB.getView().findViewById(TestController.VIEW_ID));
|
||||
childRouter.setPopsLastView(true);
|
||||
childRouter.pushController(RouterTransaction.with(childController)
|
||||
.pushChangeHandler(MockChangeHandler.defaultHandler())
|
||||
.popChangeHandler(MockChangeHandler.defaultHandler()));
|
||||
|
||||
assertFalse(controllerA.isAttached());
|
||||
assertTrue(controllerB.isAttached());
|
||||
assertTrue(childController.isAttached());
|
||||
|
||||
activityProxy.rotate();
|
||||
router.rebindIfNeeded();
|
||||
|
||||
assertFalse(controllerA.isAttached());
|
||||
assertTrue(controllerB.isAttached());
|
||||
assertTrue(childController.isAttached());
|
||||
|
||||
router.handleBack();
|
||||
|
||||
assertFalse(controllerA.isAttached());
|
||||
assertTrue(controllerB.isAttached());
|
||||
assertFalse(childController.isAttached());
|
||||
|
||||
router.handleBack();
|
||||
|
||||
assertTrue(controllerA.isAttached());
|
||||
assertFalse(controllerB.isAttached());
|
||||
assertFalse(childController.isAttached());
|
||||
}
|
||||
|
||||
// Attempt to test https://github.com/bluelinelabs/Conductor/issues/86#issuecomment-231381271
|
||||
@Test
|
||||
public void testReusedChildRouterHandleBackOnOrientation() {
|
||||
TestController controllerA = new TestController();
|
||||
TestController controllerB = new TestController();
|
||||
TestController childController = new TestController();
|
||||
|
||||
router.pushController(RouterTransaction.with(controllerA)
|
||||
.pushChangeHandler(MockChangeHandler.defaultHandler())
|
||||
.popChangeHandler(MockChangeHandler.defaultHandler()));
|
||||
|
||||
assertTrue(controllerA.isAttached());
|
||||
assertFalse(controllerB.isAttached());
|
||||
assertFalse(childController.isAttached());
|
||||
|
||||
router.pushController(RouterTransaction.with(controllerB)
|
||||
.pushChangeHandler(MockChangeHandler.defaultHandler())
|
||||
.popChangeHandler(MockChangeHandler.defaultHandler()));
|
||||
|
||||
Router childRouter = controllerB.getChildRouter((ViewGroup)controllerB.getView().findViewById(TestController.VIEW_ID));
|
||||
childRouter.setPopsLastView(true);
|
||||
childRouter.pushController(RouterTransaction.with(childController)
|
||||
.pushChangeHandler(MockChangeHandler.defaultHandler())
|
||||
.popChangeHandler(MockChangeHandler.defaultHandler()));
|
||||
|
||||
assertFalse(controllerA.isAttached());
|
||||
assertTrue(controllerB.isAttached());
|
||||
assertTrue(childController.isAttached());
|
||||
|
||||
router.handleBack();
|
||||
|
||||
assertFalse(controllerA.isAttached());
|
||||
assertTrue(controllerB.isAttached());
|
||||
assertFalse(childController.isAttached());
|
||||
|
||||
childController = new TestController();
|
||||
childRouter.pushController(RouterTransaction.with(childController)
|
||||
.pushChangeHandler(MockChangeHandler.defaultHandler())
|
||||
.popChangeHandler(MockChangeHandler.defaultHandler()));
|
||||
|
||||
assertFalse(controllerA.isAttached());
|
||||
assertTrue(controllerB.isAttached());
|
||||
assertTrue(childController.isAttached());
|
||||
|
||||
activityProxy.rotate();
|
||||
router.rebindIfNeeded();
|
||||
|
||||
assertFalse(controllerA.isAttached());
|
||||
assertTrue(controllerB.isAttached());
|
||||
assertTrue(childController.isAttached());
|
||||
|
||||
router.handleBack();
|
||||
|
||||
childController = new TestController();
|
||||
childRouter.pushController(RouterTransaction.with(childController)
|
||||
.pushChangeHandler(MockChangeHandler.defaultHandler())
|
||||
.popChangeHandler(MockChangeHandler.defaultHandler()));
|
||||
|
||||
assertFalse(controllerA.isAttached());
|
||||
assertTrue(controllerB.isAttached());
|
||||
assertTrue(childController.isAttached());
|
||||
|
||||
router.handleBack();
|
||||
|
||||
assertFalse(controllerA.isAttached());
|
||||
assertTrue(controllerB.isAttached());
|
||||
assertFalse(childController.isAttached());
|
||||
|
||||
router.handleBack();
|
||||
|
||||
assertTrue(controllerA.isAttached());
|
||||
assertFalse(controllerB.isAttached());
|
||||
assertFalse(childController.isAttached());
|
||||
}
|
||||
|
||||
// Attempt to test https://github.com/bluelinelabs/Conductor/issues/367
|
||||
@Test
|
||||
public void testViewIsAttachedAfterStartedActivityIsRecreated() {
|
||||
Controller controller1 = new TestController();
|
||||
Controller controller2 = new TestController();
|
||||
|
||||
router.setRoot(RouterTransaction.with(controller1));
|
||||
assertTrue(controller1.isAttached());
|
||||
|
||||
// Lock screen
|
||||
Bundle bundle = new Bundle();
|
||||
activityProxy.pause().saveInstanceState(bundle).stop(false);
|
||||
|
||||
// Push a 2nd controller, which will rotate the screen once it unlocked
|
||||
router.pushController(RouterTransaction.with(controller2));
|
||||
assertTrue(controller2.isAttached());
|
||||
assertTrue(controller2.getNeedsAttach());
|
||||
|
||||
// Unlock screen and rotate
|
||||
activityProxy.start();
|
||||
activityProxy.rotate();
|
||||
|
||||
assertTrue(controller2.isAttached());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPopMiddleControllerAttaches() {
|
||||
Controller controller1 = new TestController();
|
||||
Controller controller2 = new TestController();
|
||||
Controller controller3 = new TestController();
|
||||
|
||||
router.setRoot(RouterTransaction.with(controller1));
|
||||
router.pushController(RouterTransaction.with(controller2));
|
||||
router.pushController(RouterTransaction.with(controller3));
|
||||
router.popController(controller2);
|
||||
|
||||
assertFalse(controller1.isAttached());
|
||||
assertFalse(controller2.isAttached());
|
||||
assertTrue(controller3.isAttached());
|
||||
|
||||
controller1 = new TestController();
|
||||
controller2 = new TestController();
|
||||
controller3 = new TestController();
|
||||
|
||||
router.setRoot(RouterTransaction.with(controller1));
|
||||
router.pushController(RouterTransaction.with(controller2));
|
||||
router.pushController(RouterTransaction.with(controller3).pushChangeHandler(MockChangeHandler.noRemoveViewOnPushHandler()));
|
||||
router.popController(controller2);
|
||||
|
||||
assertTrue(controller1.isAttached());
|
||||
assertFalse(controller2.isAttached());
|
||||
assertTrue(controller3.isAttached());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPendingChanges() {
|
||||
Controller controller1 = new TestController();
|
||||
Controller controller2 = new TestController();
|
||||
|
||||
ActivityProxy activityProxy = new ActivityProxy().create(null);
|
||||
AttachFakingFrameLayout container = new AttachFakingFrameLayout(activityProxy.getActivity());
|
||||
container.setNeedDelayPost(true); // to simulate calling posts after resume
|
||||
|
||||
activityProxy.setView(container);
|
||||
|
||||
Router router = Conductor.attachRouter(activityProxy.getActivity(), container, null);
|
||||
router.setRoot(RouterTransaction.with(controller1));
|
||||
router.pushController(RouterTransaction.with(controller2));
|
||||
|
||||
activityProxy.start().resume();
|
||||
container.setNeedDelayPost(false);
|
||||
|
||||
assertTrue(controller2.isAttached());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPendingChangesAfterRotation() {
|
||||
Controller controller1 = new TestController();
|
||||
Controller controller2 = new TestController();
|
||||
|
||||
// first activity
|
||||
ActivityProxy activityProxy = new ActivityProxy().create(null);
|
||||
AttachFakingFrameLayout container1 = new AttachFakingFrameLayout(activityProxy.getActivity());
|
||||
|
||||
container1.setNeedDelayPost(true); // delay forever as view will be removed
|
||||
activityProxy.setView(container1);
|
||||
|
||||
// first attachRouter: Conductor.attachRouter(activityProxy.getActivity(), container1, null)
|
||||
LifecycleHandler lifecycleHandler = LifecycleHandler.install(activityProxy.getActivity());
|
||||
Router router = lifecycleHandler.getRouter(container1, null);
|
||||
router.setRoot(RouterTransaction.with(controller1));
|
||||
|
||||
// setup controllers
|
||||
router.pushController(RouterTransaction.with(controller2));
|
||||
|
||||
// simulate setRequestedOrientation in activity onCreate
|
||||
activityProxy.start().resume();
|
||||
Bundle savedState = new Bundle();
|
||||
activityProxy.saveInstanceState(savedState).pause().stop(true);
|
||||
|
||||
// recreate activity and view
|
||||
activityProxy = new ActivityProxy().create(savedState);
|
||||
AttachFakingFrameLayout container2 = new AttachFakingFrameLayout(activityProxy.getActivity());
|
||||
activityProxy.setView(container2);
|
||||
|
||||
// second attach router with the same lifecycleHandler (do manually as Roboelectric recreates retained fragments)
|
||||
// Conductor.attachRouter(activityProxy.getActivity(), container2, savedState);
|
||||
router = lifecycleHandler.getRouter(container2, savedState);
|
||||
router.rebindIfNeeded();
|
||||
|
||||
activityProxy.start().resume();
|
||||
|
||||
assertTrue(controller2.isAttached());
|
||||
}
|
||||
|
||||
private void sleepWakeDevice() {
|
||||
activityProxy.saveInstanceState(new Bundle()).pause();
|
||||
activityProxy.resume();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,400 @@
|
||||
package com.bluelinelabs.conductor
|
||||
|
||||
import android.os.Bundle
|
||||
import android.os.Looper
|
||||
import com.bluelinelabs.conductor.Conductor.attachRouter
|
||||
import com.bluelinelabs.conductor.internal.LifecycleHandler
|
||||
import com.bluelinelabs.conductor.util.ActivityProxy
|
||||
import com.bluelinelabs.conductor.util.AttachFakingFrameLayout
|
||||
import com.bluelinelabs.conductor.util.MockChangeHandler
|
||||
import com.bluelinelabs.conductor.util.TestActivity
|
||||
import org.junit.Assert
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.Robolectric
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import org.robolectric.Shadows.shadowOf
|
||||
import org.robolectric.annotation.Config
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
@Config(manifest = Config.NONE)
|
||||
class ReattachCaseTests {
|
||||
|
||||
private val activityController = Robolectric.buildActivity(TestActivity::class.java).setup()
|
||||
private val router = activityController.get().router
|
||||
|
||||
@Test
|
||||
fun testNeedsAttachingOnPauseAndOrientation() {
|
||||
val controllerA = TestController()
|
||||
val controllerB = TestController()
|
||||
router.pushController(
|
||||
controllerA.asTransaction(
|
||||
pushChangeHandler = MockChangeHandler.defaultHandler(),
|
||||
popChangeHandler = MockChangeHandler.defaultHandler()
|
||||
)
|
||||
)
|
||||
Assert.assertTrue(controllerA.isAttached)
|
||||
Assert.assertFalse(controllerB.isAttached)
|
||||
|
||||
sleepWakeDevice()
|
||||
Assert.assertTrue(controllerA.isAttached)
|
||||
Assert.assertFalse(controllerB.isAttached)
|
||||
|
||||
router.pushController(
|
||||
controllerB.asTransaction(
|
||||
pushChangeHandler = MockChangeHandler.defaultHandler(),
|
||||
popChangeHandler = MockChangeHandler.defaultHandler()
|
||||
)
|
||||
)
|
||||
Assert.assertFalse(controllerA.isAttached)
|
||||
Assert.assertTrue(controllerB.isAttached)
|
||||
|
||||
activityController.configurationChange()
|
||||
Assert.assertFalse(controllerA.isAttached)
|
||||
Assert.assertTrue(controllerB.isAttached)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testChildNeedsAttachOnPauseAndOrientation() {
|
||||
val controllerA = TestController()
|
||||
val childController = TestController()
|
||||
val controllerB = TestController()
|
||||
router.pushController(
|
||||
controllerA.asTransaction(
|
||||
pushChangeHandler = MockChangeHandler.defaultHandler(),
|
||||
popChangeHandler = MockChangeHandler.defaultHandler()
|
||||
)
|
||||
)
|
||||
val childRouter = controllerA.getChildRouter(
|
||||
controllerA.view!!.findViewById(TestController.VIEW_ID)
|
||||
)
|
||||
childRouter.pushController(
|
||||
childController.asTransaction(
|
||||
pushChangeHandler = MockChangeHandler.defaultHandler(),
|
||||
popChangeHandler = MockChangeHandler.defaultHandler()
|
||||
)
|
||||
)
|
||||
Assert.assertTrue(controllerA.isAttached)
|
||||
Assert.assertTrue(childController.isAttached)
|
||||
Assert.assertFalse(controllerB.isAttached)
|
||||
|
||||
sleepWakeDevice()
|
||||
Assert.assertTrue(controllerA.isAttached)
|
||||
Assert.assertTrue(childController.isAttached)
|
||||
Assert.assertFalse(controllerB.isAttached)
|
||||
|
||||
router.pushController(
|
||||
controllerB.asTransaction(
|
||||
pushChangeHandler = MockChangeHandler.defaultHandler(),
|
||||
popChangeHandler = MockChangeHandler.defaultHandler()
|
||||
)
|
||||
)
|
||||
Assert.assertFalse(controllerA.isAttached)
|
||||
Assert.assertFalse(childController.isAttached)
|
||||
Assert.assertTrue(controllerB.isAttached)
|
||||
|
||||
activityController.configurationChange()
|
||||
Assert.assertFalse(controllerA.isAttached)
|
||||
Assert.assertFalse(childController.isAttached)
|
||||
Assert.assertTrue(childController.needsAttach)
|
||||
Assert.assertTrue(controllerB.isAttached)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testChildHandleBackOnOrientation() {
|
||||
val controllerA = TestController()
|
||||
val controllerB = TestController()
|
||||
val childController = TestController()
|
||||
router.pushController(
|
||||
controllerA.asTransaction(
|
||||
pushChangeHandler = MockChangeHandler.defaultHandler(),
|
||||
popChangeHandler = MockChangeHandler.defaultHandler()
|
||||
)
|
||||
)
|
||||
Assert.assertTrue(controllerA.isAttached)
|
||||
Assert.assertFalse(controllerB.isAttached)
|
||||
Assert.assertFalse(childController.isAttached)
|
||||
|
||||
router.pushController(
|
||||
controllerB.asTransaction(
|
||||
pushChangeHandler = MockChangeHandler.defaultHandler(),
|
||||
popChangeHandler = MockChangeHandler.defaultHandler()
|
||||
)
|
||||
)
|
||||
val childRouter = controllerB.getChildRouter(
|
||||
controllerB.view!!.findViewById(TestController.VIEW_ID)
|
||||
)
|
||||
childRouter.setPopRootControllerMode(Router.PopRootControllerMode.POP_ROOT_CONTROLLER_AND_VIEW)
|
||||
childRouter.pushController(
|
||||
childController.asTransaction(
|
||||
pushChangeHandler = MockChangeHandler.defaultHandler(),
|
||||
popChangeHandler = MockChangeHandler.defaultHandler()
|
||||
)
|
||||
)
|
||||
Assert.assertFalse(controllerA.isAttached)
|
||||
Assert.assertTrue(controllerB.isAttached)
|
||||
Assert.assertTrue(childController.isAttached)
|
||||
|
||||
activityController.configurationChange()
|
||||
shadowOf(Looper.getMainLooper()).idle()
|
||||
Assert.assertFalse(controllerA.isAttached)
|
||||
Assert.assertTrue(controllerB.isAttached)
|
||||
Assert.assertTrue(childController.isAttached)
|
||||
|
||||
router.handleBack()
|
||||
shadowOf(Looper.getMainLooper()).idle()
|
||||
Assert.assertFalse(controllerA.isAttached)
|
||||
Assert.assertTrue(controllerB.isAttached)
|
||||
Assert.assertFalse(childController.isAttached)
|
||||
|
||||
router.handleBack()
|
||||
shadowOf(Looper.getMainLooper()).idle()
|
||||
Assert.assertTrue(controllerA.isAttached)
|
||||
Assert.assertFalse(controllerB.isAttached)
|
||||
Assert.assertFalse(childController.isAttached)
|
||||
}
|
||||
|
||||
// Attempt to test https://github.com/bluelinelabs/Conductor/issues/86#issuecomment-231381271
|
||||
@Test
|
||||
fun testReusedChildRouterHandleBackOnOrientation() {
|
||||
val controllerA = TestController()
|
||||
val controllerB = TestController()
|
||||
var childController = TestController()
|
||||
router.pushController(
|
||||
controllerA.asTransaction(
|
||||
pushChangeHandler = MockChangeHandler.defaultHandler(),
|
||||
popChangeHandler = MockChangeHandler.defaultHandler()
|
||||
)
|
||||
)
|
||||
Assert.assertTrue(controllerA.isAttached)
|
||||
Assert.assertFalse(controllerB.isAttached)
|
||||
Assert.assertFalse(childController.isAttached)
|
||||
|
||||
router.pushController(
|
||||
controllerB.asTransaction(
|
||||
pushChangeHandler = MockChangeHandler.defaultHandler(),
|
||||
popChangeHandler = MockChangeHandler.defaultHandler()
|
||||
)
|
||||
)
|
||||
val childRouter = controllerB.getChildRouter(
|
||||
controllerB.view!!.findViewById(TestController.VIEW_ID)
|
||||
)
|
||||
childRouter.setPopRootControllerMode(Router.PopRootControllerMode.POP_ROOT_CONTROLLER_AND_VIEW)
|
||||
childRouter.pushController(
|
||||
childController.asTransaction(
|
||||
pushChangeHandler = MockChangeHandler.defaultHandler(),
|
||||
popChangeHandler = MockChangeHandler.defaultHandler()
|
||||
)
|
||||
)
|
||||
Assert.assertFalse(controllerA.isAttached)
|
||||
Assert.assertTrue(controllerB.isAttached)
|
||||
Assert.assertTrue(childController.isAttached)
|
||||
|
||||
router.handleBack()
|
||||
shadowOf(Looper.getMainLooper()).idle()
|
||||
Assert.assertFalse(controllerA.isAttached)
|
||||
Assert.assertTrue(controllerB.isAttached)
|
||||
Assert.assertFalse(childController.isAttached)
|
||||
|
||||
childController = TestController()
|
||||
childRouter.pushController(
|
||||
childController.asTransaction(
|
||||
pushChangeHandler = MockChangeHandler.defaultHandler(),
|
||||
popChangeHandler = MockChangeHandler.defaultHandler()
|
||||
)
|
||||
)
|
||||
Assert.assertFalse(controllerA.isAttached)
|
||||
Assert.assertTrue(controllerB.isAttached)
|
||||
Assert.assertTrue(childController.isAttached)
|
||||
|
||||
activityController.configurationChange()
|
||||
Assert.assertFalse(controllerA.isAttached)
|
||||
Assert.assertTrue(controllerB.isAttached)
|
||||
Assert.assertTrue(childController.isAttached)
|
||||
|
||||
router.handleBack()
|
||||
childController = TestController()
|
||||
childRouter.pushController(
|
||||
childController.asTransaction(
|
||||
pushChangeHandler = MockChangeHandler.defaultHandler(),
|
||||
popChangeHandler = MockChangeHandler.defaultHandler()
|
||||
)
|
||||
)
|
||||
Assert.assertFalse(controllerA.isAttached)
|
||||
Assert.assertTrue(controllerB.isAttached)
|
||||
Assert.assertTrue(childController.isAttached)
|
||||
|
||||
router.handleBack()
|
||||
Assert.assertFalse(controllerA.isAttached)
|
||||
Assert.assertTrue(controllerB.isAttached)
|
||||
Assert.assertFalse(childController.isAttached)
|
||||
|
||||
router.handleBack()
|
||||
Assert.assertTrue(controllerA.isAttached)
|
||||
Assert.assertFalse(controllerB.isAttached)
|
||||
Assert.assertFalse(childController.isAttached)
|
||||
}
|
||||
|
||||
// Attempt to test https://github.com/bluelinelabs/Conductor/issues/367
|
||||
@Test
|
||||
fun testViewIsAttachedAfterStartedActivityIsRecreated() {
|
||||
val controller1 = TestController()
|
||||
val controller2 = TestController()
|
||||
router.setRoot(controller1.asTransaction())
|
||||
Assert.assertTrue(controller1.isAttached)
|
||||
|
||||
// Lock screen
|
||||
val bundle = Bundle()
|
||||
activityController.pause().saveInstanceState(bundle).stop()
|
||||
|
||||
// Push a 2nd controller, which will rotate the screen once it unlocked
|
||||
router.pushController(controller2.asTransaction())
|
||||
Assert.assertTrue(controller2.isAttached)
|
||||
Assert.assertTrue(controller2.needsAttach)
|
||||
|
||||
// Unlock screen and rotate
|
||||
activityController.start()
|
||||
activityController.configurationChange()
|
||||
Assert.assertTrue(controller2.isAttached)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testPopMiddleControllerAttaches() {
|
||||
var controller1 = TestController()
|
||||
var controller2 = TestController()
|
||||
var controller3 = TestController()
|
||||
router.setRoot(controller1.asTransaction())
|
||||
router.pushController(controller2.asTransaction())
|
||||
router.pushController(controller3.asTransaction())
|
||||
router.popController(controller2)
|
||||
Assert.assertFalse(controller1.isAttached)
|
||||
Assert.assertFalse(controller2.isAttached)
|
||||
Assert.assertTrue(controller3.isAttached)
|
||||
|
||||
controller1 = TestController()
|
||||
controller2 = TestController()
|
||||
controller3 = TestController()
|
||||
router.setRoot(controller1.asTransaction())
|
||||
router.pushController(controller2.asTransaction())
|
||||
router.pushController(
|
||||
controller3.asTransaction(
|
||||
pushChangeHandler = MockChangeHandler.noRemoveViewOnPushHandler()
|
||||
)
|
||||
)
|
||||
router.popController(controller2)
|
||||
Assert.assertTrue(controller1.isAttached())
|
||||
Assert.assertFalse(controller2.isAttached())
|
||||
Assert.assertTrue(controller3.isAttached())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testPendingChanges() {
|
||||
val controller1 = TestController()
|
||||
val controller2 = TestController()
|
||||
val activityProxy = ActivityProxy().create(null)
|
||||
val container = AttachFakingFrameLayout(activityProxy.activity)
|
||||
container.setNeedDelayPost(true) // to simulate calling posts after resume
|
||||
activityProxy.view = container
|
||||
val router = attachRouter(activityProxy.activity, container, null)
|
||||
router.setRoot(controller1.asTransaction())
|
||||
router.pushController(controller2.asTransaction())
|
||||
activityProxy.start().resume()
|
||||
container.setNeedDelayPost(false)
|
||||
Assert.assertTrue(controller2.isAttached)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testPendingChangesAfterRotation() {
|
||||
val controller1 = TestController()
|
||||
val controller2 = TestController()
|
||||
|
||||
// first activity
|
||||
var activityProxy = ActivityProxy().create(null)
|
||||
val container1 = AttachFakingFrameLayout(activityProxy.activity)
|
||||
container1.setNeedDelayPost(true) // delay forever as view will be removed
|
||||
activityProxy.view = container1
|
||||
|
||||
// first attachRouter: Conductor.attachRouter(activityProxy.getActivity(), container1, null)
|
||||
val lifecycleHandler = LifecycleHandler.install(activityProxy.activity)
|
||||
var router = lifecycleHandler.getRouter(container1, null)
|
||||
router.setRoot(controller1.asTransaction())
|
||||
|
||||
// setup controllers
|
||||
router.pushController(controller2.asTransaction())
|
||||
|
||||
// simulate setRequestedOrientation in activity onCreate
|
||||
activityProxy.start().resume()
|
||||
val savedState = Bundle()
|
||||
activityProxy.saveInstanceState(savedState).pause().stop(true)
|
||||
|
||||
// recreate activity and view
|
||||
activityProxy = ActivityProxy().create(savedState)
|
||||
val container2 = AttachFakingFrameLayout(activityProxy.activity)
|
||||
activityProxy.view = container2
|
||||
|
||||
// second attach router with the same lifecycleHandler (do manually as robolectric recreates retained fragments)
|
||||
// Conductor.attachRouter(activityProxy.getActivity(), container2, savedState);
|
||||
router = lifecycleHandler.getRouter(container2, savedState)
|
||||
router.rebindIfNeeded()
|
||||
activityProxy.start().resume()
|
||||
Assert.assertTrue(controller2.isAttached)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testHostAvailableDuringRotation() {
|
||||
val controllerA = TestController()
|
||||
val childControllerA = TestController()
|
||||
val controllerB = TestController()
|
||||
val childControllerB = TestController()
|
||||
router.pushController(
|
||||
controllerA.asTransaction(
|
||||
pushChangeHandler = MockChangeHandler.defaultHandler(),
|
||||
popChangeHandler = MockChangeHandler.defaultHandler()
|
||||
)
|
||||
)
|
||||
val childRouterA = controllerA.getChildRouter(
|
||||
controllerA.view!!.findViewById(TestController.VIEW_ID)
|
||||
)
|
||||
childRouterA.pushController(
|
||||
childControllerA.asTransaction(
|
||||
pushChangeHandler = MockChangeHandler.defaultHandler(),
|
||||
popChangeHandler = MockChangeHandler.defaultHandler()
|
||||
)
|
||||
)
|
||||
Assert.assertNotNull(controllerA.activity)
|
||||
Assert.assertNotNull(childControllerA.activity)
|
||||
|
||||
router.pushController(
|
||||
controllerB.asTransaction(
|
||||
pushChangeHandler = MockChangeHandler.defaultHandler(),
|
||||
popChangeHandler = MockChangeHandler.defaultHandler()
|
||||
)
|
||||
)
|
||||
val childRouterB = controllerB.getChildRouter(
|
||||
controllerB.view!!.findViewById(
|
||||
TestController.VIEW_ID
|
||||
)
|
||||
)
|
||||
childRouterB.pushController(
|
||||
childControllerB.asTransaction(
|
||||
pushChangeHandler = MockChangeHandler.defaultHandler(),
|
||||
popChangeHandler = MockChangeHandler.defaultHandler()
|
||||
)
|
||||
)
|
||||
Assert.assertNotNull(controllerA.activity)
|
||||
Assert.assertNotNull(childControllerA.activity)
|
||||
Assert.assertNotNull(controllerB.activity)
|
||||
Assert.assertNotNull(childControllerB.activity)
|
||||
|
||||
activityController.configurationChange()
|
||||
Assert.assertNotNull(controllerA.activity)
|
||||
Assert.assertNotNull(childControllerA.activity)
|
||||
Assert.assertNotNull(controllerB.activity)
|
||||
Assert.assertNotNull(childControllerB.activity)
|
||||
}
|
||||
|
||||
private fun sleepWakeDevice() {
|
||||
activityController.saveInstanceState(Bundle()).pause()
|
||||
activityController.resume()
|
||||
}
|
||||
}
|
||||
@@ -1,354 +0,0 @@
|
||||
package com.bluelinelabs.conductor;
|
||||
|
||||
import android.view.View;
|
||||
|
||||
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 java.util.Arrays;
|
||||
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 = Arrays.asList(
|
||||
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);
|
||||
MockChangeHandler pushController2Handler = MockChangeHandler.noRemoveViewOnPushHandler("pushController2");
|
||||
List<RouterTransaction> newBackstack = Arrays.asList(
|
||||
RouterTransaction.with(newController1),
|
||||
RouterTransaction.with(newController2).pushChangeHandler(pushController2Handler)
|
||||
);
|
||||
|
||||
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.changeHandlerAt(0).tag);
|
||||
assertEquals(pushController2Handler.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(pushController2Handler.tag, newController2.changeHandlerHistory.latestChangeHandler().tag);
|
||||
assertTrue(newController2.changeHandlerHistory.latestIsPush());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetBackstackForPushHandlers() {
|
||||
TestController initialController = new TestController();
|
||||
MockChangeHandler initialPushHandler = MockChangeHandler.taggedHandler("initialPush1", true);
|
||||
MockChangeHandler initialPopHandler = MockChangeHandler.taggedHandler("initialPop1", true);
|
||||
RouterTransaction initialTransaction = RouterTransaction.with(initialController).pushChangeHandler(initialPushHandler).popChangeHandler(initialPopHandler);
|
||||
router.setRoot(initialTransaction);
|
||||
View initialView = initialController.getView();
|
||||
|
||||
TestController newController = new TestController();
|
||||
MockChangeHandler setBackstackHandler = MockChangeHandler.taggedHandler("setBackstackHandler", true);
|
||||
|
||||
List<RouterTransaction> newBackstack = Arrays.asList(
|
||||
initialTransaction,
|
||||
RouterTransaction.with(newController)
|
||||
);
|
||||
|
||||
router.setBackstack(newBackstack, setBackstackHandler);
|
||||
|
||||
assertTrue(initialController.changeHandlerHistory.isValidHistory);
|
||||
assertTrue(newController.changeHandlerHistory.isValidHistory);
|
||||
|
||||
assertEquals(2, initialController.changeHandlerHistory.size());
|
||||
assertEquals(1, newController.changeHandlerHistory.size());
|
||||
|
||||
assertNotNull(initialController.changeHandlerHistory.latestToView());
|
||||
assertEquals(newController.getView(), initialController.changeHandlerHistory.latestToView());
|
||||
assertEquals(initialView, initialController.changeHandlerHistory.latestFromView());
|
||||
assertEquals(setBackstackHandler.tag, initialController.changeHandlerHistory.latestChangeHandler().tag);
|
||||
assertTrue(initialController.changeHandlerHistory.latestIsPush());
|
||||
assertTrue(newController.changeHandlerHistory.latestIsPush());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetBackstackForInvertHandlersWithRemovesView() {
|
||||
TestController initialController1 = new TestController();
|
||||
MockChangeHandler initialPushHandler1 = MockChangeHandler.taggedHandler("initialPush1", true);
|
||||
MockChangeHandler initialPopHandler1 = MockChangeHandler.taggedHandler("initialPop1", true);
|
||||
RouterTransaction initialTransaction1 = RouterTransaction.with(initialController1).pushChangeHandler(initialPushHandler1).popChangeHandler(initialPopHandler1);
|
||||
router.setRoot(initialTransaction1);
|
||||
TestController initialController2 = new TestController();
|
||||
MockChangeHandler initialPushHandler2 = MockChangeHandler.taggedHandler("initialPush2", true);
|
||||
MockChangeHandler initialPopHandler2 = MockChangeHandler.taggedHandler("initialPop2", true);
|
||||
RouterTransaction initialTransaction2 = RouterTransaction.with(initialController2).pushChangeHandler(initialPushHandler2).popChangeHandler(initialPopHandler2);
|
||||
router.pushController(initialTransaction2);
|
||||
|
||||
View initialView2 = initialController2.getView();
|
||||
|
||||
MockChangeHandler setBackstackHandler = MockChangeHandler.taggedHandler("setBackstackHandler", true);
|
||||
List<RouterTransaction> newBackstack = Arrays.asList(
|
||||
initialTransaction2,
|
||||
initialTransaction1
|
||||
);
|
||||
|
||||
router.setBackstack(newBackstack, setBackstackHandler);
|
||||
|
||||
assertTrue(initialController1.changeHandlerHistory.isValidHistory);
|
||||
assertTrue(initialController2.changeHandlerHistory.isValidHistory);
|
||||
|
||||
assertEquals(3, initialController1.changeHandlerHistory.size());
|
||||
assertEquals(2, initialController2.changeHandlerHistory.size());
|
||||
|
||||
assertNotNull(initialController1.changeHandlerHistory.latestToView());
|
||||
assertEquals(initialView2, initialController1.changeHandlerHistory.latestFromView());
|
||||
assertEquals(setBackstackHandler.tag, initialController1.changeHandlerHistory.latestChangeHandler().tag);
|
||||
assertFalse(initialController1.changeHandlerHistory.latestIsPush());
|
||||
|
||||
assertNotNull(initialController2.changeHandlerHistory.latestToView());
|
||||
assertEquals(initialView2, initialController2.changeHandlerHistory.latestFromView());
|
||||
assertEquals(setBackstackHandler.tag, initialController2.changeHandlerHistory.latestChangeHandler().tag);
|
||||
assertFalse(initialController2.changeHandlerHistory.latestIsPush());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetBackstackForInvertHandlersWithoutRemovesView() {
|
||||
TestController initialController1 = new TestController();
|
||||
MockChangeHandler initialPushHandler1 = MockChangeHandler.taggedHandler("initialPush1", true);
|
||||
MockChangeHandler initialPopHandler1 = MockChangeHandler.taggedHandler("initialPop1", true);
|
||||
RouterTransaction initialTransaction1 = RouterTransaction.with(initialController1).pushChangeHandler(initialPushHandler1).popChangeHandler(initialPopHandler1);
|
||||
router.setRoot(initialTransaction1);
|
||||
TestController initialController2 = new TestController();
|
||||
MockChangeHandler initialPushHandler2 = MockChangeHandler.taggedHandler("initialPush2", false);
|
||||
MockChangeHandler initialPopHandler2 = MockChangeHandler.taggedHandler("initialPop2", false);
|
||||
RouterTransaction initialTransaction2 = RouterTransaction.with(initialController2).pushChangeHandler(initialPushHandler2).popChangeHandler(initialPopHandler2);
|
||||
router.pushController(initialTransaction2);
|
||||
|
||||
View initialView1 = initialController1.getView();
|
||||
View initialView2 = initialController2.getView();
|
||||
|
||||
MockChangeHandler setBackstackHandler = MockChangeHandler.taggedHandler("setBackstackHandler", true);
|
||||
List<RouterTransaction> newBackstack = Arrays.asList(
|
||||
initialTransaction2,
|
||||
initialTransaction1
|
||||
);
|
||||
|
||||
router.setBackstack(newBackstack, setBackstackHandler);
|
||||
|
||||
assertTrue(initialController1.changeHandlerHistory.isValidHistory);
|
||||
assertTrue(initialController2.changeHandlerHistory.isValidHistory);
|
||||
|
||||
assertEquals(2, initialController1.changeHandlerHistory.size());
|
||||
assertEquals(2, initialController2.changeHandlerHistory.size());
|
||||
|
||||
assertNotNull(initialController1.changeHandlerHistory.latestToView());
|
||||
assertEquals(initialView1, initialController1.changeHandlerHistory.latestFromView());
|
||||
assertEquals(initialPushHandler2.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);
|
||||
assertFalse(initialController2.changeHandlerHistory.latestIsPush());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,425 @@
|
||||
package com.bluelinelabs.conductor
|
||||
|
||||
import com.bluelinelabs.conductor.util.MockChangeHandler
|
||||
import com.bluelinelabs.conductor.util.TestActivity
|
||||
import org.junit.Assert
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.Robolectric
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import org.robolectric.annotation.Config
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
@Config(manifest = Config.NONE)
|
||||
class RouterChangeHandlerTests {
|
||||
|
||||
private val router = Robolectric.buildActivity(TestActivity::class.java)
|
||||
.setup()
|
||||
.get()
|
||||
.router
|
||||
|
||||
@Test
|
||||
fun testSetRootHandler() {
|
||||
val handler = MockChangeHandler.taggedHandler("root", true)
|
||||
val rootController = TestController()
|
||||
router.setRoot(
|
||||
rootController.asTransaction(pushChangeHandler = handler)
|
||||
)
|
||||
|
||||
Assert.assertTrue(rootController.changeHandlerHistory.isValidHistory)
|
||||
Assert.assertNull(rootController.changeHandlerHistory.latestFromView())
|
||||
Assert.assertNotNull(rootController.changeHandlerHistory.latestToView())
|
||||
Assert.assertEquals(
|
||||
rootController.view,
|
||||
rootController.changeHandlerHistory.latestToView()
|
||||
)
|
||||
Assert.assertTrue(rootController.changeHandlerHistory.latestIsPush())
|
||||
Assert.assertEquals(handler.tag, rootController.changeHandlerHistory.latestChangeHandler().tag)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testPushPopHandlers() {
|
||||
val rootController = TestController()
|
||||
router.setRoot(
|
||||
rootController.asTransaction(pushChangeHandler = MockChangeHandler.defaultHandler())
|
||||
)
|
||||
|
||||
val rootView = rootController.view
|
||||
val pushHandler = MockChangeHandler.taggedHandler("push", true)
|
||||
val popHandler = MockChangeHandler.taggedHandler("pop", true)
|
||||
val pushController = TestController()
|
||||
router.pushController(
|
||||
pushController.asTransaction(
|
||||
pushChangeHandler = pushHandler,
|
||||
popChangeHandler = popHandler
|
||||
)
|
||||
)
|
||||
|
||||
Assert.assertTrue(rootController.changeHandlerHistory.isValidHistory)
|
||||
Assert.assertTrue(pushController.changeHandlerHistory.isValidHistory)
|
||||
Assert.assertNotNull(pushController.changeHandlerHistory.latestFromView())
|
||||
Assert.assertNotNull(pushController.changeHandlerHistory.latestToView())
|
||||
Assert.assertEquals(rootView, pushController.changeHandlerHistory.latestFromView())
|
||||
Assert.assertEquals(
|
||||
pushController.view,
|
||||
pushController.changeHandlerHistory.latestToView()
|
||||
)
|
||||
Assert.assertTrue(pushController.changeHandlerHistory.latestIsPush())
|
||||
Assert.assertEquals(
|
||||
pushHandler.tag,
|
||||
pushController.changeHandlerHistory.latestChangeHandler().tag
|
||||
)
|
||||
|
||||
val pushView = pushController.view
|
||||
router.popController(pushController)
|
||||
Assert.assertNotNull(pushController.changeHandlerHistory.latestFromView())
|
||||
Assert.assertNotNull(pushController.changeHandlerHistory.latestToView())
|
||||
Assert.assertEquals(pushView, pushController.changeHandlerHistory.fromViewAt(1))
|
||||
Assert.assertEquals(
|
||||
rootController.view,
|
||||
pushController.changeHandlerHistory.latestToView()
|
||||
)
|
||||
Assert.assertFalse(pushController.changeHandlerHistory.latestIsPush())
|
||||
Assert.assertEquals(
|
||||
popHandler.tag,
|
||||
pushController.changeHandlerHistory.latestChangeHandler().tag
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testResetRootHandlers() {
|
||||
val initialController1 = TestController()
|
||||
router.setRoot(
|
||||
initialController1.asTransaction(
|
||||
pushChangeHandler = MockChangeHandler.taggedHandler("initialPush1", true),
|
||||
popChangeHandler = MockChangeHandler.taggedHandler("initialPop1", true)
|
||||
)
|
||||
)
|
||||
|
||||
val initialController2 = TestController()
|
||||
router.pushController(
|
||||
initialController2.asTransaction(
|
||||
pushChangeHandler = MockChangeHandler.taggedHandler("initialPush2", false),
|
||||
popChangeHandler = MockChangeHandler.taggedHandler("initialPop2", false)
|
||||
)
|
||||
)
|
||||
|
||||
val initialView1 = initialController1.view
|
||||
val initialView2 = initialController2.view
|
||||
val newRootController = TestController()
|
||||
val newRootHandlerTag = "newRootHandler"
|
||||
router.setRoot(
|
||||
newRootController.asTransaction(
|
||||
pushChangeHandler = MockChangeHandler.taggedHandler(newRootHandlerTag, true)
|
||||
)
|
||||
)
|
||||
|
||||
Assert.assertTrue(initialController1.changeHandlerHistory.isValidHistory)
|
||||
Assert.assertTrue(initialController2.changeHandlerHistory.isValidHistory)
|
||||
Assert.assertTrue(newRootController.changeHandlerHistory.isValidHistory)
|
||||
Assert.assertEquals(3, initialController1.changeHandlerHistory.size().toLong())
|
||||
Assert.assertEquals(2, initialController2.changeHandlerHistory.size().toLong())
|
||||
Assert.assertEquals(1, newRootController.changeHandlerHistory.size().toLong())
|
||||
Assert.assertNotNull(initialController1.changeHandlerHistory.latestToView())
|
||||
Assert.assertEquals(
|
||||
newRootController.view,
|
||||
initialController1.changeHandlerHistory.latestToView()
|
||||
)
|
||||
Assert.assertEquals(initialView1, initialController1.changeHandlerHistory.latestFromView())
|
||||
Assert.assertEquals(
|
||||
newRootHandlerTag,
|
||||
initialController1.changeHandlerHistory.latestChangeHandler().tag
|
||||
)
|
||||
Assert.assertTrue(initialController1.changeHandlerHistory.latestIsPush())
|
||||
Assert.assertNull(initialController2.changeHandlerHistory.latestToView())
|
||||
Assert.assertEquals(initialView2, initialController2.changeHandlerHistory.latestFromView())
|
||||
Assert.assertEquals(
|
||||
newRootHandlerTag,
|
||||
initialController2.changeHandlerHistory.latestChangeHandler().tag
|
||||
)
|
||||
Assert.assertTrue(initialController2.changeHandlerHistory.latestIsPush())
|
||||
Assert.assertNotNull(newRootController.changeHandlerHistory.latestToView())
|
||||
Assert.assertEquals(
|
||||
newRootController.view,
|
||||
newRootController.changeHandlerHistory.latestToView()
|
||||
)
|
||||
Assert.assertEquals(initialView1, newRootController.changeHandlerHistory.latestFromView())
|
||||
Assert.assertEquals(
|
||||
newRootHandlerTag,
|
||||
newRootController.changeHandlerHistory.latestChangeHandler().tag
|
||||
)
|
||||
Assert.assertTrue(newRootController.changeHandlerHistory.latestIsPush())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSetBackstackHandlers() {
|
||||
val initialController1 = TestController()
|
||||
router.setRoot(
|
||||
initialController1.asTransaction(
|
||||
pushChangeHandler = MockChangeHandler.taggedHandler("initialPush1", true),
|
||||
popChangeHandler = MockChangeHandler.taggedHandler("initialPop1", true)
|
||||
)
|
||||
)
|
||||
|
||||
val initialController2 = TestController()
|
||||
router.pushController(
|
||||
initialController2.asTransaction(
|
||||
pushChangeHandler = MockChangeHandler.taggedHandler("initialPush2", false),
|
||||
popChangeHandler = MockChangeHandler.taggedHandler("initialPop2", false)
|
||||
)
|
||||
)
|
||||
|
||||
val initialView1 = initialController1.view
|
||||
val initialView2 = initialController2.view
|
||||
val newController1 = TestController()
|
||||
val newController2 = TestController()
|
||||
val setBackstackHandler = MockChangeHandler.taggedHandler("setBackstackHandler", true)
|
||||
val newBackstack = listOf(newController1.asTransaction(), newController2.asTransaction())
|
||||
router.setBackstack(newBackstack, setBackstackHandler)
|
||||
|
||||
Assert.assertTrue(initialController1.changeHandlerHistory.isValidHistory)
|
||||
Assert.assertTrue(initialController2.changeHandlerHistory.isValidHistory)
|
||||
Assert.assertTrue(newController1.changeHandlerHistory.isValidHistory)
|
||||
Assert.assertEquals(3, initialController1.changeHandlerHistory.size().toLong())
|
||||
Assert.assertEquals(2, initialController2.changeHandlerHistory.size().toLong())
|
||||
Assert.assertEquals(0, newController1.changeHandlerHistory.size().toLong())
|
||||
Assert.assertEquals(1, newController2.changeHandlerHistory.size().toLong())
|
||||
Assert.assertNotNull(initialController1.changeHandlerHistory.latestToView())
|
||||
Assert.assertEquals(
|
||||
newController2.view,
|
||||
initialController1.changeHandlerHistory.latestToView()
|
||||
)
|
||||
Assert.assertEquals(initialView1, initialController1.changeHandlerHistory.latestFromView())
|
||||
Assert.assertEquals(
|
||||
setBackstackHandler.tag,
|
||||
initialController1.changeHandlerHistory.latestChangeHandler().tag
|
||||
)
|
||||
Assert.assertTrue(initialController1.changeHandlerHistory.latestIsPush())
|
||||
Assert.assertNull(initialController2.changeHandlerHistory.latestToView())
|
||||
Assert.assertEquals(initialView2, initialController2.changeHandlerHistory.latestFromView())
|
||||
Assert.assertEquals(
|
||||
setBackstackHandler.tag,
|
||||
initialController2.changeHandlerHistory.latestChangeHandler().tag
|
||||
)
|
||||
Assert.assertTrue(initialController2.changeHandlerHistory.latestIsPush())
|
||||
Assert.assertNotNull(newController2.changeHandlerHistory.latestToView())
|
||||
Assert.assertEquals(
|
||||
newController2.view,
|
||||
newController2.changeHandlerHistory.latestToView()
|
||||
)
|
||||
Assert.assertEquals(initialView1, newController2.changeHandlerHistory.latestFromView())
|
||||
Assert.assertEquals(
|
||||
setBackstackHandler.tag,
|
||||
newController2.changeHandlerHistory.latestChangeHandler().tag
|
||||
)
|
||||
Assert.assertTrue(newController2.changeHandlerHistory.latestIsPush())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSetBackstackWithTwoVisibleHandlers() {
|
||||
val initialController1 = TestController()
|
||||
router.setRoot(
|
||||
initialController1.asTransaction(
|
||||
pushChangeHandler = MockChangeHandler.taggedHandler("initialPush1", true),
|
||||
popChangeHandler = MockChangeHandler.taggedHandler("initialPop1", true)
|
||||
)
|
||||
)
|
||||
|
||||
val initialController2 = TestController()
|
||||
router.pushController(
|
||||
initialController2.asTransaction(
|
||||
pushChangeHandler = MockChangeHandler.taggedHandler("initialPush2", false),
|
||||
popChangeHandler = MockChangeHandler.taggedHandler("initialPop2", false)
|
||||
)
|
||||
)
|
||||
|
||||
val initialView1 = initialController1.view
|
||||
val initialView2 = initialController2.view
|
||||
val newController1 = TestController()
|
||||
val newController2 = TestController()
|
||||
val setBackstackHandler = MockChangeHandler.taggedHandler("setBackstackHandler", true)
|
||||
val pushController2Handler = MockChangeHandler.noRemoveViewOnPushHandler("pushController2")
|
||||
val newBackstack = listOf(
|
||||
newController1.asTransaction(),
|
||||
newController2.asTransaction(pushChangeHandler = pushController2Handler)
|
||||
)
|
||||
router.setBackstack(newBackstack, setBackstackHandler)
|
||||
|
||||
Assert.assertTrue(initialController1.changeHandlerHistory.isValidHistory)
|
||||
Assert.assertTrue(initialController2.changeHandlerHistory.isValidHistory)
|
||||
Assert.assertTrue(newController1.changeHandlerHistory.isValidHistory)
|
||||
Assert.assertEquals(3, initialController1.changeHandlerHistory.size().toLong())
|
||||
Assert.assertEquals(2, initialController2.changeHandlerHistory.size().toLong())
|
||||
Assert.assertEquals(2, newController1.changeHandlerHistory.size().toLong())
|
||||
Assert.assertEquals(1, newController2.changeHandlerHistory.size().toLong())
|
||||
Assert.assertNotNull(initialController1.changeHandlerHistory.latestToView())
|
||||
Assert.assertEquals(
|
||||
newController1.view,
|
||||
initialController1.changeHandlerHistory.latestToView()
|
||||
)
|
||||
Assert.assertEquals(initialView1, initialController1.changeHandlerHistory.latestFromView())
|
||||
Assert.assertEquals(
|
||||
setBackstackHandler.tag,
|
||||
initialController1.changeHandlerHistory.latestChangeHandler().tag
|
||||
)
|
||||
Assert.assertTrue(initialController1.changeHandlerHistory.latestIsPush())
|
||||
Assert.assertNull(initialController2.changeHandlerHistory.latestToView())
|
||||
Assert.assertEquals(initialView2, initialController2.changeHandlerHistory.latestFromView())
|
||||
Assert.assertEquals(
|
||||
setBackstackHandler.tag,
|
||||
initialController2.changeHandlerHistory.latestChangeHandler().tag
|
||||
)
|
||||
Assert.assertTrue(initialController2.changeHandlerHistory.latestIsPush())
|
||||
Assert.assertNotNull(newController1.changeHandlerHistory.latestToView())
|
||||
Assert.assertEquals(newController1.view, newController1.changeHandlerHistory.toViewAt(0))
|
||||
Assert.assertEquals(
|
||||
newController2.view,
|
||||
newController1.changeHandlerHistory.latestToView()
|
||||
)
|
||||
Assert.assertEquals(initialView1, newController1.changeHandlerHistory.fromViewAt(0))
|
||||
Assert.assertEquals(
|
||||
newController1.view,
|
||||
newController1.changeHandlerHistory.latestFromView()
|
||||
)
|
||||
Assert.assertEquals(
|
||||
setBackstackHandler.tag,
|
||||
newController1.changeHandlerHistory.changeHandlerAt(0).tag
|
||||
)
|
||||
Assert.assertEquals(
|
||||
pushController2Handler.tag,
|
||||
newController1.changeHandlerHistory.latestChangeHandler().tag
|
||||
)
|
||||
Assert.assertTrue(newController1.changeHandlerHistory.latestIsPush())
|
||||
Assert.assertNotNull(newController2.changeHandlerHistory.latestToView())
|
||||
Assert.assertEquals(
|
||||
newController2.view,
|
||||
newController2.changeHandlerHistory.latestToView()
|
||||
)
|
||||
Assert.assertEquals(
|
||||
newController1.view,
|
||||
newController2.changeHandlerHistory.latestFromView()
|
||||
)
|
||||
Assert.assertEquals(
|
||||
pushController2Handler.tag,
|
||||
newController2.changeHandlerHistory.latestChangeHandler().tag
|
||||
)
|
||||
Assert.assertTrue(newController2.changeHandlerHistory.latestIsPush())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSetBackstackForPushHandlers() {
|
||||
val initialController = TestController()
|
||||
val initialTransaction = initialController.asTransaction(
|
||||
pushChangeHandler = MockChangeHandler.taggedHandler("initialPush1", true),
|
||||
popChangeHandler = MockChangeHandler.taggedHandler("initialPop1", true)
|
||||
)
|
||||
router.setRoot(initialTransaction)
|
||||
|
||||
val initialView = initialController.view
|
||||
val newController = TestController()
|
||||
val setBackstackHandler = MockChangeHandler.taggedHandler("setBackstackHandler", true)
|
||||
val newBackstack = listOf(initialTransaction, newController.asTransaction())
|
||||
router.setBackstack(newBackstack, setBackstackHandler)
|
||||
|
||||
Assert.assertTrue(initialController.changeHandlerHistory.isValidHistory)
|
||||
Assert.assertTrue(newController.changeHandlerHistory.isValidHistory)
|
||||
Assert.assertEquals(2, initialController.changeHandlerHistory.size().toLong())
|
||||
Assert.assertEquals(1, newController.changeHandlerHistory.size().toLong())
|
||||
Assert.assertNotNull(initialController.changeHandlerHistory.latestToView())
|
||||
Assert.assertEquals(
|
||||
newController.view,
|
||||
initialController.changeHandlerHistory.latestToView()
|
||||
)
|
||||
Assert.assertEquals(initialView, initialController.changeHandlerHistory.latestFromView())
|
||||
Assert.assertEquals(
|
||||
setBackstackHandler.tag,
|
||||
initialController.changeHandlerHistory.latestChangeHandler().tag
|
||||
)
|
||||
Assert.assertTrue(initialController.changeHandlerHistory.latestIsPush())
|
||||
Assert.assertTrue(newController.changeHandlerHistory.latestIsPush())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSetBackstackForInvertHandlersWithRemovesView() {
|
||||
val initialController1 = TestController()
|
||||
val initialTransaction1 = initialController1.asTransaction(
|
||||
pushChangeHandler = MockChangeHandler.taggedHandler("initialPush1", true),
|
||||
popChangeHandler = MockChangeHandler.taggedHandler("initialPop1", true)
|
||||
)
|
||||
router.setRoot(initialTransaction1)
|
||||
|
||||
val initialController2 = TestController()
|
||||
val initialTransaction2 = initialController2.asTransaction(
|
||||
pushChangeHandler = MockChangeHandler.taggedHandler("initialPush2", true),
|
||||
popChangeHandler = MockChangeHandler.taggedHandler("initialPop2", true)
|
||||
)
|
||||
router.pushController(initialTransaction2)
|
||||
|
||||
val initialView2 = initialController2.view
|
||||
val setBackstackHandler = MockChangeHandler.taggedHandler("setBackstackHandler", true)
|
||||
val newBackstack = listOf(initialTransaction2, initialTransaction1)
|
||||
router.setBackstack(newBackstack, setBackstackHandler)
|
||||
|
||||
Assert.assertTrue(initialController1.changeHandlerHistory.isValidHistory)
|
||||
Assert.assertTrue(initialController2.changeHandlerHistory.isValidHistory)
|
||||
Assert.assertEquals(3, initialController1.changeHandlerHistory.size().toLong())
|
||||
Assert.assertEquals(2, initialController2.changeHandlerHistory.size().toLong())
|
||||
Assert.assertNotNull(initialController1.changeHandlerHistory.latestToView())
|
||||
Assert.assertEquals(initialView2, initialController1.changeHandlerHistory.latestFromView())
|
||||
Assert.assertEquals(
|
||||
setBackstackHandler.tag,
|
||||
initialController1.changeHandlerHistory.latestChangeHandler().tag
|
||||
)
|
||||
Assert.assertFalse(initialController1.changeHandlerHistory.latestIsPush())
|
||||
Assert.assertNotNull(initialController2.changeHandlerHistory.latestToView())
|
||||
Assert.assertEquals(initialView2, initialController2.changeHandlerHistory.latestFromView())
|
||||
Assert.assertEquals(
|
||||
setBackstackHandler.tag,
|
||||
initialController2.changeHandlerHistory.latestChangeHandler().tag
|
||||
)
|
||||
Assert.assertFalse(initialController2.changeHandlerHistory.latestIsPush())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSetBackstackForInvertHandlersWithoutRemovesView() {
|
||||
val initialController1 = TestController()
|
||||
val initialTransaction1 = initialController1.asTransaction(
|
||||
pushChangeHandler = MockChangeHandler.taggedHandler("initialPush1", true),
|
||||
popChangeHandler = MockChangeHandler.taggedHandler("initialPop1", true)
|
||||
)
|
||||
router.setRoot(initialTransaction1)
|
||||
|
||||
val initialController2 = TestController()
|
||||
val initialPushHandler2Tag = "initialPush2"
|
||||
val initialTransaction2 = initialController2.asTransaction(
|
||||
pushChangeHandler = MockChangeHandler.taggedHandler(initialPushHandler2Tag, false),
|
||||
popChangeHandler = MockChangeHandler.taggedHandler("initialPop2", false)
|
||||
)
|
||||
router.pushController(initialTransaction2)
|
||||
|
||||
val initialView1 = initialController1.view
|
||||
val initialView2 = initialController2.view
|
||||
val setBackstackHandler = MockChangeHandler.taggedHandler("setBackstackHandler", true)
|
||||
val newBackstack = listOf(initialTransaction2, initialTransaction1)
|
||||
router.setBackstack(newBackstack, setBackstackHandler)
|
||||
|
||||
Assert.assertTrue(initialController1.changeHandlerHistory.isValidHistory)
|
||||
Assert.assertTrue(initialController2.changeHandlerHistory.isValidHistory)
|
||||
Assert.assertEquals(2, initialController1.changeHandlerHistory.size().toLong())
|
||||
Assert.assertEquals(2, initialController2.changeHandlerHistory.size().toLong())
|
||||
Assert.assertNotNull(initialController1.changeHandlerHistory.latestToView())
|
||||
Assert.assertEquals(initialView1, initialController1.changeHandlerHistory.latestFromView())
|
||||
Assert.assertEquals(
|
||||
initialPushHandler2Tag,
|
||||
initialController1.changeHandlerHistory.latestChangeHandler().tag
|
||||
)
|
||||
Assert.assertTrue(initialController1.changeHandlerHistory.latestIsPush())
|
||||
Assert.assertNull(initialController2.changeHandlerHistory.latestToView())
|
||||
Assert.assertEquals(initialView2, initialController2.changeHandlerHistory.latestFromView())
|
||||
Assert.assertEquals(
|
||||
setBackstackHandler.tag,
|
||||
initialController2.changeHandlerHistory.latestChangeHandler().tag
|
||||
)
|
||||
Assert.assertFalse(initialController2.changeHandlerHistory.latestIsPush())
|
||||
}
|
||||
}
|
||||
@@ -1,496 +0,0 @@
|
||||
package com.bluelinelabs.conductor;
|
||||
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.bluelinelabs.conductor.Controller.LifecycleListener;
|
||||
import com.bluelinelabs.conductor.changehandler.FadeChangeHandler;
|
||||
import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler;
|
||||
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 java.util.Arrays;
|
||||
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 {
|
||||
|
||||
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 testSetRoot() {
|
||||
String rootTag = "root";
|
||||
|
||||
Controller rootController = new TestController();
|
||||
|
||||
assertFalse(router.hasRootController());
|
||||
|
||||
router.setRoot(RouterTransaction.with(rootController).tag(rootTag));
|
||||
|
||||
assertTrue(router.hasRootController());
|
||||
|
||||
assertEquals(rootController, router.getControllerWithTag(rootTag));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetNewRoot() {
|
||||
String oldRootTag = "oldRoot";
|
||||
String newRootTag = "newRoot";
|
||||
|
||||
Controller oldRootController = new TestController();
|
||||
Controller newRootController = new TestController();
|
||||
|
||||
router.setRoot(RouterTransaction.with(oldRootController).tag(oldRootTag));
|
||||
router.setRoot(RouterTransaction.with(newRootController).tag(newRootTag));
|
||||
|
||||
assertNull(router.getControllerWithTag(oldRootTag));
|
||||
assertEquals(newRootController, router.getControllerWithTag(newRootTag));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetByInstanceId() {
|
||||
Controller controller = new TestController();
|
||||
|
||||
router.pushController(RouterTransaction.with(controller));
|
||||
|
||||
assertEquals(controller, router.getControllerWithInstanceId(controller.getInstanceId()));
|
||||
assertNull(router.getControllerWithInstanceId("fake id"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetByTag() {
|
||||
String controller1Tag = "controller1";
|
||||
String controller2Tag = "controller2";
|
||||
|
||||
Controller controller1 = new TestController();
|
||||
Controller controller2 = new TestController();
|
||||
|
||||
router.pushController(RouterTransaction.with(controller1)
|
||||
.tag(controller1Tag));
|
||||
|
||||
router.pushController(RouterTransaction.with(controller2)
|
||||
.tag(controller2Tag));
|
||||
|
||||
assertEquals(controller1, router.getControllerWithTag(controller1Tag));
|
||||
assertEquals(controller2, router.getControllerWithTag(controller2Tag));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPushPopControllers() {
|
||||
String controller1Tag = "controller1";
|
||||
String controller2Tag = "controller2";
|
||||
|
||||
Controller controller1 = new TestController();
|
||||
Controller controller2 = new TestController();
|
||||
|
||||
router.pushController(RouterTransaction.with(controller1)
|
||||
.tag(controller1Tag));
|
||||
|
||||
assertEquals(1, router.getBackstackSize());
|
||||
|
||||
router.pushController(RouterTransaction.with(controller2)
|
||||
.tag(controller2Tag));
|
||||
|
||||
assertEquals(2, router.getBackstackSize());
|
||||
|
||||
router.popCurrentController();
|
||||
|
||||
assertEquals(1, router.getBackstackSize());
|
||||
|
||||
assertEquals(controller1, router.getControllerWithTag(controller1Tag));
|
||||
assertNull(router.getControllerWithTag(controller2Tag));
|
||||
|
||||
router.popCurrentController();
|
||||
|
||||
assertEquals(0, router.getBackstackSize());
|
||||
|
||||
assertNull(router.getControllerWithTag(controller1Tag));
|
||||
assertNull(router.getControllerWithTag(controller2Tag));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPopControllerConcurrentModificationException() {
|
||||
int step = 1;
|
||||
for (int i = 0; i < 10; i++, step++) {
|
||||
router.pushController(RouterTransaction.with(new TestController()).tag("1"));
|
||||
router.pushController(RouterTransaction.with(new TestController()).tag("2"));
|
||||
router.pushController(RouterTransaction.with(new TestController()).tag("3"));
|
||||
|
||||
String tag;
|
||||
if (step == 1) {
|
||||
tag = "1";
|
||||
} else if (step == 2) {
|
||||
tag = "2";
|
||||
} else {
|
||||
tag = "3";
|
||||
step = 0;
|
||||
}
|
||||
Controller controller = router.getControllerWithTag(tag);
|
||||
if (controller != null) {
|
||||
router.popController(controller);
|
||||
}
|
||||
router.popToRoot();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPopToTag() {
|
||||
String controller1Tag = "controller1";
|
||||
String controller2Tag = "controller2";
|
||||
String controller3Tag = "controller3";
|
||||
String controller4Tag = "controller4";
|
||||
|
||||
Controller controller1 = new TestController();
|
||||
Controller controller2 = new TestController();
|
||||
Controller controller3 = new TestController();
|
||||
Controller controller4 = new TestController();
|
||||
|
||||
router.pushController(RouterTransaction.with(controller1)
|
||||
.tag(controller1Tag));
|
||||
|
||||
router.pushController(RouterTransaction.with(controller2)
|
||||
.tag(controller2Tag));
|
||||
|
||||
router.pushController(RouterTransaction.with(controller3)
|
||||
.tag(controller3Tag));
|
||||
|
||||
router.pushController(RouterTransaction.with(controller4)
|
||||
.tag(controller4Tag));
|
||||
|
||||
router.popToTag(controller2Tag);
|
||||
|
||||
assertEquals(2, router.getBackstackSize());
|
||||
assertEquals(controller1, router.getControllerWithTag(controller1Tag));
|
||||
assertEquals(controller2, router.getControllerWithTag(controller2Tag));
|
||||
assertNull(router.getControllerWithTag(controller3Tag));
|
||||
assertNull(router.getControllerWithTag(controller4Tag));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPopNonCurrent() {
|
||||
String controller1Tag = "controller1";
|
||||
String controller2Tag = "controller2";
|
||||
String controller3Tag = "controller3";
|
||||
|
||||
Controller controller1 = new TestController();
|
||||
Controller controller2 = new TestController();
|
||||
Controller controller3 = new TestController();
|
||||
|
||||
router.pushController(RouterTransaction.with(controller1)
|
||||
.tag(controller1Tag));
|
||||
|
||||
router.pushController(RouterTransaction.with(controller2)
|
||||
.tag(controller2Tag));
|
||||
|
||||
router.pushController(RouterTransaction.with(controller3)
|
||||
.tag(controller3Tag));
|
||||
|
||||
router.popController(controller2);
|
||||
|
||||
assertEquals(2, router.getBackstackSize());
|
||||
assertEquals(controller1, router.getControllerWithTag(controller1Tag));
|
||||
assertNull(router.getControllerWithTag(controller2Tag));
|
||||
assertEquals(controller3, router.getControllerWithTag(controller3Tag));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetBackstack() {
|
||||
RouterTransaction rootTransaction = RouterTransaction.with(new TestController());
|
||||
RouterTransaction middleTransaction = RouterTransaction.with(new TestController());
|
||||
RouterTransaction topTransaction = RouterTransaction.with(new TestController());
|
||||
|
||||
List<RouterTransaction> backstack = Arrays.asList(rootTransaction, middleTransaction, topTransaction);
|
||||
router.setBackstack(backstack, null);
|
||||
|
||||
assertEquals(3, router.getBackstackSize());
|
||||
|
||||
List<RouterTransaction> fetchedBackstack = router.getBackstack();
|
||||
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()));
|
||||
|
||||
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 = Arrays.asList(rootTransaction, middleTransaction, topTransaction);
|
||||
router.setBackstack(backstack, null);
|
||||
|
||||
assertEquals(3, router.getBackstackSize());
|
||||
|
||||
List<RouterTransaction> fetchedBackstack = router.getBackstack();
|
||||
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(MockChangeHandler.noRemoveViewOnPushHandler());
|
||||
|
||||
router.setRoot(oldRootTransaction);
|
||||
router.pushController(oldTopTransaction);
|
||||
assertEquals(2, router.getBackstackSize());
|
||||
|
||||
assertTrue(oldRootTransaction.controller().isAttached());
|
||||
assertTrue(oldTopTransaction.controller().isAttached());
|
||||
|
||||
RouterTransaction rootTransaction = RouterTransaction.with(new TestController());
|
||||
RouterTransaction middleTransaction = RouterTransaction.with(new TestController()).pushChangeHandler(MockChangeHandler.noRemoveViewOnPushHandler());
|
||||
RouterTransaction topTransaction = RouterTransaction.with(new TestController()).pushChangeHandler(MockChangeHandler.noRemoveViewOnPushHandler());
|
||||
|
||||
List<RouterTransaction> backstack = Arrays.asList(rootTransaction, middleTransaction, topTransaction);
|
||||
router.setBackstack(backstack, null);
|
||||
|
||||
assertEquals(3, router.getBackstackSize());
|
||||
|
||||
List<RouterTransaction> fetchedBackstack = router.getBackstack();
|
||||
assertEquals(rootTransaction, fetchedBackstack.get(0));
|
||||
assertEquals(middleTransaction, fetchedBackstack.get(1));
|
||||
assertEquals(topTransaction, fetchedBackstack.get(2));
|
||||
|
||||
assertFalse(oldRootTransaction.controller().isAttached());
|
||||
assertFalse(oldTopTransaction.controller().isAttached());
|
||||
assertTrue(rootTransaction.controller().isAttached());
|
||||
assertTrue(middleTransaction.controller().isAttached());
|
||||
assertTrue(topTransaction.controller().isAttached());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPopToRoot() {
|
||||
RouterTransaction rootTransaction = RouterTransaction.with(new TestController());
|
||||
RouterTransaction transaction1 = RouterTransaction.with(new TestController());
|
||||
RouterTransaction transaction2 = RouterTransaction.with(new TestController());
|
||||
|
||||
List<RouterTransaction> backstack = Arrays.asList(rootTransaction, transaction1, transaction2);
|
||||
router.setBackstack(backstack, null);
|
||||
|
||||
assertEquals(3, router.getBackstackSize());
|
||||
|
||||
router.popToRoot();
|
||||
|
||||
assertEquals(1, router.getBackstackSize());
|
||||
assertEquals(rootTransaction, router.getBackstack().get(0));
|
||||
|
||||
assertTrue(rootTransaction.controller().isAttached());
|
||||
assertFalse(transaction1.controller().isAttached());
|
||||
assertFalse(transaction2.controller().isAttached());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPopToRootWithNoRemoveViewOnPush() {
|
||||
RouterTransaction rootTransaction = RouterTransaction.with(new TestController()).pushChangeHandler(new HorizontalChangeHandler(false));
|
||||
RouterTransaction transaction1 = RouterTransaction.with(new TestController()).pushChangeHandler(new HorizontalChangeHandler(false));
|
||||
RouterTransaction transaction2 = RouterTransaction.with(new TestController()).pushChangeHandler(new HorizontalChangeHandler(false));
|
||||
|
||||
List<RouterTransaction> backstack = Arrays.asList(rootTransaction, transaction1, transaction2);
|
||||
router.setBackstack(backstack, null);
|
||||
|
||||
assertEquals(3, router.getBackstackSize());
|
||||
|
||||
router.popToRoot();
|
||||
|
||||
assertEquals(1, router.getBackstackSize());
|
||||
assertEquals(rootTransaction, router.getBackstack().get(0));
|
||||
|
||||
assertTrue(rootTransaction.controller().isAttached());
|
||||
assertFalse(transaction1.controller().isAttached());
|
||||
assertFalse(transaction2.controller().isAttached());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReplaceTopController() {
|
||||
RouterTransaction rootTransaction = RouterTransaction.with(new TestController());
|
||||
RouterTransaction topTransaction = RouterTransaction.with(new TestController());
|
||||
|
||||
List<RouterTransaction> backstack = Arrays.asList(rootTransaction, topTransaction);
|
||||
router.setBackstack(backstack, null);
|
||||
|
||||
assertEquals(2, router.getBackstackSize());
|
||||
|
||||
List<RouterTransaction> fetchedBackstack = router.getBackstack();
|
||||
assertEquals(rootTransaction, fetchedBackstack.get(0));
|
||||
assertEquals(topTransaction, fetchedBackstack.get(1));
|
||||
|
||||
RouterTransaction newTopTransaction = RouterTransaction.with(new TestController());
|
||||
router.replaceTopController(newTopTransaction);
|
||||
|
||||
assertEquals(2, router.getBackstackSize());
|
||||
|
||||
fetchedBackstack = router.getBackstack();
|
||||
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(MockChangeHandler.noRemoveViewOnPushHandler());
|
||||
|
||||
List<RouterTransaction> backstack = Arrays.asList(rootTransaction, topTransaction);
|
||||
router.setBackstack(backstack, null);
|
||||
|
||||
assertEquals(2, router.getBackstackSize());
|
||||
|
||||
assertTrue(rootTransaction.controller().isAttached());
|
||||
assertTrue(topTransaction.controller().isAttached());
|
||||
|
||||
List<RouterTransaction> fetchedBackstack = router.getBackstack();
|
||||
assertEquals(rootTransaction, fetchedBackstack.get(0));
|
||||
assertEquals(topTransaction, fetchedBackstack.get(1));
|
||||
|
||||
RouterTransaction newTopTransaction = RouterTransaction.with(new TestController()).pushChangeHandler(MockChangeHandler.noRemoveViewOnPushHandler());
|
||||
router.replaceTopController(newTopTransaction);
|
||||
newTopTransaction.pushChangeHandler().completeImmediately();
|
||||
|
||||
assertEquals(2, router.getBackstackSize());
|
||||
|
||||
fetchedBackstack = router.getBackstack();
|
||||
assertEquals(rootTransaction, fetchedBackstack.get(0));
|
||||
assertEquals(newTopTransaction, fetchedBackstack.get(1));
|
||||
|
||||
assertTrue(rootTransaction.controller().isAttached());
|
||||
assertFalse(topTransaction.controller().isAttached());
|
||||
assertTrue(newTopTransaction.controller().isAttached());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRearrangeTransactionBackstack() {
|
||||
RouterTransaction transaction1 = RouterTransaction.with(new TestController());
|
||||
RouterTransaction transaction2 = RouterTransaction.with(new TestController());
|
||||
|
||||
List<RouterTransaction> backstack = Arrays.asList(transaction1, transaction2);
|
||||
router.setBackstack(backstack, null);
|
||||
|
||||
assertEquals(1, transaction1.getTransactionIndex());
|
||||
assertEquals(2, transaction2.getTransactionIndex());
|
||||
|
||||
backstack = Arrays.asList(transaction2, transaction1);
|
||||
router.setBackstack(backstack, null);
|
||||
|
||||
assertEquals(1, transaction2.getTransactionIndex());
|
||||
assertEquals(2, transaction1.getTransactionIndex());
|
||||
|
||||
router.handleBack();
|
||||
|
||||
assertEquals(1, router.getBackstackSize());
|
||||
assertEquals(transaction2, router.getBackstack().get(0));
|
||||
|
||||
router.handleBack();
|
||||
assertEquals(0, router.getBackstackSize());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testChildRouterRearrangeTransactionBackstack() {
|
||||
Controller parent = new TestController();
|
||||
router.setRoot(RouterTransaction.with(parent));
|
||||
|
||||
Router childRouter = parent.getChildRouter((ViewGroup)parent.getView().findViewById(TestController.CHILD_VIEW_ID_1));
|
||||
|
||||
RouterTransaction transaction1 = RouterTransaction.with(new TestController());
|
||||
RouterTransaction transaction2 = RouterTransaction.with(new TestController());
|
||||
|
||||
List<RouterTransaction> backstack = Arrays.asList(transaction1, transaction2);
|
||||
childRouter.setBackstack(backstack, null);
|
||||
|
||||
assertEquals(2, transaction1.getTransactionIndex());
|
||||
assertEquals(3, transaction2.getTransactionIndex());
|
||||
|
||||
backstack = Arrays.asList(transaction2, transaction1);
|
||||
childRouter.setBackstack(backstack, null);
|
||||
|
||||
assertEquals(2, transaction2.getTransactionIndex());
|
||||
assertEquals(3, transaction1.getTransactionIndex());
|
||||
|
||||
childRouter.handleBack();
|
||||
|
||||
assertEquals(1, childRouter.getBackstackSize());
|
||||
assertEquals(transaction2, childRouter.getBackstack().get(0));
|
||||
|
||||
childRouter.handleBack();
|
||||
assertEquals(0, childRouter.getBackstackSize());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemovesAllViewsOnDestroy() {
|
||||
Controller controller1 = new TestController();
|
||||
Controller controller2 = new TestController();
|
||||
|
||||
router.setRoot(RouterTransaction.with(controller1));
|
||||
router.pushController(RouterTransaction.with(controller2)
|
||||
.pushChangeHandler(new FadeChangeHandler(false)));
|
||||
|
||||
assertEquals(2, router.container.getChildCount());
|
||||
|
||||
router.destroy(true);
|
||||
|
||||
assertEquals(0, router.container.getChildCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsBeingDestroyed() {
|
||||
final LifecycleListener lifecycleListener = new LifecycleListener() {
|
||||
@Override
|
||||
public void preDestroyView(@NonNull Controller controller, @NonNull View view) {
|
||||
assertTrue(controller.isBeingDestroyed());
|
||||
}
|
||||
};
|
||||
|
||||
Controller controller1 = new TestController();
|
||||
Controller controller2 = new TestController();
|
||||
controller2.addLifecycleListener(lifecycleListener);
|
||||
|
||||
router.setRoot(RouterTransaction.with(controller1));
|
||||
router.pushController(RouterTransaction.with(controller2));
|
||||
assertFalse(controller1.isBeingDestroyed());
|
||||
assertFalse(controller2.isBeingDestroyed());
|
||||
|
||||
router.popCurrentController();
|
||||
assertFalse(controller1.isBeingDestroyed());
|
||||
assertTrue(controller2.isBeingDestroyed());
|
||||
|
||||
Controller controller3 = new TestController();
|
||||
controller3.addLifecycleListener(lifecycleListener);
|
||||
router.pushController(RouterTransaction.with(controller3));
|
||||
assertFalse(controller1.isBeingDestroyed());
|
||||
assertFalse(controller3.isBeingDestroyed());
|
||||
|
||||
router.popToRoot();
|
||||
assertFalse(controller1.isBeingDestroyed());
|
||||
assertTrue(controller3.isBeingDestroyed());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,436 @@
|
||||
package com.bluelinelabs.conductor
|
||||
|
||||
import android.view.View
|
||||
import com.bluelinelabs.conductor.Controller.LifecycleListener
|
||||
import com.bluelinelabs.conductor.changehandler.FadeChangeHandler
|
||||
import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler
|
||||
import com.bluelinelabs.conductor.util.MockChangeHandler
|
||||
import com.bluelinelabs.conductor.util.TestActivity
|
||||
import org.junit.Assert
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.Robolectric
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import org.robolectric.annotation.Config
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
@Config(manifest = Config.NONE)
|
||||
class RouterTests {
|
||||
|
||||
private val router = Robolectric.buildActivity(TestActivity::class.java)
|
||||
.setup()
|
||||
.get()
|
||||
.router
|
||||
|
||||
@Test
|
||||
fun testSetRoot() {
|
||||
val rootTag = "root"
|
||||
val rootController = TestController()
|
||||
Assert.assertFalse(router.hasRootController())
|
||||
|
||||
router.setRoot(RouterTransaction.with(rootController).tag(rootTag))
|
||||
Assert.assertTrue(router.hasRootController())
|
||||
Assert.assertEquals(rootController, router.getControllerWithTag(rootTag))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSetNewRoot() {
|
||||
val oldRootTag = "oldRoot"
|
||||
val newRootTag = "newRoot"
|
||||
val oldRootController = TestController()
|
||||
val newRootController = TestController()
|
||||
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))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testGetByInstanceId() {
|
||||
val controller = TestController()
|
||||
router.pushController(controller.asTransaction())
|
||||
Assert.assertEquals(
|
||||
controller,
|
||||
router.getControllerWithInstanceId(controller.getInstanceId())
|
||||
)
|
||||
Assert.assertNull(router.getControllerWithInstanceId("fake id"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testGetByTag() {
|
||||
val controller1Tag = "controller1"
|
||||
val controller2Tag = "controller2"
|
||||
val controller1 = TestController()
|
||||
val controller2 = TestController()
|
||||
router.pushController(
|
||||
RouterTransaction.with(controller1).tag(controller1Tag)
|
||||
)
|
||||
router.pushController(
|
||||
RouterTransaction.with(controller2).tag(controller2Tag)
|
||||
)
|
||||
Assert.assertEquals(controller1, router.getControllerWithTag(controller1Tag))
|
||||
Assert.assertEquals(controller2, router.getControllerWithTag(controller2Tag))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testPushPopControllers() {
|
||||
val controller1Tag = "controller1"
|
||||
val controller2Tag = "controller2"
|
||||
val controller1 = TestController()
|
||||
val controller2 = TestController()
|
||||
router.pushController(
|
||||
RouterTransaction.with(controller1).tag(controller1Tag)
|
||||
)
|
||||
Assert.assertEquals(1, router.backstackSize.toLong())
|
||||
|
||||
router.pushController(
|
||||
RouterTransaction.with(controller2).tag(controller2Tag)
|
||||
)
|
||||
Assert.assertEquals(2, router.backstackSize.toLong())
|
||||
|
||||
router.popCurrentController()
|
||||
Assert.assertEquals(1, router.backstackSize.toLong())
|
||||
Assert.assertEquals(controller1, router.getControllerWithTag(controller1Tag))
|
||||
Assert.assertNull(router.getControllerWithTag(controller2Tag))
|
||||
|
||||
router.popCurrentController()
|
||||
Assert.assertEquals(0, router.backstackSize.toLong())
|
||||
Assert.assertNull(router.getControllerWithTag(controller1Tag))
|
||||
Assert.assertNull(router.getControllerWithTag(controller2Tag))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testPopControllerConcurrentModificationException() {
|
||||
var step = 1
|
||||
var i = 0
|
||||
while (i < 10) {
|
||||
router.pushController(RouterTransaction.with(TestController()).tag("1"))
|
||||
router.pushController(RouterTransaction.with(TestController()).tag("2"))
|
||||
router.pushController(RouterTransaction.with(TestController()).tag("3"))
|
||||
val tag = when (step) {
|
||||
1 -> "1"
|
||||
2 -> "2"
|
||||
else -> {
|
||||
step = 0
|
||||
"3"
|
||||
}
|
||||
}
|
||||
val controller = router.getControllerWithTag(tag)
|
||||
if (controller != null) {
|
||||
router.popController(controller)
|
||||
}
|
||||
router.popToRoot()
|
||||
i++
|
||||
step++
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testPopToTag() {
|
||||
val controller1Tag = "controller1"
|
||||
val controller2Tag = "controller2"
|
||||
val controller3Tag = "controller3"
|
||||
val controller4Tag = "controller4"
|
||||
val controller1 = TestController()
|
||||
val controller2 = TestController()
|
||||
val controller3 = TestController()
|
||||
val controller4 = TestController()
|
||||
router.pushController(
|
||||
RouterTransaction.with(controller1).tag(controller1Tag)
|
||||
)
|
||||
router.pushController(
|
||||
RouterTransaction.with(controller2).tag(controller2Tag)
|
||||
)
|
||||
router.pushController(
|
||||
RouterTransaction.with(controller3).tag(controller3Tag)
|
||||
)
|
||||
router.pushController(
|
||||
RouterTransaction.with(controller4).tag(controller4Tag)
|
||||
)
|
||||
router.popToTag(controller2Tag)
|
||||
|
||||
Assert.assertEquals(2, router.backstackSize.toLong())
|
||||
Assert.assertEquals(controller1, router.getControllerWithTag(controller1Tag))
|
||||
Assert.assertEquals(controller2, router.getControllerWithTag(controller2Tag))
|
||||
Assert.assertNull(router.getControllerWithTag(controller3Tag))
|
||||
Assert.assertNull(router.getControllerWithTag(controller4Tag))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testPopNonCurrent() {
|
||||
val controller1Tag = "controller1"
|
||||
val controller2Tag = "controller2"
|
||||
val controller3Tag = "controller3"
|
||||
val controller1 = TestController()
|
||||
val controller2 = TestController()
|
||||
val controller3 = TestController()
|
||||
router.pushController(
|
||||
RouterTransaction.with(controller1).tag(controller1Tag)
|
||||
)
|
||||
router.pushController(
|
||||
RouterTransaction.with(controller2).tag(controller2Tag)
|
||||
)
|
||||
router.pushController(
|
||||
RouterTransaction.with(controller3).tag(controller3Tag)
|
||||
)
|
||||
router.popController(controller2)
|
||||
|
||||
Assert.assertEquals(2, router.backstackSize.toLong())
|
||||
Assert.assertEquals(controller1, router.getControllerWithTag(controller1Tag))
|
||||
Assert.assertNull(router.getControllerWithTag(controller2Tag))
|
||||
Assert.assertEquals(controller3, router.getControllerWithTag(controller3Tag))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSetBackstack() {
|
||||
val rootTransaction = TestController().asTransaction()
|
||||
val middleTransaction = TestController().asTransaction()
|
||||
val topTransaction = TestController().asTransaction()
|
||||
val backstack = listOf(rootTransaction, middleTransaction, topTransaction)
|
||||
router.setBackstack(backstack, null)
|
||||
Assert.assertEquals(3, router.backstackSize.toLong())
|
||||
|
||||
val fetchedBackstack = router.getBackstack()
|
||||
Assert.assertEquals(rootTransaction, fetchedBackstack[0])
|
||||
Assert.assertEquals(middleTransaction, fetchedBackstack[1])
|
||||
Assert.assertEquals(topTransaction, fetchedBackstack[2])
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNewSetBackstack() {
|
||||
router.setRoot(TestController().asTransaction())
|
||||
Assert.assertEquals(1, router.backstackSize.toLong())
|
||||
|
||||
val rootTransaction = TestController().asTransaction()
|
||||
val middleTransaction = TestController().asTransaction()
|
||||
val topTransaction = TestController().asTransaction()
|
||||
val backstack = listOf(rootTransaction, middleTransaction, topTransaction)
|
||||
router.setBackstack(backstack, null)
|
||||
|
||||
Assert.assertEquals(3, router.backstackSize.toLong())
|
||||
val fetchedBackstack = router.getBackstack()
|
||||
Assert.assertEquals(rootTransaction, fetchedBackstack[0])
|
||||
Assert.assertEquals(middleTransaction, fetchedBackstack[1])
|
||||
Assert.assertEquals(topTransaction, fetchedBackstack[2])
|
||||
Assert.assertEquals(router, rootTransaction.controller.getRouter())
|
||||
Assert.assertEquals(router, middleTransaction.controller.getRouter())
|
||||
Assert.assertEquals(router, topTransaction.controller.getRouter())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNewSetBackstackWithNoRemoveViewOnPush() {
|
||||
val oldRootTransaction = TestController().asTransaction()
|
||||
val oldTopTransaction = TestController().asTransaction(
|
||||
pushChangeHandler = MockChangeHandler.noRemoveViewOnPushHandler()
|
||||
)
|
||||
router.setRoot(oldRootTransaction)
|
||||
router.pushController(oldTopTransaction)
|
||||
Assert.assertEquals(2, router.backstackSize.toLong())
|
||||
Assert.assertTrue(oldRootTransaction.controller.isAttached)
|
||||
Assert.assertTrue(oldTopTransaction.controller.isAttached)
|
||||
|
||||
val rootTransaction = TestController().asTransaction()
|
||||
val middleTransaction = TestController().asTransaction(
|
||||
pushChangeHandler = MockChangeHandler.noRemoveViewOnPushHandler()
|
||||
)
|
||||
val topTransaction = TestController().asTransaction(
|
||||
pushChangeHandler = MockChangeHandler.noRemoveViewOnPushHandler()
|
||||
)
|
||||
val backstack = listOf(rootTransaction, middleTransaction, topTransaction)
|
||||
router.setBackstack(backstack, null)
|
||||
|
||||
Assert.assertEquals(3, router.backstackSize.toLong())
|
||||
val fetchedBackstack = router.getBackstack()
|
||||
Assert.assertEquals(rootTransaction, fetchedBackstack[0])
|
||||
Assert.assertEquals(middleTransaction, fetchedBackstack[1])
|
||||
Assert.assertEquals(topTransaction, fetchedBackstack[2])
|
||||
Assert.assertFalse(oldRootTransaction.controller.isAttached)
|
||||
Assert.assertFalse(oldTopTransaction.controller.isAttached)
|
||||
Assert.assertTrue(rootTransaction.controller.isAttached)
|
||||
Assert.assertTrue(middleTransaction.controller.isAttached)
|
||||
Assert.assertTrue(topTransaction.controller.isAttached)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testPopToRoot() {
|
||||
val rootTransaction = TestController().asTransaction()
|
||||
val transaction1 = TestController().asTransaction()
|
||||
val transaction2 = TestController().asTransaction()
|
||||
val backstack = listOf(rootTransaction, transaction1, transaction2)
|
||||
router.setBackstack(backstack, null)
|
||||
Assert.assertEquals(3, router.backstackSize.toLong())
|
||||
|
||||
router.popToRoot()
|
||||
Assert.assertEquals(1, router.backstackSize.toLong())
|
||||
Assert.assertEquals(rootTransaction, router.getBackstack()[0])
|
||||
Assert.assertTrue(rootTransaction.controller.isAttached)
|
||||
Assert.assertFalse(transaction1.controller.isAttached)
|
||||
Assert.assertFalse(transaction2.controller.isAttached)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testPopToRootWithNoRemoveViewOnPush() {
|
||||
val rootTransaction = TestController().asTransaction(
|
||||
pushChangeHandler = HorizontalChangeHandler(false)
|
||||
)
|
||||
val transaction1 = TestController().asTransaction(
|
||||
pushChangeHandler = HorizontalChangeHandler(false)
|
||||
)
|
||||
val transaction2 = TestController().asTransaction(
|
||||
pushChangeHandler = HorizontalChangeHandler(false)
|
||||
)
|
||||
val backstack = listOf(rootTransaction, transaction1, transaction2)
|
||||
router.setBackstack(backstack, null)
|
||||
Assert.assertEquals(3, router.backstackSize.toLong())
|
||||
|
||||
router.popToRoot()
|
||||
Assert.assertEquals(1, router.backstackSize.toLong())
|
||||
Assert.assertEquals(rootTransaction, router.getBackstack()[0])
|
||||
Assert.assertTrue(rootTransaction.controller.isAttached)
|
||||
Assert.assertFalse(transaction1.controller.isAttached)
|
||||
Assert.assertFalse(transaction2.controller.isAttached)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testReplaceTopController() {
|
||||
val rootTransaction = TestController().asTransaction()
|
||||
val topTransaction = TestController().asTransaction()
|
||||
val backstack = listOf(rootTransaction, topTransaction)
|
||||
router.setBackstack(backstack, null)
|
||||
Assert.assertEquals(2, router.backstackSize.toLong())
|
||||
|
||||
var fetchedBackstack = router.getBackstack()
|
||||
Assert.assertEquals(rootTransaction, fetchedBackstack[0])
|
||||
Assert.assertEquals(topTransaction, fetchedBackstack[1])
|
||||
|
||||
val newTopTransaction = TestController().asTransaction()
|
||||
router.replaceTopController(newTopTransaction)
|
||||
Assert.assertEquals(2, router.backstackSize.toLong())
|
||||
fetchedBackstack = router.getBackstack()
|
||||
Assert.assertEquals(rootTransaction, fetchedBackstack[0])
|
||||
Assert.assertEquals(newTopTransaction, fetchedBackstack[1])
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testReplaceTopControllerWithNoRemoveViewOnPush() {
|
||||
val rootTransaction = TestController().asTransaction()
|
||||
val topTransaction = TestController().asTransaction(
|
||||
pushChangeHandler = MockChangeHandler.noRemoveViewOnPushHandler()
|
||||
)
|
||||
val backstack = listOf(rootTransaction, topTransaction)
|
||||
router.setBackstack(backstack, null)
|
||||
Assert.assertEquals(2, router.backstackSize.toLong())
|
||||
Assert.assertTrue(rootTransaction.controller.isAttached)
|
||||
Assert.assertTrue(topTransaction.controller.isAttached)
|
||||
|
||||
var fetchedBackstack = router.getBackstack()
|
||||
Assert.assertEquals(rootTransaction, fetchedBackstack[0])
|
||||
Assert.assertEquals(topTransaction, fetchedBackstack[1])
|
||||
|
||||
val newTopTransaction = TestController().asTransaction(
|
||||
pushChangeHandler = MockChangeHandler.noRemoveViewOnPushHandler()
|
||||
)
|
||||
router.replaceTopController(newTopTransaction)
|
||||
newTopTransaction.pushChangeHandler()!!.completeImmediately()
|
||||
Assert.assertEquals(2, router.backstackSize.toLong())
|
||||
fetchedBackstack = router.getBackstack()
|
||||
Assert.assertEquals(rootTransaction, fetchedBackstack[0])
|
||||
Assert.assertEquals(newTopTransaction, fetchedBackstack[1])
|
||||
Assert.assertTrue(rootTransaction.controller.isAttached)
|
||||
Assert.assertFalse(topTransaction.controller.isAttached)
|
||||
Assert.assertTrue(newTopTransaction.controller.isAttached)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testRearrangeTransactionBackstack() {
|
||||
val transaction1 = TestController().asTransaction()
|
||||
val transaction2 = TestController().asTransaction()
|
||||
var backstack = listOf(transaction1, transaction2)
|
||||
router.setBackstack(backstack, null)
|
||||
Assert.assertEquals(1, transaction1.transactionIndex.toLong())
|
||||
Assert.assertEquals(2, transaction2.transactionIndex.toLong())
|
||||
|
||||
backstack = listOf(transaction2, transaction1)
|
||||
router.setBackstack(backstack, null)
|
||||
Assert.assertEquals(1, transaction2.transactionIndex.toLong())
|
||||
Assert.assertEquals(2, transaction1.transactionIndex.toLong())
|
||||
|
||||
router.handleBack()
|
||||
Assert.assertEquals(1, router.backstackSize.toLong())
|
||||
Assert.assertEquals(transaction2, router.getBackstack()[0])
|
||||
|
||||
router.handleBack()
|
||||
Assert.assertEquals(0, router.backstackSize.toLong())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testChildRouterRearrangeTransactionBackstack() {
|
||||
val parent = TestController()
|
||||
router.setRoot(parent.asTransaction())
|
||||
val childRouter = parent.getChildRouter(
|
||||
parent.view!!.findViewById(TestController.CHILD_VIEW_ID_1)
|
||||
)
|
||||
val transaction1 = TestController().asTransaction()
|
||||
val transaction2 = TestController().asTransaction()
|
||||
var backstack = listOf(transaction1, transaction2)
|
||||
childRouter.setBackstack(backstack, null)
|
||||
Assert.assertEquals(2, transaction1.transactionIndex.toLong())
|
||||
Assert.assertEquals(3, transaction2.transactionIndex.toLong())
|
||||
|
||||
backstack = listOf(transaction2, transaction1)
|
||||
childRouter.setBackstack(backstack, null)
|
||||
Assert.assertEquals(2, transaction2.transactionIndex.toLong())
|
||||
Assert.assertEquals(3, transaction1.transactionIndex.toLong())
|
||||
|
||||
childRouter.handleBack()
|
||||
Assert.assertEquals(1, childRouter.backstackSize.toLong())
|
||||
Assert.assertEquals(transaction2, childRouter.getBackstack()[0])
|
||||
|
||||
childRouter.handleBack()
|
||||
Assert.assertEquals(0, childRouter.backstackSize.toLong())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testRemovesAllViewsOnDestroy() {
|
||||
router.setRoot(TestController().asTransaction())
|
||||
router.pushController(
|
||||
TestController().asTransaction(
|
||||
pushChangeHandler = FadeChangeHandler(false)
|
||||
)
|
||||
)
|
||||
Assert.assertEquals(2, router.container.childCount.toLong())
|
||||
|
||||
router.destroy(true)
|
||||
Assert.assertEquals(0, router.container.childCount.toLong())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testIsBeingDestroyed() {
|
||||
val lifecycleListener: LifecycleListener = object : LifecycleListener() {
|
||||
override fun preDestroyView(controller: Controller, view: View) {
|
||||
Assert.assertTrue(controller.isBeingDestroyed())
|
||||
}
|
||||
}
|
||||
val controller1 = TestController()
|
||||
val controller2 = TestController()
|
||||
controller2.addLifecycleListener(lifecycleListener)
|
||||
router.setRoot(controller1.asTransaction())
|
||||
router.pushController(controller2.asTransaction())
|
||||
Assert.assertFalse(controller1.isBeingDestroyed())
|
||||
Assert.assertFalse(controller2.isBeingDestroyed())
|
||||
|
||||
router.popCurrentController()
|
||||
Assert.assertFalse(controller1.isBeingDestroyed())
|
||||
Assert.assertTrue(controller2.isBeingDestroyed())
|
||||
|
||||
val controller3 = TestController()
|
||||
controller3.addLifecycleListener(lifecycleListener)
|
||||
router.pushController(controller3.asTransaction())
|
||||
Assert.assertFalse(controller1.isBeingDestroyed())
|
||||
Assert.assertFalse(controller3.isBeingDestroyed())
|
||||
|
||||
router.popToRoot()
|
||||
Assert.assertFalse(controller1.isBeingDestroyed())
|
||||
Assert.assertTrue(controller3.isBeingDestroyed())
|
||||
}
|
||||
}
|
||||
@@ -1,106 +0,0 @@
|
||||
package com.bluelinelabs.conductor;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
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 {
|
||||
|
||||
private Router router;
|
||||
|
||||
public void createActivityController(Bundle savedInstanceState) {
|
||||
ActivityProxy activityProxy = new ActivityProxy().create(savedInstanceState).start().resume();
|
||||
router = Conductor.attachRouter(activityProxy.getActivity(), activityProxy.getView(), savedInstanceState);
|
||||
if (!router.hasRootController()) {
|
||||
router.setRoot(RouterTransaction.with(new TestController()));
|
||||
}
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
createActivityController(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSiblingTarget() {
|
||||
final TestController controllerA = new TestController();
|
||||
final TestController controllerB = new TestController();
|
||||
|
||||
assertNull(controllerA.getTargetController());
|
||||
assertNull(controllerB.getTargetController());
|
||||
|
||||
router.pushController(RouterTransaction.with(controllerA)
|
||||
.pushChangeHandler(MockChangeHandler.defaultHandler())
|
||||
.popChangeHandler(MockChangeHandler.defaultHandler()));
|
||||
|
||||
controllerB.setTargetController(controllerA);
|
||||
|
||||
router.pushController(RouterTransaction.with(controllerB)
|
||||
.pushChangeHandler(MockChangeHandler.defaultHandler())
|
||||
.popChangeHandler(MockChangeHandler.defaultHandler()));
|
||||
|
||||
assertNull(controllerA.getTargetController());
|
||||
assertEquals(controllerA, controllerB.getTargetController());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParentChildTarget() {
|
||||
final TestController controllerA = new TestController();
|
||||
final TestController controllerB = new TestController();
|
||||
|
||||
assertNull(controllerA.getTargetController());
|
||||
assertNull(controllerB.getTargetController());
|
||||
|
||||
router.pushController(RouterTransaction.with(controllerA)
|
||||
.pushChangeHandler(MockChangeHandler.defaultHandler())
|
||||
.popChangeHandler(MockChangeHandler.defaultHandler()));
|
||||
|
||||
controllerB.setTargetController(controllerA);
|
||||
|
||||
Router childRouter = controllerA.getChildRouter((ViewGroup)controllerA.getView().findViewById(TestController.VIEW_ID));
|
||||
childRouter.pushController(RouterTransaction.with(controllerB)
|
||||
.pushChangeHandler(MockChangeHandler.defaultHandler())
|
||||
.popChangeHandler(MockChangeHandler.defaultHandler()));
|
||||
|
||||
assertNull(controllerA.getTargetController());
|
||||
assertEquals(controllerA, controllerB.getTargetController());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testChildParentTarget() {
|
||||
final TestController controllerA = new TestController();
|
||||
final TestController controllerB = new TestController();
|
||||
|
||||
assertNull(controllerA.getTargetController());
|
||||
assertNull(controllerB.getTargetController());
|
||||
|
||||
router.pushController(RouterTransaction.with(controllerA)
|
||||
.pushChangeHandler(MockChangeHandler.defaultHandler())
|
||||
.popChangeHandler(MockChangeHandler.defaultHandler()));
|
||||
|
||||
controllerA.setTargetController(controllerB);
|
||||
|
||||
Router childRouter = controllerA.getChildRouter((ViewGroup)controllerA.getView().findViewById(TestController.VIEW_ID));
|
||||
childRouter.pushController(RouterTransaction.with(controllerB)
|
||||
.pushChangeHandler(MockChangeHandler.defaultHandler())
|
||||
.popChangeHandler(MockChangeHandler.defaultHandler()));
|
||||
|
||||
assertNull(controllerB.getTargetController());
|
||||
assertEquals(controllerB, controllerA.getTargetController());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
package com.bluelinelabs.conductor
|
||||
|
||||
import com.bluelinelabs.conductor.util.MockChangeHandler
|
||||
import com.bluelinelabs.conductor.util.TestActivity
|
||||
import org.junit.Assert
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.Robolectric
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import org.robolectric.annotation.Config
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
@Config(manifest = Config.NONE)
|
||||
class TargetControllerTests {
|
||||
|
||||
private val router = Robolectric.buildActivity(TestActivity::class.java)
|
||||
.setup()
|
||||
.get()
|
||||
.router
|
||||
|
||||
@Test
|
||||
fun testSiblingTarget() {
|
||||
val controllerA = TestController()
|
||||
val controllerB = TestController()
|
||||
Assert.assertNull(controllerA.targetController)
|
||||
Assert.assertNull(controllerB.targetController)
|
||||
|
||||
router.pushController(
|
||||
controllerA.asTransaction(
|
||||
pushChangeHandler = MockChangeHandler.defaultHandler(),
|
||||
popChangeHandler = MockChangeHandler.defaultHandler()
|
||||
)
|
||||
)
|
||||
controllerB.targetController = controllerA
|
||||
router.pushController(
|
||||
controllerB.asTransaction(
|
||||
pushChangeHandler = MockChangeHandler.defaultHandler(),
|
||||
popChangeHandler = MockChangeHandler.defaultHandler()
|
||||
)
|
||||
)
|
||||
Assert.assertNull(controllerA.targetController)
|
||||
Assert.assertEquals(controllerA, controllerB.targetController)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testParentChildTarget() {
|
||||
val controllerA = TestController()
|
||||
val controllerB = TestController()
|
||||
Assert.assertNull(controllerA.targetController)
|
||||
Assert.assertNull(controllerB.targetController)
|
||||
|
||||
router.pushController(
|
||||
controllerA.asTransaction(
|
||||
pushChangeHandler = MockChangeHandler.defaultHandler(),
|
||||
popChangeHandler = MockChangeHandler.defaultHandler()
|
||||
)
|
||||
)
|
||||
controllerB.targetController = controllerA
|
||||
val childRouter = controllerA.getChildRouter(
|
||||
controllerA.view!!.findViewById(TestController.VIEW_ID)
|
||||
)
|
||||
childRouter.pushController(
|
||||
controllerB.asTransaction(
|
||||
pushChangeHandler = MockChangeHandler.defaultHandler(),
|
||||
popChangeHandler = MockChangeHandler.defaultHandler()
|
||||
)
|
||||
)
|
||||
Assert.assertNull(controllerA.targetController)
|
||||
Assert.assertEquals(controllerA, controllerB.targetController)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testChildParentTarget() {
|
||||
val controllerA = TestController()
|
||||
val controllerB = TestController()
|
||||
Assert.assertNull(controllerA.targetController)
|
||||
Assert.assertNull(controllerB.targetController)
|
||||
|
||||
router.pushController(
|
||||
controllerA.asTransaction(
|
||||
pushChangeHandler = MockChangeHandler.defaultHandler(),
|
||||
popChangeHandler = MockChangeHandler.defaultHandler()
|
||||
)
|
||||
)
|
||||
controllerA.targetController = controllerB
|
||||
val childRouter = controllerA.getChildRouter(
|
||||
controllerA.view!!.findViewById(TestController.VIEW_ID)
|
||||
)
|
||||
childRouter.pushController(
|
||||
controllerB.asTransaction(
|
||||
pushChangeHandler = MockChangeHandler.defaultHandler(),
|
||||
popChangeHandler = MockChangeHandler.defaultHandler()
|
||||
)
|
||||
)
|
||||
Assert.assertNull(controllerB.targetController)
|
||||
Assert.assertEquals(controllerB, controllerA.targetController)
|
||||
}
|
||||
}
|
||||
+5
-4
@@ -1,4 +1,4 @@
|
||||
package com.bluelinelabs.conductor.util;
|
||||
package com.bluelinelabs.conductor;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
@@ -14,9 +14,10 @@ import androidx.annotation.IdRes;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.bluelinelabs.conductor.Controller;
|
||||
import com.bluelinelabs.conductor.ControllerChangeHandler;
|
||||
import com.bluelinelabs.conductor.ControllerChangeType;
|
||||
import com.bluelinelabs.conductor.util.AttachFakingFrameLayout;
|
||||
import com.bluelinelabs.conductor.util.CallState;
|
||||
import com.bluelinelabs.conductor.util.ChangeHandlerHistory;
|
||||
import com.bluelinelabs.conductor.util.MockChangeHandler;
|
||||
|
||||
public class TestController extends Controller {
|
||||
|
||||
@@ -1,132 +0,0 @@
|
||||
package com.bluelinelabs.conductor;
|
||||
|
||||
import android.os.Bundle;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.bluelinelabs.conductor.util.ActivityProxy;
|
||||
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.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@Config(manifest = Config.NONE)
|
||||
public class ViewLeakTests {
|
||||
|
||||
private ActivityProxy activityProxy;
|
||||
private Router router;
|
||||
|
||||
public void createActivityController(Bundle savedInstanceState) {
|
||||
activityProxy = new ActivityProxy().create(savedInstanceState).start().resume();
|
||||
router = Conductor.attachRouter(activityProxy.getActivity(), activityProxy.getView(), savedInstanceState);
|
||||
if (!router.hasRootController()) {
|
||||
router.setRoot(RouterTransaction.with(new TestController()));
|
||||
}
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
createActivityController(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPop() {
|
||||
Controller controller = new TestController();
|
||||
router.pushController(RouterTransaction.with(controller));
|
||||
|
||||
assertNotNull(controller.getView());
|
||||
|
||||
router.popCurrentController();
|
||||
|
||||
assertNull(controller.getView());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPopWhenPushNeverAdded() {
|
||||
Controller controller = new TestController();
|
||||
router.pushController(RouterTransaction.with(controller).pushChangeHandler(new NeverAddChangeHandler()));
|
||||
|
||||
assertNotNull(controller.getView());
|
||||
|
||||
router.popCurrentController();
|
||||
|
||||
assertNull(controller.getView());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPopWhenPushNeverCompleted() {
|
||||
Controller controller = new TestController();
|
||||
router.pushController(RouterTransaction.with(controller).pushChangeHandler(new NeverCompleteChangeHandler()));
|
||||
|
||||
assertNotNull(controller.getView());
|
||||
|
||||
router.popCurrentController();
|
||||
|
||||
assertNull(controller.getView());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testActivityStop() {
|
||||
Controller controller = new TestController();
|
||||
router.pushController(RouterTransaction.with(controller));
|
||||
|
||||
assertNotNull(controller.getView());
|
||||
|
||||
activityProxy.stop(true);
|
||||
|
||||
assertNull(controller.getView());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testActivityStopWhenPushNeverCompleted() {
|
||||
Controller controller = new TestController();
|
||||
router.pushController(RouterTransaction.with(controller).pushChangeHandler(new NeverCompleteChangeHandler()));
|
||||
|
||||
assertNotNull(controller.getView());
|
||||
|
||||
activityProxy.stop(true);
|
||||
|
||||
assertNull(controller.getView());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testActivityDestroyWhenPushNeverAdded() {
|
||||
Controller controller = new TestController();
|
||||
router.pushController(RouterTransaction.with(controller).pushChangeHandler(new NeverAddChangeHandler()));
|
||||
|
||||
assertNotNull(controller.getView());
|
||||
|
||||
activityProxy.stop(true).destroy();
|
||||
|
||||
assertNull(controller.getView());
|
||||
}
|
||||
|
||||
public static class NeverAddChangeHandler extends ControllerChangeHandler {
|
||||
@Override
|
||||
public void performChange(@NonNull final ViewGroup container, @Nullable View from, @Nullable final View to, boolean isPush, @NonNull ControllerChangeCompletedListener changeListener) {
|
||||
if (from != null) {
|
||||
container.removeView(from);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class NeverCompleteChangeHandler extends ControllerChangeHandler {
|
||||
@Override
|
||||
public void performChange(@NonNull ViewGroup container, @Nullable View from, @Nullable View to, boolean isPush, @NonNull ControllerChangeCompletedListener changeListener) {
|
||||
if (from != null) {
|
||||
container.removeView(from);
|
||||
}
|
||||
container.addView(to);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
package com.bluelinelabs.conductor
|
||||
|
||||
import android.os.Looper
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import com.bluelinelabs.conductor.changehandler.SimpleSwapChangeHandler
|
||||
import com.bluelinelabs.conductor.util.TestActivity
|
||||
import org.junit.Assert
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.Robolectric
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import org.robolectric.Shadows.shadowOf
|
||||
import org.robolectric.annotation.Config
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
@Config(manifest = Config.NONE)
|
||||
class ViewLeakTests {
|
||||
|
||||
private val activityController = Robolectric.buildActivity(TestActivity::class.java).setup()
|
||||
private val router = activityController.get().router
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
if (!router.hasRootController()) {
|
||||
router.setRoot(TestController().asTransaction())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testPop() {
|
||||
val controller = TestController()
|
||||
router.pushController(controller.asTransaction())
|
||||
Assert.assertNotNull(controller.getView())
|
||||
|
||||
router.popCurrentController()
|
||||
shadowOf(Looper.getMainLooper()).idle()
|
||||
Assert.assertNull(controller.getView())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testPopWhenPushNeverAdded() {
|
||||
val controller = TestController()
|
||||
router.pushController(controller.asTransaction(pushChangeHandler = NeverAddChangeHandler()))
|
||||
Assert.assertNotNull(controller.getView())
|
||||
|
||||
router.popCurrentController()
|
||||
shadowOf(Looper.getMainLooper()).idle()
|
||||
Assert.assertNull(controller.getView())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testPopWhenPushNeverCompleted() {
|
||||
val controller = TestController()
|
||||
router.pushController(controller.asTransaction(pushChangeHandler = NeverCompleteChangeHandler()))
|
||||
Assert.assertNotNull(controller.getView())
|
||||
|
||||
router.popCurrentController()
|
||||
shadowOf(Looper.getMainLooper()).idle()
|
||||
Assert.assertNull(controller.getView())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testActivityDestroy() {
|
||||
val controller = TestController()
|
||||
router.pushController(controller.asTransaction())
|
||||
Assert.assertNotNull(controller.getView())
|
||||
|
||||
activityController.stop().destroy()
|
||||
shadowOf(Looper.getMainLooper()).idle()
|
||||
Assert.assertNull(controller.getView())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testActivityDestroyWhenPushNeverCompleted() {
|
||||
val controller = TestController()
|
||||
router.pushController(controller.asTransaction(pushChangeHandler = NeverCompleteChangeHandler()))
|
||||
Assert.assertNotNull(controller.getView())
|
||||
|
||||
activityController.stop().destroy()
|
||||
shadowOf(Looper.getMainLooper()).idle()
|
||||
Assert.assertNull(controller.getView())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testActivityDestroyWhenPushNeverAdded() {
|
||||
val controller = TestController()
|
||||
router.pushController(controller.asTransaction(pushChangeHandler = NeverAddChangeHandler()))
|
||||
Assert.assertNotNull(controller.getView())
|
||||
|
||||
activityController.stop().destroy()
|
||||
shadowOf(Looper.getMainLooper()).idle()
|
||||
Assert.assertNull(controller.getView())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testViewRemovedIfLayeredNotRemovesFromViewOnPush() {
|
||||
val controller = TestController()
|
||||
router.pushController(controller.asTransaction())
|
||||
router.pushController(TestController().asTransaction(pushChangeHandler = SimpleSwapChangeHandler(false)))
|
||||
val view = controller.view
|
||||
shadowOf(Looper.getMainLooper()).idle()
|
||||
Assert.assertNotNull(view.parent)
|
||||
|
||||
router.pushController(TestController().asTransaction())
|
||||
shadowOf(Looper.getMainLooper()).idle()
|
||||
Assert.assertNotNull(view.parent)
|
||||
|
||||
router.popToRoot()
|
||||
shadowOf(Looper.getMainLooper()).idle()
|
||||
Assert.assertNull(view.parent)
|
||||
}
|
||||
|
||||
class NeverAddChangeHandler : ControllerChangeHandler() {
|
||||
override fun performChange(
|
||||
container: ViewGroup,
|
||||
from: View?,
|
||||
to: View?,
|
||||
isPush: Boolean,
|
||||
changeListener: ControllerChangeCompletedListener
|
||||
) {
|
||||
if (from != null) {
|
||||
container.removeView(from)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class NeverCompleteChangeHandler : ControllerChangeHandler() {
|
||||
override fun performChange(
|
||||
container: ViewGroup,
|
||||
from: View?,
|
||||
to: View?,
|
||||
isPush: Boolean,
|
||||
changeListener: ControllerChangeCompletedListener
|
||||
) {
|
||||
if (from != null) {
|
||||
container.removeView(from)
|
||||
}
|
||||
container.addView(to)
|
||||
}
|
||||
}
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
package com.bluelinelabs.conductor.internal
|
||||
|
||||
import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
import android.util.SparseArray
|
||||
import io.kotest.matchers.ints.shouldBeExactly
|
||||
import io.kotest.matchers.shouldBe
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import org.robolectric.annotation.Config
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
@Config(manifest = Config.NONE)
|
||||
class StringSparseArrayParcelerTest {
|
||||
|
||||
@Test
|
||||
fun emptyArray() {
|
||||
SparseArray<String>().parcelAndUnParcel().size() shouldBeExactly 0
|
||||
}
|
||||
|
||||
@Test
|
||||
fun arrayWithContents() {
|
||||
val array = SparseArray<String>()
|
||||
array.put(1, "one")
|
||||
array.put(7, "seven")
|
||||
|
||||
val unParceled = array.parcelAndUnParcel()
|
||||
|
||||
unParceled.size() shouldBeExactly 2
|
||||
unParceled[1] shouldBe "one"
|
||||
unParceled[7] shouldBe "seven"
|
||||
}
|
||||
|
||||
private fun SparseArray<String>.parcelAndUnParcel(): SparseArray<String> {
|
||||
val parceler = StringSparseArrayParceler(this)
|
||||
|
||||
val parcel = Parcel.obtain()
|
||||
parceler.writeToParcel(parcel, 0)
|
||||
|
||||
parcel.setDataPosition(0)
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val creator = StringSparseArrayParceler::class.java.getField("CREATOR").get(null)
|
||||
as Parcelable.Creator<StringSparseArrayParceler>
|
||||
val unParceled = creator.createFromParcel(parcel)
|
||||
return unParceled.stringSparseArray.also {
|
||||
check(it !== this)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
package com.bluelinelabs.conductor.util;
|
||||
|
||||
import android.app.Activity;
|
||||
|
||||
public class TestActivity extends Activity {
|
||||
|
||||
public boolean isChangingConfigurations = false;
|
||||
public boolean isDestroying = false;
|
||||
|
||||
@Override
|
||||
public boolean isChangingConfigurations() {
|
||||
return isChangingConfigurations;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDestroyed() {
|
||||
return isDestroying || super.isDestroyed();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.bluelinelabs.conductor.util
|
||||
|
||||
import android.app.Activity
|
||||
import android.os.Bundle
|
||||
import com.bluelinelabs.conductor.Conductor
|
||||
import com.bluelinelabs.conductor.Router
|
||||
|
||||
class TestActivity : Activity() {
|
||||
|
||||
lateinit var router: Router
|
||||
|
||||
var changingConfigurations = false
|
||||
var destroying = false
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
router = Conductor.attachRouter(
|
||||
this,
|
||||
findViewById(android.R.id.content),
|
||||
savedInstanceState
|
||||
)
|
||||
}
|
||||
|
||||
override fun isChangingConfigurations(): Boolean {
|
||||
return changingConfigurations
|
||||
}
|
||||
|
||||
override fun isDestroyed(): Boolean {
|
||||
return destroying || super.isDestroyed()
|
||||
}
|
||||
}
|
||||
+37
-14
@@ -1,13 +1,7 @@
|
||||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'kotlin-android'
|
||||
|
||||
android {
|
||||
compileSdkVersion rootProject.ext.compileSdkVersion
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility 1.8
|
||||
targetCompatibility 1.8
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
applicationId "com.bluelinelabs.conductor.demo"
|
||||
minSdkVersion 21
|
||||
@@ -27,26 +21,55 @@ android {
|
||||
packagingOptions {
|
||||
exclude 'META-INF/rxjava.properties'
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
viewBinding = true
|
||||
compose = true
|
||||
}
|
||||
|
||||
compileSdkVersion rootProject.ext.compileSdkVersion
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = "1.8"
|
||||
}
|
||||
|
||||
composeOptions {
|
||||
kotlinCompilerExtensionVersion composeVersion
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation rootProject.ext.androidxAppCompat
|
||||
implementation rootProject.ext.androidxViewPager2
|
||||
implementation rootProject.ext.material
|
||||
implementation rootProject.ext.androidxCoreKtx
|
||||
|
||||
implementation rootProject.ext.archComponentsLiveDataCore // Fix duplicate classes
|
||||
|
||||
annotationProcessor rootProject.ext.butterknifeCompiler
|
||||
implementation rootProject.ext.butterknife
|
||||
implementation rootProject.ext.picasso
|
||||
|
||||
implementation rootProject.ext.autodisposeKtx
|
||||
|
||||
implementation project(':conductor')
|
||||
implementation project(':conductor-modules:viewpager')
|
||||
implementation project(':conductor-modules:rxlifecycle2')
|
||||
implementation project(':conductor-modules:viewpager2')
|
||||
implementation project(':conductor-modules:autodispose')
|
||||
implementation project(':conductor-modules:arch-components-lifecycle')
|
||||
implementation project(':conductor-modules:transition-androidx')
|
||||
implementation project(':conductor-modules:androidx-transition')
|
||||
|
||||
debugImplementation rootProject.ext.leakCanary
|
||||
releaseImplementation rootProject.ext.leakCanaryNoOp
|
||||
testImplementation rootProject.ext.leakCanaryNoOp
|
||||
implementation "androidx.compose.ui:ui:$composeVersion"
|
||||
implementation "androidx.compose.ui:ui-tooling:$composeVersion"
|
||||
implementation "androidx.compose.foundation:foundation:$composeVersion"
|
||||
implementation "androidx.compose.material:material:$composeVersion"
|
||||
implementation "androidx.compose.material:material-icons-core:$composeVersion"
|
||||
implementation "androidx.compose.material:material-icons-extended:$composeVersion"
|
||||
implementation "androidx.activity:activity-compose:1.3.0-beta02"
|
||||
implementation "androidx.lifecycle:lifecycle-viewmodel-compose:1.0.0-alpha07"
|
||||
|
||||
implementation rootProject.ext.leakCanary
|
||||
}
|
||||
|
||||
@@ -1,29 +1,28 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="com.bluelinelabs.conductor.demo"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
package="com.bluelinelabs.conductor.demo">
|
||||
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
|
||||
<application
|
||||
android:name="com.bluelinelabs.conductor.demo.DemoApplication"
|
||||
android:allowBackup="true"
|
||||
android:fullBackupContent="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/AppTheme"
|
||||
android:fullBackupContent="true"
|
||||
tools:ignore="GoogleAppIndexingWarning">
|
||||
|
||||
<activity
|
||||
android:name="com.bluelinelabs.conductor.demo.MainActivity"
|
||||
android:exported="true"
|
||||
android:label="@string/app_name"
|
||||
android:launchMode="singleTask"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
package com.bluelinelabs.conductor.demo;
|
||||
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
|
||||
public interface ActionBarProvider {
|
||||
ActionBar getSupportActionBar();
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
package com.bluelinelabs.conductor.demo;
|
||||
|
||||
import android.app.Application;
|
||||
|
||||
import com.squareup.leakcanary.LeakCanary;
|
||||
import com.squareup.leakcanary.RefWatcher;
|
||||
|
||||
public class DemoApplication extends Application {
|
||||
|
||||
public static RefWatcher refWatcher;
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
refWatcher = LeakCanary.install(this);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
package com.bluelinelabs.conductor.demo;
|
||||
|
||||
import android.os.Bundle;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.bluelinelabs.conductor.Conductor;
|
||||
import com.bluelinelabs.conductor.Router;
|
||||
import com.bluelinelabs.conductor.RouterTransaction;
|
||||
import com.bluelinelabs.conductor.demo.controllers.HomeController;
|
||||
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
|
||||
public final class MainActivity extends AppCompatActivity implements ActionBarProvider {
|
||||
|
||||
@BindView(R.id.toolbar) Toolbar toolbar;
|
||||
@BindView(R.id.controller_container) ViewGroup container;
|
||||
|
||||
private Router router;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.activity_main);
|
||||
ButterKnife.bind(this);
|
||||
|
||||
setSupportActionBar(toolbar);
|
||||
|
||||
router = Conductor.attachRouter(this, container, savedInstanceState);
|
||||
if (!router.hasRootController()) {
|
||||
router.setRoot(RouterTransaction.with(new HomeController()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
if (!router.handleBack()) {
|
||||
super.onBackPressed();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.bluelinelabs.conductor.demo
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import com.bluelinelabs.conductor.Conductor.attachRouter
|
||||
import com.bluelinelabs.conductor.Router
|
||||
import com.bluelinelabs.conductor.Router.PopRootControllerMode
|
||||
import com.bluelinelabs.conductor.RouterTransaction.Companion.with
|
||||
import com.bluelinelabs.conductor.demo.controllers.HomeController
|
||||
import com.bluelinelabs.conductor.demo.databinding.ActivityMainBinding
|
||||
|
||||
class MainActivity : AppCompatActivity(), ToolbarProvider {
|
||||
private lateinit var binding: ActivityMainBinding
|
||||
private lateinit var router: Router
|
||||
|
||||
override val toolbar: Toolbar
|
||||
get() = binding.toolbar
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
binding = ActivityMainBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
|
||||
router = attachRouter(this, binding.controllerContainer, savedInstanceState)
|
||||
.setPopRootControllerMode(PopRootControllerMode.NEVER)
|
||||
|
||||
if (!router.hasRootController()) {
|
||||
router.setRoot(with(HomeController()))
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBackPressed() {
|
||||
if (!router.handleBack()) {
|
||||
super.onBackPressed()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.bluelinelabs.conductor.demo
|
||||
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
|
||||
interface ToolbarProvider {
|
||||
val toolbar: Toolbar
|
||||
}
|
||||
+1
-1
@@ -51,7 +51,7 @@ public class CircularRevealChangeHandler extends AnimatorChangeHandler {
|
||||
* @param removesFromViewOnPush If true, the view being replaced will be removed from the view hierarchy on pushes
|
||||
*/
|
||||
public CircularRevealChangeHandler(@NonNull View fromView, @NonNull View containerView, boolean removesFromViewOnPush) {
|
||||
this(fromView, containerView, DEFAULT_ANIMATION_DURATION, true);
|
||||
this(fromView, containerView, DEFAULT_ANIMATION_DURATION, removesFromViewOnPush);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
-140
@@ -1,140 +0,0 @@
|
||||
package com.bluelinelabs.conductor.demo.controllers;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.lifecycle.Lifecycle.Event;
|
||||
import androidx.lifecycle.LifecycleObserver;
|
||||
import androidx.lifecycle.LifecycleOwner;
|
||||
import androidx.lifecycle.OnLifecycleEvent;
|
||||
|
||||
import com.bluelinelabs.conductor.ControllerChangeHandler;
|
||||
import com.bluelinelabs.conductor.ControllerChangeType;
|
||||
import com.bluelinelabs.conductor.RouterTransaction;
|
||||
import com.bluelinelabs.conductor.archlifecycle.LifecycleController;
|
||||
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 butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
import butterknife.OnClick;
|
||||
import butterknife.Unbinder;
|
||||
|
||||
public class ArchLifecycleController extends LifecycleController {
|
||||
|
||||
private static final String TAG = "ArchLifecycleController";
|
||||
|
||||
@BindView(R.id.tv_title) TextView tvTitle;
|
||||
|
||||
private Unbinder unbinder;
|
||||
private boolean hasExited;
|
||||
|
||||
public ArchLifecycleController() {
|
||||
Log.i(TAG, "Conductor: Constructor called");
|
||||
|
||||
getLifecycle().addObserver(new LifecycleObserver() {
|
||||
@OnLifecycleEvent(Event.ON_ANY)
|
||||
void onLifecycleEvent(@NonNull LifecycleOwner source, @NonNull Event event) {
|
||||
Log.d(TAG, "Lifecycle: " + source.getClass().getSimpleName() + " emitted event " + event + " and is now in state " + source.getLifecycle().getCurrentState());
|
||||
}
|
||||
});
|
||||
|
||||
Log.d(TAG, "Lifecycle: " + getClass().getSimpleName() + " is now in state " + getLifecycle().getCurrentState());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onContextAvailable(@NonNull Context context) {
|
||||
Log.i(TAG, "Conductor: onContextAvailable() called");
|
||||
super.onContextAvailable(context);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected View onCreateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container, @Nullable Bundle savedViewState) {
|
||||
Log.i(TAG, "Conductor: onCreateView() called");
|
||||
|
||||
View view = inflater.inflate(R.layout.controller_lifecycle, container, false);
|
||||
view.setBackgroundColor(ContextCompat.getColor(container.getContext(), R.color.orange_300));
|
||||
unbinder = ButterKnife.bind(this, view);
|
||||
|
||||
tvTitle.setText(getResources().getString(R.string.rxlifecycle_title, TAG));
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAttach(@NonNull View view) {
|
||||
Log.i(TAG, "Conductor: onAttach() called");
|
||||
super.onAttach(view);
|
||||
|
||||
(((ActionBarProvider) getActivity()).getSupportActionBar()).setTitle("Arch Components Lifecycle Demo");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDetach(@NonNull View view) {
|
||||
Log.i(TAG, "Conductor: onDetach() called");
|
||||
super.onDetach(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroyView(@NonNull View view) {
|
||||
Log.i(TAG, "Conductor: onDestroyView() called");
|
||||
super.onDestroyView(view);
|
||||
|
||||
unbinder.unbind();
|
||||
unbinder = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onContextUnavailable() {
|
||||
Log.i(TAG, "Conductor: onContextUnavailable() called");
|
||||
super.onContextUnavailable();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
Log.i(TAG, "Conductor: onDestroy() called");
|
||||
super.onDestroy();
|
||||
|
||||
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 Controller's onDetach() and LifecycleObserver's onPause() methods were called, followed by the Controller's onDestroyView() and LifecycleObserver's onStop()."))
|
||||
.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 Controller's onDetach() and LifecycleObserver's onPause() methods were called."))
|
||||
.pushChangeHandler(new HorizontalChangeHandler())
|
||||
.popChangeHandler(new HorizontalChangeHandler()));
|
||||
}
|
||||
|
||||
}
|
||||
+110
@@ -0,0 +1,110 @@
|
||||
package com.bluelinelabs.conductor.demo.controllers
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleObserver
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.OnLifecycleEvent
|
||||
import com.bluelinelabs.conductor.RouterTransaction
|
||||
import com.bluelinelabs.conductor.archlifecycle.LifecycleController
|
||||
import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler
|
||||
import com.bluelinelabs.conductor.demo.R
|
||||
import com.bluelinelabs.conductor.demo.ToolbarProvider
|
||||
import com.bluelinelabs.conductor.demo.controllers.base.watchForLeaks
|
||||
import com.bluelinelabs.conductor.demo.databinding.ControllerLifecycleBinding
|
||||
|
||||
class ArchLifecycleController : LifecycleController() {
|
||||
|
||||
|
||||
init {
|
||||
Log.i(TAG, "Conductor: Constructor called")
|
||||
watchForLeaks()
|
||||
|
||||
lifecycle.addObserver(object : LifecycleObserver {
|
||||
@OnLifecycleEvent(Lifecycle.Event.ON_ANY)
|
||||
fun onLifecycleEvent(source: LifecycleOwner, event: Lifecycle.Event) {
|
||||
Log.d(
|
||||
TAG,
|
||||
"Lifecycle: " + source.javaClass.simpleName + " emitted event " + event + " and is now in state " + source.lifecycle.currentState
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
Log.d(TAG, "Lifecycle: " + javaClass.simpleName + " is now in state " + lifecycle.currentState)
|
||||
}
|
||||
|
||||
override fun onContextAvailable(context: Context) {
|
||||
Log.i(TAG, "Conductor: onContextAvailable() called")
|
||||
super.onContextAvailable(context)
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup,
|
||||
savedViewState: Bundle?
|
||||
): View {
|
||||
Log.i(TAG, "Conductor: onCreateView() called")
|
||||
|
||||
val binding = ControllerLifecycleBinding.inflate(inflater, container, false)
|
||||
|
||||
binding.root.setBackgroundColor(ContextCompat.getColor(container.context, R.color.orange_300))
|
||||
binding.title.text = binding.root.resources.getString(R.string.rxlifecycle_title, TAG)
|
||||
|
||||
binding.nextReleaseView.setOnClickListener {
|
||||
retainViewMode = RetainViewMode.RELEASE_DETACH
|
||||
|
||||
router.pushController(
|
||||
RouterTransaction.with(TextController("Logcat should now report that the Controller's onDetach() and LifecycleObserver's onPause() methods were called, followed by the Controller's onDestroyView() and LifecycleObserver's onStop()."))
|
||||
.pushChangeHandler(HorizontalChangeHandler())
|
||||
.popChangeHandler(HorizontalChangeHandler())
|
||||
)
|
||||
}
|
||||
|
||||
binding.nextRetainView.setOnClickListener {
|
||||
retainViewMode = RetainViewMode.RETAIN_DETACH
|
||||
|
||||
router.pushController(
|
||||
RouterTransaction.with(TextController("Logcat should now report that the Controller's onDetach() and LifecycleObserver's onPause() methods were called."))
|
||||
.pushChangeHandler(HorizontalChangeHandler())
|
||||
.popChangeHandler(HorizontalChangeHandler())
|
||||
)
|
||||
}
|
||||
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onAttach(view: View) {
|
||||
Log.i(TAG, "Conductor: onAttach() called")
|
||||
super.onAttach(view)
|
||||
(activity as ToolbarProvider).toolbar.title = "Arch Components Lifecycle Demo"
|
||||
}
|
||||
|
||||
override fun onDetach(view: View) {
|
||||
Log.i(TAG, "Conductor: onDetach() called")
|
||||
super.onDetach(view)
|
||||
}
|
||||
|
||||
override fun onDestroyView(view: View) {
|
||||
Log.i(TAG, "Conductor: onDestroyView() called")
|
||||
super.onDestroyView(view)
|
||||
}
|
||||
|
||||
override fun onContextUnavailable() {
|
||||
Log.i(TAG, "Conductor: onContextUnavailable() called")
|
||||
super.onContextUnavailable()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
Log.i(TAG, "Conductor: onDestroy() called")
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "ArchLifecycleController"
|
||||
}
|
||||
}
|
||||
-143
@@ -1,143 +0,0 @@
|
||||
package com.bluelinelabs.conductor.demo.controllers;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import com.bluelinelabs.conductor.Controller;
|
||||
import com.bluelinelabs.conductor.ControllerChangeHandler;
|
||||
import com.bluelinelabs.conductor.ControllerChangeType;
|
||||
import com.bluelinelabs.conductor.RouterTransaction;
|
||||
import com.bluelinelabs.conductor.autodispose.ControllerScopeProvider;
|
||||
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.uber.autodispose.AutoDispose;
|
||||
import com.uber.autodispose.lifecycle.LifecycleScopeProvider;
|
||||
|
||||
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 AutodisposeController extends Controller {
|
||||
|
||||
private static final String TAG = "AutodisposeController";
|
||||
|
||||
@BindView(R.id.tv_title) TextView tvTitle;
|
||||
|
||||
private Unbinder unbinder;
|
||||
private boolean hasExited;
|
||||
private final LifecycleScopeProvider scopeProvider = ControllerScopeProvider.from(this);
|
||||
|
||||
public AutodisposeController() {
|
||||
Observable.interval(1, TimeUnit.SECONDS)
|
||||
.doOnDispose(() -> Log.i(TAG, "Disposing from constructor"))
|
||||
.as(AutoDispose.<Long>autoDisposable((scopeProvider)))
|
||||
.subscribe(num -> Log.i(TAG, "Started in constructor, running until onDestroy(): " + num));
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected View onCreateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container, @Nullable Bundle savedViewState) {
|
||||
Log.i(TAG, "onCreateView() called");
|
||||
|
||||
View view = inflater.inflate(R.layout.controller_lifecycle, container, false);
|
||||
view.setBackgroundColor(ContextCompat.getColor(container.getContext(), R.color.purple_300));
|
||||
unbinder = ButterKnife.bind(this, view);
|
||||
|
||||
tvTitle.setText(getResources().getString(R.string.rxlifecycle_title, TAG));
|
||||
|
||||
Observable.interval(1, TimeUnit.SECONDS)
|
||||
.doOnDispose(() -> Log.i(TAG, "Disposing from onCreateView()"))
|
||||
.as(AutoDispose.<Long>autoDisposable((scopeProvider)))
|
||||
.subscribe(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("Autodispose Demo");
|
||||
|
||||
Observable.interval(1, TimeUnit.SECONDS)
|
||||
.doOnDispose(() -> Log.i(TAG, "Disposing from onAttach()"))
|
||||
.as(AutoDispose.<Long>autoDisposable((scopeProvider)))
|
||||
.subscribe(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()));
|
||||
}
|
||||
}
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
package com.bluelinelabs.conductor.demo.controllers
|
||||
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import com.bluelinelabs.conductor.Controller
|
||||
import com.bluelinelabs.conductor.RouterTransaction.Companion.with
|
||||
import com.bluelinelabs.conductor.autodispose.ControllerScopeProvider
|
||||
import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler
|
||||
import com.bluelinelabs.conductor.demo.R
|
||||
import com.bluelinelabs.conductor.demo.ToolbarProvider
|
||||
import com.bluelinelabs.conductor.demo.controllers.base.watchForLeaks
|
||||
import com.bluelinelabs.conductor.demo.databinding.ControllerLifecycleBinding
|
||||
import com.uber.autodispose.autoDisposable
|
||||
import io.reactivex.Observable
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
// Shamelessly borrowed from the official RxLifecycle demo by Trello and adapted for Conductor Controllers
|
||||
// instead of Activities or Fragments.
|
||||
class AutodisposeController : Controller() {
|
||||
|
||||
private val scopeProvider = ControllerScopeProvider.from(this)
|
||||
|
||||
init {
|
||||
watchForLeaks()
|
||||
Observable.interval(1, TimeUnit.SECONDS)
|
||||
.doOnDispose { Log.i(TAG, "Disposing from constructor") }
|
||||
.autoDisposable(scopeProvider)
|
||||
.subscribe { num: Long ->
|
||||
Log.i(TAG, "Started in constructor, running until onDestroy(): $num")
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup,
|
||||
savedViewState: Bundle?
|
||||
): View {
|
||||
Log.i(TAG, "onCreateView() called")
|
||||
val binding = ControllerLifecycleBinding.inflate(inflater, container, false)
|
||||
binding.title.text = binding.root.resources.getString(R.string.rxlifecycle_title, TAG)
|
||||
|
||||
binding.nextReleaseView.setOnClickListener {
|
||||
retainViewMode = RetainViewMode.RELEASE_DETACH
|
||||
router.pushController(
|
||||
with(TextController("Logcat should now report that the observables from onAttach() and onViewBound() have been disposed of, while the constructor observable is still running."))
|
||||
.pushChangeHandler(HorizontalChangeHandler())
|
||||
.popChangeHandler(HorizontalChangeHandler())
|
||||
)
|
||||
}
|
||||
|
||||
binding.nextRetainView.setOnClickListener {
|
||||
retainViewMode = RetainViewMode.RETAIN_DETACH
|
||||
router.pushController(
|
||||
with(TextController("Logcat should now report that the observables from onAttach() has been disposed of, while the constructor and onViewBound() observables are still running."))
|
||||
.pushChangeHandler(HorizontalChangeHandler())
|
||||
.popChangeHandler(HorizontalChangeHandler())
|
||||
)
|
||||
}
|
||||
|
||||
Observable.interval(1, TimeUnit.SECONDS)
|
||||
.doOnDispose { Log.i(TAG, "Disposing from onCreateView()") }
|
||||
.autoDisposable(scopeProvider)
|
||||
.subscribe { num: Long ->
|
||||
Log.i(TAG, "Started in onCreateView(), running until onDestroyView(): $num")
|
||||
}
|
||||
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onAttach(view: View) {
|
||||
super.onAttach(view)
|
||||
Log.i(TAG, "onAttach() called")
|
||||
|
||||
(activity as ToolbarProvider).toolbar.title = "Autodispose Demo"
|
||||
Observable.interval(1, TimeUnit.SECONDS)
|
||||
.doOnDispose { Log.i(TAG, "Disposing from onAttach()") }
|
||||
.autoDisposable(scopeProvider)
|
||||
.subscribe { num: Long ->
|
||||
Log.i(TAG, "Started in onAttach(), running until onDetach(): $num")
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroyView(view: View) {
|
||||
super.onDestroyView(view)
|
||||
Log.i(TAG, "onDestroyView() called")
|
||||
}
|
||||
|
||||
override fun onDetach(view: View) {
|
||||
super.onDetach(view)
|
||||
Log.i(TAG, "onDetach() called")
|
||||
}
|
||||
|
||||
public override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
Log.i(TAG, "onDestroy() called")
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "AutodisposeController"
|
||||
}
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
package com.bluelinelabs.conductor.demo.controllers;
|
||||
|
||||
import android.os.Bundle;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.bluelinelabs.conductor.demo.R;
|
||||
import com.bluelinelabs.conductor.demo.controllers.base.BaseController;
|
||||
import com.bluelinelabs.conductor.demo.util.BundleBuilder;
|
||||
|
||||
import butterknife.BindView;
|
||||
|
||||
public class ChildController extends BaseController {
|
||||
|
||||
private static final String KEY_TITLE = "ChildController.title";
|
||||
private static final String KEY_BG_COLOR = "ChildController.bgColor";
|
||||
private static final String KEY_COLOR_IS_RES = "ChildController.colorIsResId";
|
||||
|
||||
@BindView(R.id.tv_title) TextView tvTitle;
|
||||
|
||||
public ChildController(String title, int backgroundColor, boolean colorIsResId) {
|
||||
this(new BundleBuilder(new Bundle())
|
||||
.putString(KEY_TITLE, title)
|
||||
.putInt(KEY_BG_COLOR, backgroundColor)
|
||||
.putBoolean(KEY_COLOR_IS_RES, colorIsResId)
|
||||
.build());
|
||||
}
|
||||
|
||||
public ChildController(Bundle args) {
|
||||
super(args);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected View inflateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) {
|
||||
return inflater.inflate(R.layout.controller_child, container, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onViewBound(@NonNull View view) {
|
||||
super.onViewBound(view);
|
||||
|
||||
tvTitle.setText(getArgs().getString(KEY_TITLE));
|
||||
|
||||
int bgColor = getArgs().getInt(KEY_BG_COLOR);
|
||||
if (getArgs().getBoolean(KEY_COLOR_IS_RES)) {
|
||||
bgColor = ContextCompat.getColor(getActivity(), bgColor);
|
||||
}
|
||||
view.setBackgroundColor(bgColor);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.bluelinelabs.conductor.demo.controllers
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.os.bundleOf
|
||||
import com.bluelinelabs.conductor.demo.R
|
||||
import com.bluelinelabs.conductor.demo.controllers.base.BaseController
|
||||
import com.bluelinelabs.conductor.demo.databinding.ControllerChildBinding
|
||||
import com.bluelinelabs.conductor.demo.util.viewBinding
|
||||
|
||||
class ChildController(args: Bundle) : BaseController(R.layout.controller_child, args) {
|
||||
private val binding: ControllerChildBinding by viewBinding(ControllerChildBinding::bind)
|
||||
|
||||
constructor(title: String, backgroundColor: Int, colorIsResId: Boolean) : this(
|
||||
bundleOf(
|
||||
KEY_TITLE to title,
|
||||
KEY_BG_COLOR to backgroundColor,
|
||||
KEY_COLOR_IS_RES to colorIsResId
|
||||
)
|
||||
)
|
||||
|
||||
override fun onViewCreated(view: View) {
|
||||
binding.title.text = args.getString(KEY_TITLE)
|
||||
|
||||
val bgColor = args.getInt(KEY_BG_COLOR)
|
||||
if (args.getBoolean(KEY_COLOR_IS_RES)) {
|
||||
view.setBackgroundColor(ContextCompat.getColor(view.context, bgColor))
|
||||
} else {
|
||||
view.setBackgroundColor(bgColor)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val KEY_TITLE = "ChildController.title"
|
||||
private const val KEY_BG_COLOR = "ChildController.bgColor"
|
||||
private const val KEY_COLOR_IS_RES = "ChildController.colorIsResId"
|
||||
}
|
||||
}
|
||||
-167
@@ -1,167 +0,0 @@
|
||||
package com.bluelinelabs.conductor.demo.controllers;
|
||||
|
||||
import android.os.Bundle;
|
||||
import androidx.annotation.DrawableRes;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.view.ViewCompat;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.bluelinelabs.conductor.demo.R;
|
||||
import com.bluelinelabs.conductor.demo.controllers.base.BaseController;
|
||||
import com.bluelinelabs.conductor.demo.util.BundleBuilder;
|
||||
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
|
||||
public class CityDetailController extends BaseController {
|
||||
|
||||
private static final String KEY_TITLE = "CityDetailController.title";
|
||||
private static final String KEY_IMAGE = "CityDetailController.image";
|
||||
|
||||
private static final String[] LIST_ROWS = new String[] {
|
||||
"• This is a city.",
|
||||
"• There's some cool stuff about it.",
|
||||
"• But really this is just a demo, not a city guide app.",
|
||||
"• This demo is meant to show some nice transitions.",
|
||||
"• You should have seen some sweet shared element transitions using the ImageView and the TextView in the \"header\" above.",
|
||||
"• This transition utilized some callbacks to ensure all the necessary rows in the RecyclerView were laid about before the transition occurred.",
|
||||
"• Just adding some more lines so it scrolls now...\n\n\n\n\n\n\nThe end."
|
||||
};
|
||||
|
||||
@BindView(R.id.recycler_view) RecyclerView recyclerView;
|
||||
|
||||
@DrawableRes private int imageDrawableRes;
|
||||
private String title;
|
||||
|
||||
public CityDetailController(@DrawableRes int imageDrawableRes, String title) {
|
||||
this(new BundleBuilder(new Bundle())
|
||||
.putInt(KEY_IMAGE, imageDrawableRes)
|
||||
.putString(KEY_TITLE, title)
|
||||
.build());
|
||||
}
|
||||
|
||||
public CityDetailController(Bundle args) {
|
||||
super(args);
|
||||
imageDrawableRes = getArgs().getInt(KEY_IMAGE);
|
||||
title = getArgs().getString(KEY_TITLE);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected View inflateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) {
|
||||
return inflater.inflate(R.layout.controller_city_detail, container, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onViewBound(@NonNull View view) {
|
||||
super.onViewBound(view);
|
||||
|
||||
recyclerView.setHasFixedSize(true);
|
||||
recyclerView.setLayoutManager(new LinearLayoutManager(view.getContext()));
|
||||
recyclerView.setAdapter(new CityDetailAdapter(LayoutInflater.from(view.getContext()), title, imageDrawableRes, LIST_ROWS, title));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
static class CityDetailAdapter extends RecyclerView.Adapter<CityDetailAdapter.ViewHolder> {
|
||||
|
||||
private static final int VIEW_TYPE_HEADER = 0;
|
||||
private static final int VIEW_TYPE_DETAIL = 1;
|
||||
|
||||
private final LayoutInflater inflater;
|
||||
private final String title;
|
||||
@DrawableRes private final int imageDrawableRes;
|
||||
private final String imageViewTransitionName;
|
||||
private final String textViewTransitionName;
|
||||
private final String[] details;
|
||||
|
||||
public CityDetailAdapter(LayoutInflater inflater, String title, @DrawableRes int imageDrawableRes, String[] details, String transitionNameBase) {
|
||||
this.inflater = inflater;
|
||||
this.title = title;
|
||||
this.imageDrawableRes = imageDrawableRes;
|
||||
this.details = details;
|
||||
imageViewTransitionName = inflater.getContext().getResources().getString(R.string.transition_tag_image_named, transitionNameBase);
|
||||
textViewTransitionName = inflater.getContext().getResources().getString(R.string.transition_tag_title_named, transitionNameBase);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position) {
|
||||
if (position == 0) {
|
||||
return VIEW_TYPE_HEADER;
|
||||
} else {
|
||||
return VIEW_TYPE_DETAIL;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||
if (viewType == VIEW_TYPE_HEADER) {
|
||||
return new HeaderViewHolder(inflater.inflate(R.layout.row_city_header, parent, false));
|
||||
} else {
|
||||
return new DetailViewHolder(inflater.inflate(R.layout.row_city_detail, parent, false));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(ViewHolder holder, int position) {
|
||||
if (getItemViewType(position) == VIEW_TYPE_HEADER) {
|
||||
((HeaderViewHolder)holder).bind(imageDrawableRes, title, imageViewTransitionName, textViewTransitionName);
|
||||
} else {
|
||||
((DetailViewHolder)holder).bind(details[position - 1]);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return 1 + details.length;
|
||||
}
|
||||
|
||||
static class ViewHolder extends RecyclerView.ViewHolder {
|
||||
ViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
ButterKnife.bind(this, itemView);
|
||||
}
|
||||
}
|
||||
|
||||
static class HeaderViewHolder extends ViewHolder {
|
||||
|
||||
@BindView(R.id.image_view) ImageView imageView;
|
||||
@BindView(R.id.text_view) TextView textView;
|
||||
|
||||
public HeaderViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
}
|
||||
|
||||
void bind(@DrawableRes int imageDrawableRes, String title, String imageTransitionName, String textViewTransitionName) {
|
||||
imageView.setImageResource(imageDrawableRes);
|
||||
textView.setText(title);
|
||||
|
||||
ViewCompat.setTransitionName(imageView, imageTransitionName);
|
||||
ViewCompat.setTransitionName(textView, textViewTransitionName);
|
||||
}
|
||||
}
|
||||
|
||||
static class DetailViewHolder extends ViewHolder {
|
||||
|
||||
@BindView(R.id.text_view) TextView textView;
|
||||
|
||||
public DetailViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
}
|
||||
|
||||
void bind(String detail) {
|
||||
textView.setText(detail);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
package com.bluelinelabs.conductor.demo.controllers
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.viewbinding.ViewBinding
|
||||
import com.bluelinelabs.conductor.demo.R
|
||||
import com.bluelinelabs.conductor.demo.controllers.base.BaseController
|
||||
import com.bluelinelabs.conductor.demo.databinding.ControllerCityDetailBinding
|
||||
import com.bluelinelabs.conductor.demo.databinding.RowCityDetailBinding
|
||||
import com.bluelinelabs.conductor.demo.databinding.RowCityHeaderBinding
|
||||
import com.bluelinelabs.conductor.demo.util.viewBinding
|
||||
|
||||
class CityDetailController(args: Bundle) : BaseController(R.layout.controller_city_detail, args) {
|
||||
private val binding: ControllerCityDetailBinding by viewBinding(ControllerCityDetailBinding::bind)
|
||||
|
||||
override val title = args.getString(KEY_TITLE)!!
|
||||
|
||||
constructor(@DrawableRes image: Int, title: String) : this(
|
||||
bundleOf(
|
||||
KEY_TITLE to title,
|
||||
KEY_IMAGE to image
|
||||
)
|
||||
)
|
||||
|
||||
override fun onViewCreated(view: View) {
|
||||
binding.recyclerView.setHasFixedSize(true)
|
||||
binding.recyclerView.layoutManager = LinearLayoutManager(view.context)
|
||||
binding.recyclerView.adapter = CityDetailAdapter(
|
||||
inflater = LayoutInflater.from(view.context),
|
||||
title = title,
|
||||
imageDrawableRes = args.getInt(KEY_IMAGE),
|
||||
details = LIST_ROWS,
|
||||
transitionNameBase = title
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val KEY_TITLE = "CityDetailController.title"
|
||||
private const val KEY_IMAGE = "CityDetailController.image"
|
||||
|
||||
private val LIST_ROWS = listOf(
|
||||
"• This is a city.",
|
||||
"• There's some cool stuff about it.",
|
||||
"• But really this is just a demo, not a city guide app.",
|
||||
"• This demo is meant to show some nice transitions.",
|
||||
"• You should have seen some sweet shared element transitions using the ImageView and the TextView in the \"header\" above.",
|
||||
"• This transition utilized some callbacks to ensure all the necessary rows in the RecyclerView were laid about before the transition occurred.",
|
||||
"• Just adding some more lines so it scrolls now...\n\n\n\n\n\n\nThe end."
|
||||
)
|
||||
}
|
||||
|
||||
class CityDetailAdapter(
|
||||
private val inflater: LayoutInflater,
|
||||
private val title: String,
|
||||
@DrawableRes private val imageDrawableRes: Int,
|
||||
private val details: List<String>,
|
||||
private val transitionNameBase: String
|
||||
) : RecyclerView.Adapter<ViewHolder<*>>() {
|
||||
|
||||
override fun getItemViewType(position: Int): Int {
|
||||
return if (position == 0) VIEW_TYPE_HEADER else VIEW_TYPE_DETAIL
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder<*> {
|
||||
return if (viewType == VIEW_TYPE_HEADER) {
|
||||
HeaderViewHolder(RowCityHeaderBinding.inflate(inflater, parent, false))
|
||||
} else {
|
||||
DetailViewHolder(RowCityDetailBinding.inflate(inflater, parent, false))
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: ViewHolder<*>, position: Int) {
|
||||
when (holder) {
|
||||
is HeaderViewHolder -> {
|
||||
holder.bind(
|
||||
imageDrawableRes = imageDrawableRes,
|
||||
title = title,
|
||||
imageTransitionName = inflater.context.resources.getString(R.string.transition_tag_image_named, transitionNameBase),
|
||||
textViewTransitionName = inflater.context.resources.getString(R.string.transition_tag_title_named, transitionNameBase)
|
||||
)
|
||||
}
|
||||
is DetailViewHolder -> {
|
||||
holder.bind(details[position - 1])
|
||||
}
|
||||
else -> {
|
||||
throw IllegalStateException("Invalid view holder class ${holder.javaClass.canonicalName}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItemCount() = details.size + 1
|
||||
|
||||
companion object {
|
||||
private const val VIEW_TYPE_HEADER = 0
|
||||
private const val VIEW_TYPE_DETAIL = 1
|
||||
}
|
||||
}
|
||||
|
||||
open class ViewHolder<Binding : ViewBinding>(binding: Binding) : RecyclerView.ViewHolder(binding.root)
|
||||
|
||||
class HeaderViewHolder(private val binding: RowCityHeaderBinding) : ViewHolder<RowCityHeaderBinding>(binding) {
|
||||
fun bind(@DrawableRes imageDrawableRes: Int, title: String?, imageTransitionName: String?, textViewTransitionName: String?) {
|
||||
binding.imageView.setImageResource(imageDrawableRes)
|
||||
binding.textView.text = title
|
||||
binding.imageView.transitionName = imageTransitionName
|
||||
binding.textView.transitionName = textViewTransitionName
|
||||
}
|
||||
}
|
||||
|
||||
class DetailViewHolder(private val binding: RowCityDetailBinding) : ViewHolder<RowCityDetailBinding>(binding) {
|
||||
fun bind(detail: String) {
|
||||
binding.textView.text = detail
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user