Compare commits
56 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 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 | |||
| 76074d1e3d | |||
| 623ed03df8 | |||
| 28b8810e56 | |||
| 5c397404ce | |||
| 3b81a962b1 | |||
| 4f3662bbf6 | |||
| c53b8d9d49 | |||
| c8f6e552e4 | |||
| c1cc2e2bca | |||
| 9ace20b88b | |||
| d8606498b0 | |||
| f35768393d | |||
| bbb4e8c066 |
@@ -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=
|
||||
@@ -13,34 +13,34 @@ A small, yet full-featured framework that allows building View-based Android app
|
||||
:twisted_rightwards_arrows: | Beautiful transitions between views
|
||||
:floppy_disk: | State persistence
|
||||
:phone: | Callbacks for onActivityResult, onRequestPermissionsResult, etc
|
||||
:european_post_office: | MVP / MVVM / VIPER / MVC ready
|
||||
:european_post_office: | MVP / MVVM / MVI / VIPER / MVC ready
|
||||
|
||||
Conductor is architecture-agnostic and does not try to force any design decisions on the developer. We here at BlueLine Labs tend to use either MVP or MVVM, but it would work equally well with standard MVC or whatever else you want to throw at it.
|
||||
|
||||
## Installation
|
||||
|
||||
```gradle
|
||||
implementation 'com.bluelinelabs:conductor:3.0.0-rc4'
|
||||
implementation 'com.bluelinelabs:conductor:3.1.1'
|
||||
|
||||
// AndroidX Transition change handlers:
|
||||
implementation 'com.bluelinelabs:conductor-androidx-transition:3.0.0-rc4'
|
||||
implementation 'com.bluelinelabs:conductor-androidx-transition:3.1.1'
|
||||
|
||||
// ViewPager PagerAdapter:
|
||||
implementation 'com.bluelinelabs:conductor-viewpager:3.0.0-rc4'
|
||||
implementation 'com.bluelinelabs:conductor-viewpager:3.1.1'
|
||||
|
||||
// RxJava2 lifecycle support:
|
||||
implementation 'com.bluelinelabs:conductor-rxlifecycle2:3.0.0-rc4'
|
||||
// ViewPager2 Adapter:
|
||||
implementation 'com.bluelinelabs:conductor-viewpager2:3.1.1'
|
||||
|
||||
// RxJava2 Autodispose support:
|
||||
implementation 'com.bluelinelabs:conductor-autodispose:3.0.0-rc4'
|
||||
implementation 'com.bluelinelabs:conductor-autodispose:3.1.1'
|
||||
|
||||
// Lifecycle-aware Controllers (architecture components):
|
||||
implementation 'com.bluelinelabs:conductor-archlifecycle:3.0.0-rc4'
|
||||
implementation 'com.bluelinelabs:conductor-archlifecycle:3.1.1'
|
||||
```
|
||||
|
||||
**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.2-SNAPSHOT` as your version number in any of the dependencies above and add the url to the snapshot repository:
|
||||
|
||||
```gradle
|
||||
allprojects {
|
||||
|
||||
+5
-23
@@ -1,40 +1,22 @@
|
||||
buildscript {
|
||||
|
||||
apply from: rootProject.file('dependencies.gradle')
|
||||
|
||||
repositories {
|
||||
jcenter()
|
||||
mavenCentral()
|
||||
google()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:3.6.1'
|
||||
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,9 +1,3 @@
|
||||
buildscript {
|
||||
repositories {
|
||||
jcenter()
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'com.android.library'
|
||||
|
||||
android {
|
||||
@@ -27,4 +21,4 @@ dependencies {
|
||||
ext.artifactId = 'conductor-androidx-transition'
|
||||
|
||||
apply from: rootProject.file('dependencies.gradle')
|
||||
apply from: rootProject.file('gradle-mvn-push.gradle')
|
||||
apply plugin: "com.vanniktech.maven.publish"
|
||||
|
||||
+3
@@ -25,6 +25,9 @@ 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 the <b>androidx</b> {@link Transition}. If you're using Android's platform transitions,
|
||||
* consider using the {@code SharedElementTransitionChangeHandler} provided by the {@code android-transitions} Conductor module.
|
||||
*/
|
||||
// Much of this class is based on FragmentTransition.java and FragmentTransitionCompat21.java from the Android support library
|
||||
public abstract class SharedElementTransitionChangeHandler extends TransitionChangeHandler {
|
||||
|
||||
+3
@@ -13,6 +13,9 @@ 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 the <b>androidx</b> {@link Transition}. If you're using Android's platform transitions,
|
||||
* consider using the {@code TransitionChangeHandler} provided by the {@code android-transitions} Conductor module.
|
||||
*/
|
||||
public abstract class TransitionChangeHandler extends ControllerChangeHandler {
|
||||
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
apply from: rootProject.file('dependencies.gradle')
|
||||
apply from: rootProject.file('gradle-mvn-push.gradle')
|
||||
|
||||
apply plugin: 'com.android.library'
|
||||
|
||||
android {
|
||||
@@ -21,3 +18,6 @@ dependencies {
|
||||
}
|
||||
|
||||
ext.artifactId = 'conductor-arch-components-lifecycle'
|
||||
|
||||
apply from: rootProject.file('dependencies.gradle')
|
||||
apply plugin: "com.vanniktech.maven.publish"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
apply from: rootProject.file('dependencies.gradle')
|
||||
apply from: rootProject.file('gradle-mvn-push.gradle')
|
||||
|
||||
apply plugin: 'com.android.library'
|
||||
apply plugin: "com.vanniktech.maven.publish"
|
||||
|
||||
android {
|
||||
compileSdkVersion rootProject.ext.compileSdkVersion
|
||||
|
||||
@@ -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,4 +1,5 @@
|
||||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'kotlin-android'
|
||||
|
||||
android {
|
||||
compileSdkVersion rootProject.ext.compileSdkVersion
|
||||
@@ -13,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')
|
||||
apply from: rootProject.file('gradle-mvn-push.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) {
|
||||
|
||||
-113
@@ -1,113 +0,0 @@
|
||||
package com.bluelinelabs.conductor.viewpager;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
import androidx.annotation.NonNull;
|
||||
import android.util.SparseArray;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import com.bluelinelabs.conductor.Conductor;
|
||||
import com.bluelinelabs.conductor.Router;
|
||||
import com.bluelinelabs.conductor.RouterTransaction;
|
||||
import com.bluelinelabs.conductor.viewpager.util.FakePager;
|
||||
import com.bluelinelabs.conductor.viewpager.util.TestController;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.Robolectric;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.android.controller.ActivityController;
|
||||
import org.robolectric.annotation.Config;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@Config(manifest = Config.NONE)
|
||||
public class StateSaveTests {
|
||||
|
||||
private FakePager pager;
|
||||
private RouterPagerAdapter pagerAdapter;
|
||||
|
||||
public void createActivityController(Bundle savedInstanceState) {
|
||||
ActivityController<Activity> activityController = Robolectric.buildActivity(Activity.class).create().start().resume();
|
||||
Router router = Conductor.attachRouter(activityController.get(), new FrameLayout(activityController.get()), savedInstanceState);
|
||||
TestController controller = new TestController();
|
||||
router.setRoot(RouterTransaction.with(controller));
|
||||
|
||||
pager = new FakePager(new FrameLayout(activityController.get()));
|
||||
pager.setOffscreenPageLimit(1);
|
||||
|
||||
pagerAdapter = new RouterPagerAdapter(controller) {
|
||||
@Override
|
||||
public void configureRouter(@NonNull Router router, int position) {
|
||||
if (!router.hasRootController()) {
|
||||
router.setRoot(RouterTransaction.with(new TestController()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return 20;
|
||||
}
|
||||
};
|
||||
|
||||
pager.setAdapter(pagerAdapter);
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
createActivityController(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoMaxSaves() {
|
||||
// Load all pages
|
||||
for (int i = 0; i < pagerAdapter.getCount(); i++) {
|
||||
pager.pageTo(i);
|
||||
}
|
||||
|
||||
pager.pageTo(pagerAdapter.getCount() / 2);
|
||||
|
||||
// Ensure all non-visible pages are saved
|
||||
assertEquals(pagerAdapter.getCount() - 1 - pager.getOffscreenPageLimit() * 2, pagerAdapter.getSavedPages().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMaxSavedSet() {
|
||||
final int maxPages = 3;
|
||||
pagerAdapter.setMaxPagesToStateSave(maxPages);
|
||||
|
||||
// Load all pages
|
||||
for (int i = 0; i < pagerAdapter.getCount(); i++) {
|
||||
pager.pageTo(i);
|
||||
}
|
||||
|
||||
final int firstSelectedItem = pagerAdapter.getCount() / 2;
|
||||
pager.pageTo(firstSelectedItem);
|
||||
|
||||
SparseArray<Bundle> savedPages = pagerAdapter.getSavedPages();
|
||||
|
||||
// Ensure correct number of pages are saved
|
||||
assertEquals(maxPages, savedPages.size());
|
||||
|
||||
// Ensure correct pages are saved
|
||||
assertEquals(pagerAdapter.getCount() - 3, savedPages.keyAt(0));
|
||||
assertEquals(pagerAdapter.getCount() - 2, savedPages.keyAt(1));
|
||||
assertEquals(pagerAdapter.getCount() - 1, savedPages.keyAt(2));
|
||||
|
||||
final int secondSelectedItem = 1;
|
||||
pager.pageTo(secondSelectedItem);
|
||||
|
||||
savedPages = pagerAdapter.getSavedPages();
|
||||
|
||||
// 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));
|
||||
}
|
||||
|
||||
}
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
package com.bluelinelabs.conductor.viewpager
|
||||
|
||||
import com.bluelinelabs.conductor.viewpager.util.TestActivity
|
||||
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.annotation.Config
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
@Config(manifest = Config.NONE)
|
||||
class StateSaveTests {
|
||||
|
||||
private val testController = Robolectric.buildActivity(TestActivity::class.java)
|
||||
.setup()
|
||||
.get()
|
||||
.testController()
|
||||
|
||||
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.currentItem = i
|
||||
}
|
||||
|
||||
// Ensure all non-visible pages are saved
|
||||
assertEquals(
|
||||
destroyedItems.size,
|
||||
pagerAdapter.savedPages.size()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testMaxSavedSet() {
|
||||
val maxPages = 3
|
||||
pagerAdapter.setMaxPagesToStateSave(maxPages)
|
||||
|
||||
// Load all pages
|
||||
for (i in 0 until pagerAdapter.count) {
|
||||
pager.currentItem = i
|
||||
}
|
||||
|
||||
val firstSelectedItem = pagerAdapter.count / 2
|
||||
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(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
|
||||
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(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 from: rootProject.file('gradle-mvn-push.gradle')
|
||||
|
||||
apply plugin: 'com.android.library'
|
||||
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")
|
||||
|
||||
// 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')
|
||||
apply from: rootProject.file('gradle-mvn-push.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"
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@ import com.bluelinelabs.conductor.ControllerChangeHandler.ControllerChangeListen
|
||||
* [ControllerChangeHandler]s are performing changes. It is not required to use this
|
||||
* ViewGroup, but it can be helpful.
|
||||
*/
|
||||
class ChangeHandlerFrameLayout : FrameLayout, ControllerChangeListener {
|
||||
open class ChangeHandlerFrameLayout : FrameLayout, ControllerChangeListener {
|
||||
|
||||
private var inProgressTransactionCount = 0
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
@file:JvmName("Conductor")
|
||||
|
||||
package com.bluelinelabs.conductor
|
||||
|
||||
import android.app.Activity
|
||||
@@ -9,23 +7,26 @@ import androidx.annotation.UiThread
|
||||
import com.bluelinelabs.conductor.internal.LifecycleHandler
|
||||
import com.bluelinelabs.conductor.internal.ensureMainThread
|
||||
|
||||
|
||||
/**
|
||||
* Conductor will create a [Router] that has been initialized for your Activity and containing ViewGroup.
|
||||
* If an existing [Router] is already associated with this Activity/ViewGroup pair, either in memory
|
||||
* or in the savedInstanceState, that router will be used and rebound instead of creating a new one with
|
||||
* an empty backstack.
|
||||
*
|
||||
* @receiver The Activity that will host the [Router] being attached.
|
||||
* @param activity The Activity that will host the [Router] being attached.
|
||||
* @param container The ViewGroup in which the [Router]'s [Controller] views will be hosted
|
||||
* @param savedInstanceState The savedInstanceState passed into the hosting Activity's onCreate method. Used
|
||||
* for restoring the Router's state if possible.
|
||||
* @return A fully configured [Router] instance for use with this Activity/ViewGroup pair.
|
||||
*/
|
||||
@UiThread
|
||||
fun Activity.attachRouter(container: ViewGroup, savedInstanceState: Bundle?): Router {
|
||||
ensureMainThread()
|
||||
return LifecycleHandler.install(this)
|
||||
.getRouter(container, savedInstanceState)
|
||||
.also { it.rebindIfNeeded() }
|
||||
object Conductor {
|
||||
|
||||
@JvmStatic
|
||||
fun attachRouter(activity: Activity, container: ViewGroup, savedInstanceState: Bundle?): Router {
|
||||
ensureMainThread()
|
||||
return LifecycleHandler.install(activity)
|
||||
.getRouter(container, savedInstanceState)
|
||||
.also { it.rebindIfNeeded() }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,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 +33,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 +143,7 @@ public abstract class Controller {
|
||||
this.args = args != null ? args : new Bundle(getClass().getClassLoader());
|
||||
instanceId = UUID.randomUUID().toString();
|
||||
ensureRequiredConstructor();
|
||||
OwnViewTreeLifecycleAndRegistry.Companion.own(this);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -148,12 +151,12 @@ public abstract class Controller {
|
||||
* for this method will be {@code return inflater.inflate(R.layout.my_layout, container, false);}, plus
|
||||
* any binding code.
|
||||
*
|
||||
* @param inflater The LayoutInflater that should be used to inflate views
|
||||
* @param container The parent view that this Controller's view will eventually be attached to.
|
||||
* This Controller's view should NOT be added in this method. It is simply passed in
|
||||
* so that valid LayoutParams can be used during inflation.
|
||||
* @param inflater The LayoutInflater that should be used to inflate views
|
||||
* @param container The parent view that this Controller's view will eventually be attached to.
|
||||
* This Controller's view should NOT be added in this method. It is simply passed in
|
||||
* so that valid LayoutParams can be used during inflation.
|
||||
* @param savedViewState A bundle for the view's state, which would have been created in {@link #onSaveViewState(View, Bundle)},
|
||||
* or {@code null} if no saved state exists.
|
||||
* or {@code null} if no saved state exists.
|
||||
*/
|
||||
@NonNull
|
||||
protected abstract View onCreateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container, @Nullable Bundle savedViewState);
|
||||
@@ -213,6 +216,9 @@ public abstract class Controller {
|
||||
@Nullable
|
||||
public final Router getChildRouter(@NonNull ViewGroup container, @Nullable String tag, boolean createIfNeeded) {
|
||||
@IdRes final int containerId = container.getId();
|
||||
if (containerId == View.NO_ID) {
|
||||
throw new IllegalStateException("You must set an id on your container.");
|
||||
}
|
||||
|
||||
ControllerHostedRouter childRouter = null;
|
||||
for (ControllerHostedRouter router : childRouters) {
|
||||
@@ -225,7 +231,7 @@ public abstract class Controller {
|
||||
if (childRouter == null) {
|
||||
if (createIfNeeded) {
|
||||
childRouter = new ControllerHostedRouter(container.getId(), tag);
|
||||
childRouter.setHost(this, container);
|
||||
childRouter.setHostContainer(this, container);
|
||||
childRouters.add(childRouter);
|
||||
|
||||
if (isPerformingExitTransition) {
|
||||
@@ -233,7 +239,7 @@ public abstract class Controller {
|
||||
}
|
||||
}
|
||||
} else if (!childRouter.hasHost()) {
|
||||
childRouter.setHost(this, container);
|
||||
childRouter.setHostContainer(this, container);
|
||||
childRouter.rebindIfNeeded();
|
||||
}
|
||||
|
||||
@@ -519,21 +525,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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -551,7 +572,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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -574,7 +600,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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -609,7 +640,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();
|
||||
@@ -929,7 +965,9 @@ public abstract class Controller {
|
||||
}
|
||||
}
|
||||
|
||||
childRouter.rebindIfNeeded();
|
||||
if (childRouter.hasHost()) {
|
||||
childRouter.rebindIfNeeded();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -982,7 +1020,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;
|
||||
|
||||
@@ -1031,33 +1073,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();
|
||||
}
|
||||
|
||||
@@ -1069,8 +1113,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();
|
||||
}
|
||||
}
|
||||
@@ -1229,6 +1273,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);
|
||||
}
|
||||
@@ -1377,66 +1422,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 {
|
||||
|
||||
@@ -36,12 +36,18 @@ class ControllerHostedRouter extends Router {
|
||||
this.tag = tag;
|
||||
}
|
||||
|
||||
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 +63,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 +79,6 @@ class ControllerHostedRouter extends Router {
|
||||
}
|
||||
|
||||
prepareForContainerRemoval();
|
||||
hostController = null;
|
||||
container = null;
|
||||
}
|
||||
|
||||
@@ -108,14 +113,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 +205,7 @@ class ControllerHostedRouter extends Router {
|
||||
|
||||
@Override
|
||||
boolean hasHost() {
|
||||
return hostController != null;
|
||||
return hostController != null && container != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -205,9 +225,9 @@ class ControllerHostedRouter extends Router {
|
||||
}
|
||||
|
||||
@Override
|
||||
void setControllerRouter(@NonNull Controller controller) {
|
||||
void setRouterOnController(@NonNull Controller controller) {
|
||||
controller.setParentController(hostController);
|
||||
super.setControllerRouter(controller);
|
||||
super.setRouterOnController(controller);
|
||||
}
|
||||
|
||||
int getHostId() {
|
||||
|
||||
@@ -205,7 +205,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);
|
||||
}
|
||||
}
|
||||
@@ -280,7 +280,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 +375,7 @@ public abstract class Router {
|
||||
*/
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public int getBackstackSize() {
|
||||
return backstack.size();
|
||||
return backstack.getSize();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -383,7 +383,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 +404,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 +434,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 +464,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 +552,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 +608,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 +616,7 @@ public abstract class Router {
|
||||
transaction.controller().activityDestroyed(activity);
|
||||
|
||||
for (Router childRouter : transaction.controller().getChildRouters()) {
|
||||
childRouter.onActivityDestroyed(activity);
|
||||
childRouter.onActivityDestroyed(activity, isConfigurationChange);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -622,7 +625,7 @@ public abstract class Router {
|
||||
controller.activityDestroyed(activity);
|
||||
|
||||
for (Router childRouter : controller.getChildRouters()) {
|
||||
childRouter.onActivityDestroyed(activity);
|
||||
childRouter.onActivityDestroyed(activity, isConfigurationChange);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -657,7 +660,7 @@ public abstract class Router {
|
||||
|
||||
Iterator<RouterTransaction> backstackIterator = backstack.reverseIterator();
|
||||
while (backstackIterator.hasNext()) {
|
||||
setControllerRouter(backstackIterator.next().controller());
|
||||
setRouterOnController(backstackIterator.next().controller());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -697,7 +700,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 +723,12 @@ public abstract class Router {
|
||||
}
|
||||
|
||||
void watchContainerAttach() {
|
||||
container.post(() -> containerFullyAttached = true);
|
||||
container.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
containerFullyAttached = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void prepareForContainerRemoval() {
|
||||
@@ -739,7 +747,7 @@ public abstract class Router {
|
||||
|
||||
@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 +767,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 +792,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 && !popsLastView) {
|
||||
// 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 +810,7 @@ public abstract class Router {
|
||||
if (fromController.getView() != null) {
|
||||
fromController.detach(fromController.getView(), true, false);
|
||||
} else {
|
||||
from.controller().destroy();
|
||||
fromController.destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -829,7 +837,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 +886,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 +946,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 +982,7 @@ public abstract class Router {
|
||||
return true;
|
||||
}
|
||||
|
||||
void setControllerRouter(@NonNull Controller controller) {
|
||||
void setRouterOnController(@NonNull Controller controller) {
|
||||
controller.setRouter(this);
|
||||
controller.onContextAvailable();
|
||||
}
|
||||
|
||||
@@ -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
|
||||
) {
|
||||
|
||||
|
||||
-185
@@ -1,185 +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 LegacyTransitionUtils {
|
||||
|
||||
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 (!LegacyTransitionUtils.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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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.setPopsLastView(true)
|
||||
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.setPopsLastView(true)
|
||||
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.setPopsLastView(true)
|
||||
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.setPopsLastView(true)
|
||||
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.setPopsLastView(true)
|
||||
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.setPopsLastView(true)
|
||||
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.setPopsLastView(true)
|
||||
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,82 +0,0 @@
|
||||
package com.bluelinelabs.conductor.util;
|
||||
|
||||
import android.os.Bundle;
|
||||
import androidx.annotation.IdRes;
|
||||
|
||||
import org.robolectric.Robolectric;
|
||||
import org.robolectric.android.controller.ActivityController;
|
||||
|
||||
public class ActivityProxy {
|
||||
private final @IdRes int containerId = 4;
|
||||
|
||||
private ActivityController<TestActivity> activityController;
|
||||
private AttachFakingFrameLayout view;
|
||||
|
||||
public ActivityProxy() {
|
||||
activityController = Robolectric.buildActivity(TestActivity.class);
|
||||
|
||||
view = new AttachFakingFrameLayout(activityController.get());
|
||||
view.setId(containerId);
|
||||
}
|
||||
|
||||
public void setView(AttachFakingFrameLayout view) {
|
||||
this.view = view;
|
||||
view.setId(containerId);
|
||||
}
|
||||
|
||||
public ActivityProxy create(Bundle savedInstanceState) {
|
||||
activityController.create(savedInstanceState);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ActivityProxy start() {
|
||||
activityController.start();
|
||||
view.setAttached(true);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ActivityProxy resume() {
|
||||
activityController.resume();
|
||||
return this;
|
||||
}
|
||||
|
||||
public ActivityProxy pause() {
|
||||
activityController.pause();
|
||||
return this;
|
||||
}
|
||||
|
||||
public ActivityProxy saveInstanceState(Bundle outState) {
|
||||
activityController.saveInstanceState(outState);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ActivityProxy stop(boolean detachView) {
|
||||
activityController.stop();
|
||||
|
||||
if (detachView) {
|
||||
view.setAttached(false);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public ActivityProxy destroy() {
|
||||
activityController.destroy();
|
||||
view.setAttached(false);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ActivityProxy rotate() {
|
||||
getActivity().isChangingConfigurations = true;
|
||||
getActivity().recreate();
|
||||
return this;
|
||||
}
|
||||
|
||||
public TestActivity getActivity() {
|
||||
return activityController.get();
|
||||
}
|
||||
|
||||
public AttachFakingFrameLayout getView() {
|
||||
return view;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package com.bluelinelabs.conductor.util
|
||||
|
||||
import android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
|
||||
import android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
|
||||
import android.os.Bundle
|
||||
import androidx.annotation.IdRes
|
||||
import org.robolectric.Robolectric
|
||||
|
||||
class ActivityProxy {
|
||||
|
||||
@IdRes
|
||||
private val containerId = 4
|
||||
private val activityController = Robolectric.buildActivity(TestActivity::class.java)
|
||||
|
||||
val activity: TestActivity
|
||||
get() = activityController.get()
|
||||
|
||||
var view: AttachFakingFrameLayout = AttachFakingFrameLayout(activityController.get())
|
||||
.also { it.id = containerId }
|
||||
set(value) {
|
||||
value.id = containerId
|
||||
field = value
|
||||
}
|
||||
|
||||
fun create(savedInstanceState: Bundle?): ActivityProxy {
|
||||
activityController.create(savedInstanceState)
|
||||
return this
|
||||
}
|
||||
|
||||
fun start(): ActivityProxy {
|
||||
activityController.start()
|
||||
view.setAttached(true)
|
||||
return this
|
||||
}
|
||||
|
||||
fun resume(): ActivityProxy {
|
||||
activityController.resume()
|
||||
return this
|
||||
}
|
||||
|
||||
fun pause(): ActivityProxy {
|
||||
activityController.pause()
|
||||
return this
|
||||
}
|
||||
|
||||
fun saveInstanceState(outState: Bundle?): ActivityProxy {
|
||||
activityController.saveInstanceState(outState)
|
||||
return this
|
||||
}
|
||||
|
||||
fun stop(detachView: Boolean): ActivityProxy {
|
||||
activityController.stop()
|
||||
if (detachView) {
|
||||
view.setAttached(false)
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
fun destroy(): ActivityProxy {
|
||||
activityController.destroy()
|
||||
view.setAttached(false)
|
||||
return this
|
||||
}
|
||||
|
||||
fun rotate(): ActivityProxy {
|
||||
val requestedPortrait = activity.requestedOrientation == SCREEN_ORIENTATION_PORTRAIT
|
||||
activity.requestedOrientation = if (requestedPortrait) {
|
||||
SCREEN_ORIENTATION_LANDSCAPE
|
||||
} else {
|
||||
SCREEN_ORIENTATION_PORTRAIT
|
||||
}
|
||||
return 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()
|
||||
}
|
||||
}
|
||||
+36
-13
@@ -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: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,36 @@
|
||||
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.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)
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
-168
@@ -1,168 +0,0 @@
|
||||
package com.bluelinelabs.conductor.demo.controllers;
|
||||
|
||||
import android.graphics.PorterDuff.Mode;
|
||||
import android.os.Bundle;
|
||||
import androidx.annotation.DrawableRes;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.core.view.ViewCompat;
|
||||
import androidx.recyclerview.widget.GridLayoutManager;
|
||||
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.RouterTransaction;
|
||||
import com.bluelinelabs.conductor.demo.R;
|
||||
import com.bluelinelabs.conductor.demo.changehandler.CityGridSharedElementTransitionChangeHandler;
|
||||
import com.bluelinelabs.conductor.demo.controllers.base.BaseController;
|
||||
import com.bluelinelabs.conductor.demo.util.BundleBuilder;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
import butterknife.OnClick;
|
||||
|
||||
public class CityGridController extends BaseController {
|
||||
|
||||
private static final String KEY_TITLE = "CityGridController.title";
|
||||
private static final String KEY_DOT_COLOR = "CityGridController.dotColor";
|
||||
private static final String KEY_FROM_POSITION = "CityGridController.position";
|
||||
|
||||
private static final CityModel[] CITY_MODELS = new CityModel[] {
|
||||
new CityModel(R.drawable.chicago, "Chicago"),
|
||||
new CityModel(R.drawable.jakarta, "Jakarta"),
|
||||
new CityModel(R.drawable.london, "London"),
|
||||
new CityModel(R.drawable.sao_paulo, "Sao Paulo"),
|
||||
new CityModel(R.drawable.tokyo, "Tokyo")
|
||||
};
|
||||
|
||||
@BindView(R.id.tv_title) TextView tvTitle;
|
||||
@BindView(R.id.img_dot) ImageView imgDot;
|
||||
@BindView(R.id.recycler_view) RecyclerView recyclerView;
|
||||
|
||||
private String title;
|
||||
private int dotColor;
|
||||
private int fromPosition;
|
||||
|
||||
public CityGridController(String title, int dotColor, int fromPosition) {
|
||||
this(new BundleBuilder(new Bundle())
|
||||
.putString(KEY_TITLE, title)
|
||||
.putInt(KEY_DOT_COLOR, dotColor)
|
||||
.putInt(KEY_FROM_POSITION, fromPosition)
|
||||
.build());
|
||||
}
|
||||
|
||||
public CityGridController(Bundle args) {
|
||||
super(args);
|
||||
title = getArgs().getString(KEY_TITLE);
|
||||
dotColor = getArgs().getInt(KEY_DOT_COLOR);
|
||||
fromPosition = getArgs().getInt(KEY_FROM_POSITION);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected View inflateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) {
|
||||
return inflater.inflate(R.layout.controller_city_grid, container, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onViewBound(@NonNull View view) {
|
||||
super.onViewBound(view);
|
||||
|
||||
tvTitle.setText(title);
|
||||
imgDot.getDrawable().setColorFilter(ContextCompat.getColor(getActivity(), dotColor), Mode.SRC_ATOP);
|
||||
|
||||
ViewCompat.setTransitionName(tvTitle, getResources().getString(R.string.transition_tag_title_indexed, fromPosition));
|
||||
ViewCompat.setTransitionName(imgDot, getResources().getString(R.string.transition_tag_dot_indexed, fromPosition));
|
||||
|
||||
recyclerView.setHasFixedSize(true);
|
||||
recyclerView.setLayoutManager(new GridLayoutManager(view.getContext(), 2));
|
||||
recyclerView.setAdapter(new CityGridAdapter(LayoutInflater.from(view.getContext()), CITY_MODELS));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getTitle() {
|
||||
return "Shared Element Demos";
|
||||
}
|
||||
|
||||
void onModelRowClick(CityModel model) {
|
||||
String imageTransitionName = getResources().getString(R.string.transition_tag_image_named, model.title);
|
||||
String titleTransitionName = getResources().getString(R.string.transition_tag_title_named, model.title);
|
||||
|
||||
List<String> names = new ArrayList<>();
|
||||
names.add(imageTransitionName);
|
||||
names.add(titleTransitionName);
|
||||
|
||||
getRouter().pushController(RouterTransaction.with(new CityDetailController(model.drawableRes, model.title))
|
||||
.pushChangeHandler(new CityGridSharedElementTransitionChangeHandler(names))
|
||||
.popChangeHandler(new CityGridSharedElementTransitionChangeHandler(names)));
|
||||
}
|
||||
|
||||
class CityGridAdapter extends RecyclerView.Adapter<CityGridAdapter.ViewHolder> {
|
||||
|
||||
private final LayoutInflater inflater;
|
||||
private final CityModel[] items;
|
||||
|
||||
public CityGridAdapter(LayoutInflater inflater, CityModel[] items) {
|
||||
this.inflater = inflater;
|
||||
this.items = items;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||
return new ViewHolder(inflater.inflate(R.layout.row_city_grid, parent, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(ViewHolder holder, int position) {
|
||||
holder.bind(items[position]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return items.length;
|
||||
}
|
||||
|
||||
class ViewHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
@BindView(R.id.tv_title) TextView textView;
|
||||
@BindView(R.id.img_city) ImageView imageView;
|
||||
private CityModel model;
|
||||
|
||||
public ViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
ButterKnife.bind(this, itemView);
|
||||
}
|
||||
|
||||
void bind(CityModel item) {
|
||||
model = item;
|
||||
imageView.setImageResource(item.drawableRes);
|
||||
textView.setText(item.title);
|
||||
|
||||
ViewCompat.setTransitionName(textView, getResources().getString(R.string.transition_tag_title_named, model.title));
|
||||
ViewCompat.setTransitionName(imageView, getResources().getString(R.string.transition_tag_image_named, model.title));
|
||||
}
|
||||
|
||||
@OnClick(R.id.row_root)
|
||||
void onRowClick() {
|
||||
onModelRowClick(model);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private static class CityModel {
|
||||
@DrawableRes int drawableRes;
|
||||
String title;
|
||||
|
||||
public CityModel(@DrawableRes int drawableRes, String title) {
|
||||
this.drawableRes = drawableRes;
|
||||
this.title = title;
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user