Compare commits
104 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c3f7d128f5 | |||
| 5e1f072672 | |||
| b0d15d9f9e | |||
| 8ac2e04c62 | |||
| cdbdee5c42 | |||
| 8488242a26 | |||
| 1fe0187439 | |||
| f78726b916 | |||
| 1f918f10c5 | |||
| bd584727be | |||
| 91db7fe65f | |||
| 2abe2b33f9 | |||
| ac4e09cf67 | |||
| 055532bb21 | |||
| 15037c2217 | |||
| 728f1fb4e9 | |||
| 55c8d64d8a | |||
| 88e0eb882b | |||
| 63a92db540 | |||
| ba98e3b165 | |||
| 966bc1645d | |||
| c8ac58ad6a | |||
| 5f04d9de89 | |||
| d32fc813d0 | |||
| c2bc72c5ce | |||
| 924e4bebfa | |||
| 4ea4aa5c56 | |||
| 3b275d31c2 | |||
| 0e21c8c9c1 | |||
| 8297e0273d | |||
| 46519c2c2c | |||
| 211da8b2ea | |||
| 26db962168 | |||
| f4c1c6ccf5 | |||
| c89caa87e0 | |||
| 2748566437 | |||
| 506c99ed41 | |||
| 4fe0ec5f51 | |||
| afa93f2cc1 | |||
| 836f92b615 | |||
| 94c817bbd9 | |||
| fc1fee3e17 | |||
| 76b7572a01 | |||
| 3fc63b7f5f | |||
| 0ef52211a2 | |||
| 7574131940 | |||
| 1ab9a4c4f6 | |||
| 3bc23bd5cd | |||
| 5f138e5d43 | |||
| 03701d05a9 | |||
| a19968e0c9 | |||
| 5501ab2ac8 | |||
| 804fdb615e | |||
| c01b2a74d6 | |||
| 8a8622c261 | |||
| 6820aa7d6a | |||
| 9ce27e4dee | |||
| 3c8ad0a833 | |||
| a720ac57e8 | |||
| 7d6901389b | |||
| e54e88bf0d | |||
| 010117603c | |||
| cd11ac9d6b | |||
| e78347709b | |||
| 341debc5b9 | |||
| 2346e48154 | |||
| 4174e12958 | |||
| c0abed0813 | |||
| 6fdb1d6ed3 | |||
| 3334b8e21f | |||
| 240424dc63 | |||
| f768e9ab00 | |||
| 4c89124683 | |||
| e0bbd48935 | |||
| 76074d1e3d | |||
| 623ed03df8 | |||
| 28b8810e56 | |||
| 5c397404ce | |||
| 3b81a962b1 | |||
| 4f3662bbf6 | |||
| c53b8d9d49 | |||
| c8f6e552e4 | |||
| c1cc2e2bca | |||
| 9ace20b88b | |||
| d8606498b0 | |||
| f35768393d | |||
| bbb4e8c066 | |||
| 12e66867f1 | |||
| 8eee79dcea | |||
| 5ebd8c9a5d | |||
| 4c9f2e9f30 | |||
| b0340d4c67 | |||
| f6b396f679 | |||
| 589cb91fff | |||
| a51f4192cb | |||
| 266d6a0fd6 | |||
| 7410f7d123 | |||
| 56b5ce3b33 | |||
| a20ee6477a | |||
| 41f14f6ae5 | |||
| a170942ae1 | |||
| 6df6ce8de7 | |||
| 7ddc115918 | |||
| 5f8f3ad98e |
@@ -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: Release with Gradle
|
||||
run: ./gradlew clean publishAllPublicationsToMavenCentral
|
||||
env:
|
||||
ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.SONATYPE_NEXUS_USERNAME }}
|
||||
ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.SONATYPE_NEXUS_PASSWORD }}
|
||||
+4
-4
@@ -5,10 +5,12 @@
|
||||
.checkstyle
|
||||
|
||||
# IntelliJ IDEA
|
||||
.idea
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
/.idea/*
|
||||
!/.idea/codeStyles/
|
||||
!/.idea/scopes/
|
||||
classes
|
||||
gen-external-apklibs
|
||||
|
||||
@@ -35,6 +37,4 @@ pom.xml.*
|
||||
local.properties
|
||||
|
||||
*.prefs
|
||||
|
||||
# The keystore file
|
||||
app/spothero-release.keystore
|
||||
.DS_Store
|
||||
|
||||
Generated
+9
@@ -0,0 +1,9 @@
|
||||
/libraries
|
||||
/runConfigurations.xml
|
||||
/misc.xml
|
||||
/vcs.xml
|
||||
/workspace.xml
|
||||
/caches
|
||||
/gradle.xml
|
||||
/modules.xml
|
||||
/compiler.xml
|
||||
Generated
+137
@@ -0,0 +1,137 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<code_scheme name="Project" version="173">
|
||||
<JetCodeStyleSettings>
|
||||
<option name="NAME_COUNT_TO_USE_STAR_IMPORT" value="2147483647" />
|
||||
<option name="NAME_COUNT_TO_USE_STAR_IMPORT_FOR_MEMBERS" value="2147483647" />
|
||||
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
||||
</JetCodeStyleSettings>
|
||||
<codeStyleSettings language="XML">
|
||||
<indentOptions>
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||
</indentOptions>
|
||||
<arrangement>
|
||||
<rules>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>xmlns:android</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>xmlns:.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:id</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:name</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>name</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>style</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>ANDROID_ATTRIBUTE_ORDER</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>.*</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
</rules>
|
||||
</arrangement>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="kotlin">
|
||||
<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" />
|
||||
<option name="TAB_SIZE" value="2" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
</code_scheme>
|
||||
</component>
|
||||
Generated
+6
@@ -0,0 +1,6 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<state>
|
||||
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
|
||||
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default (1)" />
|
||||
</state>
|
||||
</component>
|
||||
Generated
+6
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Encoding">
|
||||
<file url="PROJECT" charset="UTF-8" />
|
||||
</component>
|
||||
</project>
|
||||
-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=
|
||||
@@ -1,4 +1,4 @@
|
||||
[](https://travis-ci.com/bluelinelabs/Conductor) [](http://android-arsenal.com/details/1/3361) [](http://javadoc.io/doc/com.bluelinelabs/conductor)
|
||||
[](https://github.com/bluelinelabs/conductor/actions/workflows/main.yml) [](http://android-arsenal.com/details/1/3361) [](http://javadoc.io/doc/com.bluelinelabs/conductor)
|
||||
|
||||
# Conductor
|
||||
|
||||
@@ -13,37 +13,36 @@ 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-rc3'
|
||||
def conductorVersion = '3.2.0'
|
||||
|
||||
// If you want AndroidX Transition change handlers:
|
||||
implementation 'com.bluelinelabs:conductor-androidx-transition:3.0.0-rc3'
|
||||
implementation "com.bluelinelabs:conductor:$conductorVersion"
|
||||
|
||||
// If you want a PagerAdapter for ViewPager:
|
||||
implementation 'com.bluelinelabs:conductor-viewpager:3.0.0-rc3'
|
||||
// AndroidX Transition change handlers:
|
||||
implementation "com.bluelinelabs:conductor-androidx-transition:$conductorVersion"
|
||||
|
||||
// If you want RxJava lifecycle support:
|
||||
implementation 'com.bluelinelabs:conductor-rxlifecycle:3.0.0-rc3'
|
||||
// ViewPager PagerAdapter:
|
||||
implementation "com.bluelinelabs:conductor-viewpager:$conductorVersion"
|
||||
|
||||
// If you want RxJava2 lifecycle support:
|
||||
implementation 'com.bluelinelabs:conductor-rxlifecycle2:3.0.0-rc3'
|
||||
// ViewPager2 Adapter:
|
||||
implementation "com.bluelinelabs:conductor-viewpager2:$conductorVersion"
|
||||
|
||||
// If you want RxJava2 Autodispose support:
|
||||
implementation 'com.bluelinelabs:conductor-autodispose:3.0.0-rc3'
|
||||
// RxJava2 Autodispose support:
|
||||
implementation "com.bluelinelabs:conductor-autodispose:$conductorVersion"
|
||||
|
||||
// If you want Controllers that are Lifecycle-aware (architecture components):
|
||||
implementation 'com.bluelinelabs:conductor-archlifecycle:3.0.0-rc3'
|
||||
// Lifecycle-aware Controllers (architecture components):
|
||||
implementation "com.bluelinelabs:conductor-archlifecycle:$conductorVersion"
|
||||
```
|
||||
|
||||
**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.2.1-SNAPSHOT` as your version number in any of the dependencies above and add the url to the snapshot repository:
|
||||
|
||||
```gradle
|
||||
allprojects {
|
||||
@@ -79,7 +78,8 @@ public class MainActivity extends Activity {
|
||||
|
||||
ViewGroup container = (ViewGroup) findViewById(R.id.controller_container);
|
||||
|
||||
router = Conductor.attachRouter(this, container, savedInstanceState);
|
||||
router = Conductor.attachRouter(this, container, savedInstanceState)
|
||||
.setPopRootControllerMode(PopRootControllerMode.NEVER);
|
||||
if (!router.hasRootController()) {
|
||||
router.setRoot(RouterTransaction.with(new HomeController()));
|
||||
}
|
||||
@@ -101,7 +101,7 @@ public class MainActivity extends Activity {
|
||||
public class HomeController extends Controller {
|
||||
|
||||
@Override
|
||||
protected View onCreateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) {
|
||||
protected View onCreateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container, @Nullable Bundle savedViewState) {
|
||||
View view = inflater.inflate(R.layout.controller_home, container, false);
|
||||
((TextView) view.findViewById(R.id.tv_title)).setText("Hello World");
|
||||
return view;
|
||||
@@ -144,7 +144,7 @@ The community has provided several helpful modules to make developing apps with
|
||||
|
||||
## License
|
||||
```
|
||||
Copyright 2016 BlueLine Labs, Inc.
|
||||
Copyright 2020 BlueLine Labs, Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
+21
-6
@@ -1,19 +1,34 @@
|
||||
buildscript {
|
||||
repositories {
|
||||
jcenter()
|
||||
mavenCentral()
|
||||
google()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:3.5.2'
|
||||
classpath libs.agp
|
||||
classpath libs.kotlin.plugin
|
||||
classpath libs.dokka
|
||||
}
|
||||
}
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven { url 'https://maven.google.com' }
|
||||
jcenter()
|
||||
google()
|
||||
}
|
||||
|
||||
if (project.hasProperty('maven_publish_url')) {
|
||||
pluginManager.withPlugin("com.vanniktech.maven.publish") {
|
||||
publishing {
|
||||
repositories {
|
||||
maven {
|
||||
url = maven_publish_url
|
||||
credentials {
|
||||
username = maven_publish_username
|
||||
password = maven_publish_password
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
apply from: rootProject.file('dependencies.gradle')
|
||||
|
||||
@@ -1,26 +1,21 @@
|
||||
apply plugin: 'java'
|
||||
|
||||
targetCompatibility = JavaVersion.VERSION_1_7
|
||||
sourceCompatibility = JavaVersion.VERSION_1_7
|
||||
apply plugin: 'kotlin'
|
||||
|
||||
configurations {
|
||||
lintChecks
|
||||
libs.lint.checks
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation rootProject.ext.lintapi
|
||||
implementation rootProject.ext.lintchecks
|
||||
compileOnly libs.lint.api
|
||||
compileOnly libs.lint.checks
|
||||
compileOnly libs.kotlin.stdlib
|
||||
|
||||
testImplementation rootProject.ext.lint
|
||||
testImplementation rootProject.ext.lintTests
|
||||
|
||||
lintChecks files(jar)
|
||||
testImplementation libs.junit
|
||||
testImplementation libs.lint
|
||||
testImplementation libs.lint.tests
|
||||
}
|
||||
|
||||
jar {
|
||||
manifest {
|
||||
attributes('Lint-Registry': 'com.bluelinelabs.conductor.lint.IssueRegistry')
|
||||
attributes('Lint-Registry-v2': 'com.bluelinelabs.conductor.lint.IssueRegistry')
|
||||
}
|
||||
}
|
||||
|
||||
apply from: rootProject.file('dependencies.gradle')
|
||||
|
||||
+5
-10
@@ -10,17 +10,18 @@ import com.android.tools.lint.detector.api.JavaContext;
|
||||
import com.android.tools.lint.detector.api.Scope;
|
||||
import com.android.tools.lint.detector.api.Severity;
|
||||
|
||||
import 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;
|
||||
|
||||
@SuppressWarnings("UnstableApiUsage")
|
||||
public final class ControllerChangeHandlerIssueDetector extends Detector implements Detector.UastScanner {
|
||||
|
||||
public static final Issue ISSUE =
|
||||
static final Issue ISSUE =
|
||||
Issue.create("ValidControllerChangeHandler", "ControllerChangeHandler not instantiatable",
|
||||
"Non-abstract ControllerChangeHandler instances must have a default constructor for the"
|
||||
+ " system to re-create them in the case of the process being killed.",
|
||||
@@ -41,18 +42,12 @@ public final class ControllerChangeHandlerIssueDetector extends Detector impleme
|
||||
return new UElementHandler() {
|
||||
|
||||
@Override
|
||||
public void visitClass(UClass node) {
|
||||
public void visitClass(@NotNull UClass node) {
|
||||
if (evaluator.isAbstract(node)) {
|
||||
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;
|
||||
}
|
||||
|
||||
+7
-12
@@ -11,18 +11,19 @@ import com.android.tools.lint.detector.api.JavaContext;
|
||||
import com.android.tools.lint.detector.api.Scope;
|
||||
import com.android.tools.lint.detector.api.Severity;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
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;
|
||||
|
||||
@SuppressWarnings("UnstableApiUsage")
|
||||
public final class ControllerIssueDetector extends Detector implements Detector.UastScanner {
|
||||
|
||||
public static final Issue ISSUE =
|
||||
static final Issue ISSUE =
|
||||
Issue.create("ValidController", "Controller not instantiatable",
|
||||
"Non-abstract Controller instances must have a default or single-argument constructor"
|
||||
+ " that takes a Bundle in order for the system to re-create them in the"
|
||||
@@ -33,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
|
||||
@@ -42,18 +43,12 @@ public final class ControllerIssueDetector extends Detector implements Detector.
|
||||
|
||||
return new UElementHandler() {
|
||||
@Override
|
||||
public void visitClass(UClass node) {
|
||||
public void visitClass(@NotNull UClass node) {
|
||||
if (evaluator.isAbstract(node)) {
|
||||
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;
|
||||
}
|
||||
@@ -100,5 +95,5 @@ public final class ControllerIssueDetector extends Detector implements Detector.
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -1,23 +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;
|
||||
|
||||
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,35 +1,24 @@
|
||||
buildscript {
|
||||
repositories {
|
||||
jcenter()
|
||||
}
|
||||
plugins {
|
||||
id("com.android.library")
|
||||
alias(libs.plugins.mvnpublish)
|
||||
}
|
||||
|
||||
apply plugin: 'com.android.library'
|
||||
|
||||
android {
|
||||
compileSdkVersion rootProject.ext.compileSdkVersion
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_7
|
||||
targetCompatibility JavaVersion.VERSION_1_7
|
||||
}
|
||||
compileSdkVersion libs.versions.compilesdk.get() as Integer
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
minSdkVersion libs.versions.minsdk.get()
|
||||
targetSdkVersion libs.versions.targetsdk.get()
|
||||
versionCode Integer.parseInt(project.VERSION_CODE)
|
||||
versionName project.VERSION_NAME
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation rootProject.ext.androidxAppCompat
|
||||
implementation rootProject.ext.androidxCollection
|
||||
api rootProject.ext.androidxTransition
|
||||
implementation libs.androidx.appcompat
|
||||
implementation libs.androidx.collection
|
||||
api libs.androidx.transition
|
||||
implementation project(':conductor')
|
||||
}
|
||||
|
||||
ext.artifactId = 'conductor-androidx-transition'
|
||||
|
||||
apply from: rootProject.file('dependencies.gradle')
|
||||
apply from: rootProject.file('gradle-mvn-push.gradle')
|
||||
|
||||
+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,26 +1,21 @@
|
||||
apply from: rootProject.file('dependencies.gradle')
|
||||
apply from: rootProject.file('gradle-mvn-push.gradle')
|
||||
|
||||
apply plugin: 'com.android.library'
|
||||
plugins {
|
||||
id("com.android.library")
|
||||
alias(libs.plugins.mvnpublish)
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion rootProject.ext.compileSdkVersion
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_7
|
||||
targetCompatibility JavaVersion.VERSION_1_7
|
||||
}
|
||||
compileSdkVersion libs.versions.compilesdk.get() as Integer
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
minSdkVersion libs.versions.minsdk.get()
|
||||
targetSdkVersion libs.versions.targetsdk.get()
|
||||
versionCode Integer.parseInt(project.VERSION_CODE)
|
||||
versionName project.VERSION_NAME
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api rootProject.ext.archComponentsLifecycle
|
||||
api libs.androidx.lifecycle.runtime
|
||||
|
||||
implementation project(':conductor')
|
||||
}
|
||||
|
||||
+4
-1
@@ -51,7 +51,10 @@ public class ControllerLifecycleOwner implements LifecycleOwner {
|
||||
|
||||
@Override
|
||||
public void preDestroy(@NonNull Controller controller) {
|
||||
lifecycleRegistry.handleLifecycleEvent(Event.ON_DESTROY); // --> State.DESTROYED;
|
||||
// Only act on Controllers that have had at least the onContextAvailable call made on them.
|
||||
if (lifecycleRegistry.getCurrentState() != Lifecycle.State.INITIALIZED) {
|
||||
lifecycleRegistry.handleLifecycleEvent(Event.ON_DESTROY); // --> State.DESTROYED;
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
-28
@@ -1,28 +0,0 @@
|
||||
package com.bluelinelabs.conductor.archlifecycle;
|
||||
|
||||
import androidx.lifecycle.Lifecycle;
|
||||
import androidx.lifecycle.LifecycleOwner;
|
||||
import android.os.Bundle;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.bluelinelabs.conductor.RestoreViewOnCreateController;
|
||||
|
||||
public abstract class LifecycleRestoreViewOnCreateController extends RestoreViewOnCreateController implements LifecycleOwner {
|
||||
|
||||
private final ControllerLifecycleOwner mLifecycleOwner = new ControllerLifecycleOwner(this);
|
||||
|
||||
public LifecycleRestoreViewOnCreateController() {
|
||||
super();
|
||||
}
|
||||
|
||||
public LifecycleRestoreViewOnCreateController(@Nullable Bundle args) {
|
||||
super(args);
|
||||
}
|
||||
|
||||
@Override @NonNull
|
||||
public Lifecycle getLifecycle() {
|
||||
return mLifecycleOwner.getLifecycle();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,28 +1,23 @@
|
||||
apply from: rootProject.file('dependencies.gradle')
|
||||
apply from: rootProject.file('gradle-mvn-push.gradle')
|
||||
|
||||
apply plugin: 'com.android.library'
|
||||
plugins {
|
||||
id("com.android.library")
|
||||
alias(libs.plugins.mvnpublish)
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion rootProject.ext.compileSdkVersion
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_7
|
||||
targetCompatibility JavaVersion.VERSION_1_7
|
||||
}
|
||||
compileSdkVersion libs.versions.compilesdk.get() as Integer
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
minSdkVersion libs.versions.minsdk.get()
|
||||
targetSdkVersion libs.versions.targetsdk.get()
|
||||
versionCode Integer.parseInt(project.VERSION_CODE)
|
||||
versionName project.VERSION_NAME
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api rootProject.ext.rxJava2
|
||||
api rootProject.ext.autodispose
|
||||
api rootProject.ext.autodisposeLifecycle
|
||||
api libs.rxjava2
|
||||
api libs.autodispose
|
||||
api libs.autodispose.lifecycle
|
||||
|
||||
implementation project(':conductor')
|
||||
}
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
apply from: rootProject.file('dependencies.gradle')
|
||||
apply from: rootProject.file('gradle-mvn-push.gradle')
|
||||
|
||||
apply plugin: 'com.android.library'
|
||||
|
||||
android {
|
||||
compileSdkVersion rootProject.ext.compileSdkVersion
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_7
|
||||
targetCompatibility JavaVersion.VERSION_1_7
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode Integer.parseInt(project.VERSION_CODE)
|
||||
versionName project.VERSION_NAME
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api rootProject.ext.rxJava
|
||||
api rootProject.ext.rxLifecycle
|
||||
api rootProject.ext.rxLifecycleAndroid
|
||||
|
||||
implementation project(':conductor')
|
||||
}
|
||||
|
||||
ext.artifactId = 'conductor-rxlifecycle'
|
||||
@@ -1,3 +0,0 @@
|
||||
POM_NAME=Conductor RxLifecycle Extensions
|
||||
POM_ARTIFACT_ID=conductor-rxlifecycle
|
||||
POM_PACKAGING=aar
|
||||
@@ -1,3 +0,0 @@
|
||||
<manifest package="com.bluelinelabs.conductor.rxlifecycle">
|
||||
<application />
|
||||
</manifest>
|
||||
-14
@@ -1,14 +0,0 @@
|
||||
package com.bluelinelabs.conductor.rxlifecycle;
|
||||
|
||||
public enum ControllerEvent {
|
||||
|
||||
CREATE,
|
||||
CONTEXT_AVAILABLE,
|
||||
CREATE_VIEW,
|
||||
ATTACH,
|
||||
DETACH,
|
||||
DESTROY_VIEW,
|
||||
CONTEXT_UNAVAILABLE,
|
||||
DESTROY
|
||||
|
||||
}
|
||||
-77
@@ -1,77 +0,0 @@
|
||||
package com.bluelinelabs.conductor.rxlifecycle;
|
||||
|
||||
import android.content.Context;
|
||||
import androidx.annotation.NonNull;
|
||||
import android.view.View;
|
||||
|
||||
import com.bluelinelabs.conductor.Controller;
|
||||
import com.bluelinelabs.conductor.Controller.LifecycleListener;
|
||||
import com.trello.rxlifecycle.OutsideLifecycleException;
|
||||
|
||||
import rx.subjects.BehaviorSubject;
|
||||
|
||||
/**
|
||||
* A simple utility class that will create a {@link BehaviorSubject} that calls onNext when events
|
||||
* occur in your {@link Controller}
|
||||
*/
|
||||
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.create(initialState);
|
||||
|
||||
controller.addLifecycleListener(new 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;
|
||||
}
|
||||
|
||||
}
|
||||
-52
@@ -1,52 +0,0 @@
|
||||
package com.bluelinelabs.conductor.rxlifecycle;
|
||||
|
||||
import android.os.Bundle;
|
||||
import androidx.annotation.CheckResult;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.bluelinelabs.conductor.Controller;
|
||||
import com.trello.rxlifecycle.LifecycleProvider;
|
||||
import com.trello.rxlifecycle.LifecycleTransformer;
|
||||
import com.trello.rxlifecycle.RxLifecycle;
|
||||
|
||||
import rx.Observable;
|
||||
import rx.subjects.BehaviorSubject;
|
||||
|
||||
/**
|
||||
* A base {@link Controller} that can be used to expose lifecycle events using RxJava
|
||||
*/
|
||||
public abstract class RxController extends Controller implements LifecycleProvider<ControllerEvent> {
|
||||
|
||||
private final BehaviorSubject<ControllerEvent> lifecycleSubject;
|
||||
|
||||
public RxController() {
|
||||
this(null);
|
||||
}
|
||||
|
||||
public RxController(Bundle args) {
|
||||
super(args);
|
||||
lifecycleSubject = ControllerLifecycleSubjectHelper.create(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
@CheckResult
|
||||
public final Observable<ControllerEvent> lifecycle() {
|
||||
return lifecycleSubject.asObservable();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
@CheckResult
|
||||
public final <T> LifecycleTransformer<T> bindUntilEvent(@NonNull ControllerEvent event) {
|
||||
return RxLifecycle.bindUntilEvent(lifecycleSubject, event);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
@CheckResult
|
||||
public final <T> LifecycleTransformer<T> bindToLifecycle() {
|
||||
return RxControllerLifecycle.bindController(lifecycleSubject);
|
||||
}
|
||||
|
||||
}
|
||||
-48
@@ -1,48 +0,0 @@
|
||||
package com.bluelinelabs.conductor.rxlifecycle;
|
||||
|
||||
import androidx.annotation.CheckResult;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.trello.rxlifecycle.LifecycleTransformer;
|
||||
import com.trello.rxlifecycle.OutsideLifecycleException;
|
||||
import com.trello.rxlifecycle.RxLifecycle;
|
||||
|
||||
import rx.Observable;
|
||||
import rx.functions.Func1;
|
||||
|
||||
public class RxControllerLifecycle {
|
||||
|
||||
/**
|
||||
* Binds the given source to a Controller lifecycle. This is the Controller version of
|
||||
* {@link com.trello.rxlifecycle.android.RxLifecycleAndroid#bindFragment(Observable)}.
|
||||
*
|
||||
* @param lifecycle the lifecycle sequence of a Controller
|
||||
* @return a reusable {@link rx.Observable.Transformer} that unsubscribes the source during the Controller lifecycle
|
||||
*/
|
||||
@NonNull
|
||||
@CheckResult
|
||||
public static <T> LifecycleTransformer<T> bindController(@NonNull final Observable<ControllerEvent> lifecycle) {
|
||||
return RxLifecycle.bind(lifecycle, CONTROLLER_LIFECYCLE);
|
||||
}
|
||||
|
||||
private static final Func1<ControllerEvent, ControllerEvent> CONTROLLER_LIFECYCLE =
|
||||
new Func1<ControllerEvent, ControllerEvent>() {
|
||||
@Override
|
||||
public ControllerEvent call(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.");
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
-52
@@ -1,52 +0,0 @@
|
||||
package com.bluelinelabs.conductor.rxlifecycle;
|
||||
|
||||
import android.os.Bundle;
|
||||
import androidx.annotation.CheckResult;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.bluelinelabs.conductor.RestoreViewOnCreateController;
|
||||
import com.trello.rxlifecycle.LifecycleProvider;
|
||||
import com.trello.rxlifecycle.LifecycleTransformer;
|
||||
import com.trello.rxlifecycle.RxLifecycle;
|
||||
|
||||
import rx.Observable;
|
||||
import rx.subjects.BehaviorSubject;
|
||||
|
||||
/**
|
||||
* A base {@link RestoreViewOnCreateController} that can be used to expose lifecycle events using RxJava
|
||||
*/
|
||||
public abstract class RxRestoreViewOnCreateController extends RestoreViewOnCreateController implements LifecycleProvider<ControllerEvent> {
|
||||
|
||||
private final BehaviorSubject<ControllerEvent> lifecycleSubject;
|
||||
|
||||
public RxRestoreViewOnCreateController() {
|
||||
this(null);
|
||||
}
|
||||
|
||||
public RxRestoreViewOnCreateController(Bundle args) {
|
||||
super(args);
|
||||
lifecycleSubject = ControllerLifecycleSubjectHelper.create(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
@CheckResult
|
||||
public final Observable<ControllerEvent> lifecycle() {
|
||||
return lifecycleSubject.asObservable();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
@CheckResult
|
||||
public final <T> LifecycleTransformer<T> bindUntilEvent(@NonNull ControllerEvent event) {
|
||||
return RxLifecycle.bindUntilEvent(lifecycleSubject, event);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
@CheckResult
|
||||
public final <T> LifecycleTransformer<T> bindToLifecycle() {
|
||||
return RxControllerLifecycle.bindController(lifecycleSubject);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
apply from: rootProject.file('dependencies.gradle')
|
||||
apply from: rootProject.file('gradle-mvn-push.gradle')
|
||||
|
||||
apply plugin: 'com.android.library'
|
||||
|
||||
android {
|
||||
compileSdkVersion rootProject.ext.compileSdkVersion
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_7
|
||||
targetCompatibility JavaVersion.VERSION_1_7
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode Integer.parseInt(project.VERSION_CODE)
|
||||
versionName project.VERSION_NAME
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api rootProject.ext.rxJava2
|
||||
api rootProject.ext.rxLifecycle2
|
||||
api rootProject.ext.rxLifecycleAndroid2
|
||||
|
||||
implementation project(':conductor')
|
||||
}
|
||||
|
||||
ext.artifactId = 'conductor-rxlifecycle2'
|
||||
@@ -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.");
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
-45
@@ -1,45 +0,0 @@
|
||||
package com.bluelinelabs.conductor.rxlifecycle2;
|
||||
|
||||
import android.os.Bundle;
|
||||
import androidx.annotation.CheckResult;
|
||||
import androidx.annotation.NonNull;
|
||||
import com.bluelinelabs.conductor.RestoreViewOnCreateController;
|
||||
import com.trello.rxlifecycle2.LifecycleProvider;
|
||||
import com.trello.rxlifecycle2.LifecycleTransformer;
|
||||
import com.trello.rxlifecycle2.RxLifecycle;
|
||||
import io.reactivex.Observable;
|
||||
import io.reactivex.subjects.BehaviorSubject;
|
||||
|
||||
public abstract class RxRestoreViewOnCreateController extends RestoreViewOnCreateController implements LifecycleProvider<ControllerEvent> {
|
||||
private final BehaviorSubject<ControllerEvent> lifecycleSubject;
|
||||
|
||||
public RxRestoreViewOnCreateController() {
|
||||
this(null);
|
||||
}
|
||||
|
||||
public RxRestoreViewOnCreateController(Bundle args) {
|
||||
super(args);
|
||||
lifecycleSubject = ControllerLifecycleSubjectHelper.create(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
@CheckResult
|
||||
public final Observable<ControllerEvent> lifecycle() {
|
||||
return lifecycleSubject.hide();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
@CheckResult
|
||||
public final <T> LifecycleTransformer<T> bindUntilEvent(@NonNull ControllerEvent event) {
|
||||
return RxLifecycle.bindUntilEvent(lifecycleSubject, event);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
@CheckResult
|
||||
public final <T> LifecycleTransformer<T> bindToLifecycle() {
|
||||
return RxControllerLifecycle.bindController(lifecycleSubject);
|
||||
}
|
||||
}
|
||||
@@ -1,40 +1,26 @@
|
||||
buildscript {
|
||||
repositories {
|
||||
jcenter()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'de.mobilej.unmock:UnMockPlugin:0.6.5'
|
||||
}
|
||||
plugins {
|
||||
id("com.android.library")
|
||||
id("kotlin-android")
|
||||
alias(libs.plugins.mvnpublish)
|
||||
}
|
||||
|
||||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'de.mobilej.unmock'
|
||||
|
||||
android {
|
||||
compileSdkVersion rootProject.ext.compileSdkVersion
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_7
|
||||
targetCompatibility JavaVersion.VERSION_1_7
|
||||
}
|
||||
compileSdkVersion libs.versions.compilesdk.get() as Integer
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
minSdkVersion libs.versions.minsdk.get()
|
||||
targetSdkVersion libs.versions.targetsdk.get()
|
||||
versionCode Integer.parseInt(project.VERSION_CODE)
|
||||
versionName project.VERSION_NAME
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
testImplementation rootProject.ext.junit
|
||||
testImplementation rootProject.ext.roboelectric
|
||||
testImplementation libs.junit
|
||||
testImplementation libs.robolectric
|
||||
|
||||
implementation rootProject.ext.androidxAppCompat
|
||||
implementation libs.androidx.appcompat
|
||||
implementation project(':conductor')
|
||||
}
|
||||
|
||||
ext.artifactId = 'conductor-viewpager'
|
||||
|
||||
apply from: rootProject.file('dependencies.gradle')
|
||||
apply from: rootProject.file('gradle-mvn-push.gradle')
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
<manifest package="com.bluelinelabs.conductor.support">
|
||||
<manifest package="com.bluelinelabs.conductor.viewpager">
|
||||
<application />
|
||||
</manifest>
|
||||
|
||||
+14
-13
@@ -1,4 +1,4 @@
|
||||
package com.bluelinelabs.conductor.support;
|
||||
package com.bluelinelabs.conductor.viewpager;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcelable;
|
||||
@@ -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;
|
||||
|
||||
@@ -77,7 +77,8 @@ public abstract class RouterPagerAdapter extends PagerAdapter {
|
||||
savedPages.remove(position);
|
||||
}
|
||||
|
||||
Router router = host.getChildRouter(container, name);
|
||||
Router router = host.getChildRouter(container, name)
|
||||
.setPopRootControllerMode(Router.PopRootControllerMode.NEVER);
|
||||
if (!router.hasRootController()) {
|
||||
Bundle routerSavedState = savedPages.get(position);
|
||||
|
||||
@@ -103,7 +104,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 +122,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 +140,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.support;
|
||||
|
||||
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.support.util.FakePager;
|
||||
import com.bluelinelabs.conductor.support.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));
|
||||
}
|
||||
|
||||
}
|
||||
-61
@@ -1,61 +0,0 @@
|
||||
package com.bluelinelabs.conductor.support.util;
|
||||
|
||||
import android.util.SparseArray;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.bluelinelabs.conductor.support.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;
|
||||
}
|
||||
}
|
||||
+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))
|
||||
}
|
||||
}
|
||||
+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)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
plugins {
|
||||
id("com.android.library")
|
||||
id("kotlin-android")
|
||||
id("kotlin-parcelize")
|
||||
alias(libs.plugins.mvnpublish)
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion libs.versions.compilesdk.get() as Integer
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion libs.versions.minsdk.get()
|
||||
targetSdkVersion libs.versions.targetsdk.get()
|
||||
versionCode Integer.parseInt(project.VERSION_CODE)
|
||||
versionName project.VERSION_NAME
|
||||
}
|
||||
|
||||
testOptions {
|
||||
unitTests {
|
||||
includeAndroidResources = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
testImplementation libs.junit
|
||||
testImplementation libs.robolectric
|
||||
|
||||
implementation libs.androidx.appcompat
|
||||
implementation libs.androidx.viewpager2
|
||||
implementation project(':conductor')
|
||||
}
|
||||
|
||||
ext.artifactId = 'conductor-viewpager2'
|
||||
@@ -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>
|
||||
+256
@@ -0,0 +1,256 @@
|
||||
package com.bluelinelabs.conductor.viewpager2
|
||||
|
||||
import android.os.Bundle
|
||||
import android.os.Parcelable
|
||||
import android.util.LongSparseArray
|
||||
import android.util.SparseArray
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.viewpager2.adapter.StatefulAdapter
|
||||
import androidx.viewpager2.widget.ViewPager2
|
||||
import com.bluelinelabs.conductor.Controller
|
||||
import com.bluelinelabs.conductor.Router
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
/**
|
||||
* An ViewPager2 adapter that uses Routers as pages
|
||||
*/
|
||||
abstract class RouterStateAdapter(private val host: Controller) :
|
||||
RecyclerView.Adapter<RouterViewHolder>(), StatefulAdapter {
|
||||
|
||||
private var savedPages = LongSparseArray<Bundle>()
|
||||
internal var savedPageHistory = mutableListOf<Long>()
|
||||
private var maxPagesToStateSave = Int.MAX_VALUE
|
||||
private val visibleRouters = SparseArray<Router>()
|
||||
private var currentPrimaryRouterPosition = 0
|
||||
private var primaryItemCallback: PrimaryItemCallback? = null
|
||||
|
||||
init {
|
||||
super.setHasStableIds(true)
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a router is instantiated. Here the router's root should be set if needed.
|
||||
*
|
||||
* @param router The router used for the page
|
||||
* @param position The page position to be instantiated.
|
||||
*/
|
||||
abstract fun configureRouter(router: Router, position: Int)
|
||||
|
||||
/**
|
||||
* Sets the maximum number of pages that will have their states saved. When this number is exceeded,
|
||||
* the page that was state saved least recently will have its state removed from the save data.
|
||||
*/
|
||||
open fun setMaxPagesToStateSave(maxPagesToStateSave: Int) {
|
||||
require(maxPagesToStateSave >= 0) { "Only positive integers may be passed for maxPagesToStateSave." }
|
||||
this.maxPagesToStateSave = maxPagesToStateSave
|
||||
ensurePagesSaved()
|
||||
}
|
||||
|
||||
private fun inferViewPager(recyclerView: RecyclerView): ViewPager2 {
|
||||
return recyclerView.parent as? ViewPager2
|
||||
?: error("Expected ViewPager2 instance. Got: ${recyclerView.parent}")
|
||||
}
|
||||
|
||||
override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {
|
||||
val viewPager = inferViewPager(recyclerView)
|
||||
primaryItemCallback = PrimaryItemCallback().also {
|
||||
viewPager.registerOnPageChangeCallback(it)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) {
|
||||
val viewPager = inferViewPager(recyclerView)
|
||||
primaryItemCallback?.let {
|
||||
viewPager.unregisterOnPageChangeCallback(it)
|
||||
}
|
||||
primaryItemCallback = null
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RouterViewHolder {
|
||||
return RouterViewHolder(parent)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: RouterViewHolder, position: Int) {
|
||||
holder.currentItemPosition = position
|
||||
|
||||
attachRouter(holder, position)
|
||||
}
|
||||
|
||||
override fun onViewAttachedToWindow(holder: RouterViewHolder) {
|
||||
super.onViewAttachedToWindow(holder)
|
||||
|
||||
if (!holder.attached) {
|
||||
attachRouter(holder, holder.currentItemPosition)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onViewDetachedFromWindow(holder: RouterViewHolder) {
|
||||
super.onViewDetachedFromWindow(holder)
|
||||
|
||||
detachRouter(holder)
|
||||
|
||||
// Controller has fully detached and destroyed its view reference by now. Remove the leftover
|
||||
// view from the container.
|
||||
holder.container.removeAllViews()
|
||||
}
|
||||
|
||||
override fun onViewRecycled(holder: RouterViewHolder) {
|
||||
super.onViewRecycled(holder)
|
||||
|
||||
detachRouter(holder)
|
||||
|
||||
holder.currentRouter?.let { router ->
|
||||
host.removeChildRouter(router)
|
||||
holder.currentRouter = null
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFailedToRecycleView(holder: RouterViewHolder): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
override fun getItemId(position: Int): Long {
|
||||
return position.toLong()
|
||||
}
|
||||
|
||||
override fun setHasStableIds(hasStableIds: Boolean) {
|
||||
throw UnsupportedOperationException("Stable Ids are required for the adapter to function properly")
|
||||
}
|
||||
|
||||
override fun saveState(): Parcelable {
|
||||
// Ensure all visible pages are saved, starting at the outermost pages and working our way in
|
||||
val visiblePositions = (0 until visibleRouters.size())
|
||||
.map { visibleRouters.keyAt(it) }.toMutableList()
|
||||
while (visiblePositions.isNotEmpty()) {
|
||||
val lastPosition = visiblePositions.removeAt(visiblePositions.lastIndex)
|
||||
savePage(getItemId(lastPosition), visibleRouters[lastPosition])
|
||||
|
||||
if (visiblePositions.isNotEmpty()) {
|
||||
val firstPosition = visiblePositions.removeAt(0)
|
||||
savePage(getItemId(firstPosition), visibleRouters[firstPosition])
|
||||
}
|
||||
}
|
||||
|
||||
return SavedState(
|
||||
savedPagesKeys = (0 until savedPages.size()).map { savedPages.keyAt(it) },
|
||||
savedPagesValues = (0 until savedPages.size()).map { savedPages.valueAt(it) },
|
||||
savedPageHistory = savedPageHistory,
|
||||
maxPagesToStateSave = maxPagesToStateSave
|
||||
)
|
||||
}
|
||||
|
||||
override fun restoreState(state: Parcelable) {
|
||||
if (state !is SavedState) return
|
||||
|
||||
savedPages = LongSparseArray()
|
||||
state.savedPagesKeys.indices.forEach { index ->
|
||||
savedPages.put(state.savedPagesKeys[index], state.savedPagesValues[index])
|
||||
}
|
||||
|
||||
savedPageHistory = state.savedPageHistory.toMutableList()
|
||||
maxPagesToStateSave = state.maxPagesToStateSave
|
||||
}
|
||||
|
||||
private fun attachRouter(holder: RouterViewHolder, position: Int) {
|
||||
val itemId = getItemId(position)
|
||||
val router = host.getChildRouter(holder.container, "$itemId", true, false)!!
|
||||
.setPopRootControllerMode(Router.PopRootControllerMode.NEVER)
|
||||
|
||||
// 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())
|
||||
}
|
||||
}
|
||||
+6
-3
@@ -1,17 +1,20 @@
|
||||
package com.bluelinelabs.conductor.support.util;
|
||||
package com.bluelinelabs.conductor.viewpager2.util;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.bluelinelabs.conductor.Controller;
|
||||
|
||||
public class TestController extends Controller {
|
||||
|
||||
@Override @NonNull
|
||||
protected View onCreateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) {
|
||||
protected View onCreateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container, @Nullable Bundle savedViewState) {
|
||||
return new FrameLayout(inflater.getContext());
|
||||
}
|
||||
|
||||
+15
-40
@@ -1,58 +1,33 @@
|
||||
buildscript {
|
||||
repositories {
|
||||
jcenter()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'de.mobilej.unmock:UnMockPlugin:0.6.5'
|
||||
}
|
||||
plugins {
|
||||
id("com.android.library")
|
||||
id("kotlin-android")
|
||||
alias(libs.plugins.mvnpublish)
|
||||
}
|
||||
|
||||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'de.mobilej.unmock'
|
||||
|
||||
android {
|
||||
compileSdkVersion rootProject.ext.compileSdkVersion
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_7
|
||||
targetCompatibility JavaVersion.VERSION_1_7
|
||||
}
|
||||
compileSdkVersion libs.versions.compilesdk.get() as Integer
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
minSdkVersion libs.versions.minsdk.get()
|
||||
targetSdkVersion libs.versions.targetsdk.get()
|
||||
versionCode Integer.parseInt(project.VERSION_CODE)
|
||||
versionName project.VERSION_NAME
|
||||
consumerProguardFiles 'proguard-rules.txt'
|
||||
}
|
||||
}
|
||||
|
||||
configurations {
|
||||
lintChecks
|
||||
}
|
||||
|
||||
dependencies {
|
||||
testImplementation rootProject.ext.junit
|
||||
testImplementation rootProject.ext.roboelectric
|
||||
implementation libs.androidx.savedstate.ktx
|
||||
testImplementation libs.junit
|
||||
testImplementation libs.robolectric
|
||||
testImplementation libs.kotest
|
||||
|
||||
api rootProject.ext.androidxAnnotations
|
||||
implementation libs.androidx.lifecycle.runtime
|
||||
|
||||
lintChecks project(path: ':conductor-lint', configuration: 'lintChecks')
|
||||
}
|
||||
api libs.androidx.annotation
|
||||
api libs.kotlin.stdlib
|
||||
|
||||
task copyLintJar(type: Copy) {
|
||||
from(configurations.lintChecks) {
|
||||
rename { 'lint.jar' }
|
||||
}
|
||||
into 'build/intermediates/lint/'
|
||||
}
|
||||
|
||||
project.afterEvaluate {
|
||||
def compileLintTask = project.tasks.find { it.name == 'compileLint' }
|
||||
compileLintTask.dependsOn(copyLintJar)
|
||||
lintPublish project(':conductor-lint')
|
||||
}
|
||||
|
||||
ext.artifactId = 'conductor'
|
||||
|
||||
apply from: rootProject.file('dependencies.gradle')
|
||||
apply from: rootProject.file('gradle-mvn-push.gradle')
|
||||
|
||||
@@ -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,117 +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"
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
package com.bluelinelabs.conductor;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import com.bluelinelabs.conductor.ControllerChangeHandler.ControllerChangeListener;
|
||||
|
||||
/**
|
||||
* A FrameLayout implementation that can be used to block user interactions while
|
||||
* {@link ControllerChangeHandler}s are performing changes. It is not required to use this
|
||||
* ViewGroup, but it can be helpful.
|
||||
*/
|
||||
public class ChangeHandlerFrameLayout extends FrameLayout implements ControllerChangeListener {
|
||||
|
||||
private int inProgressTransactionCount;
|
||||
|
||||
public ChangeHandlerFrameLayout(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public ChangeHandlerFrameLayout(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public ChangeHandlerFrameLayout(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
public ChangeHandlerFrameLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onInterceptTouchEvent(MotionEvent ev) {
|
||||
return (inProgressTransactionCount > 0) || super.onInterceptTouchEvent(ev);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChangeStarted(@Nullable Controller to, @Nullable Controller from, boolean isPush, @NonNull ViewGroup container, @NonNull ControllerChangeHandler handler) {
|
||||
inProgressTransactionCount++;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChangeCompleted(@Nullable Controller to, @Nullable Controller from, boolean isPush, @NonNull ViewGroup container, @NonNull ControllerChangeHandler handler) {
|
||||
inProgressTransactionCount--;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package com.bluelinelabs.conductor
|
||||
|
||||
import android.annotation.TargetApi
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.util.AttributeSet
|
||||
import android.view.MotionEvent
|
||||
import android.view.ViewGroup
|
||||
import android.widget.FrameLayout
|
||||
import com.bluelinelabs.conductor.ControllerChangeHandler.ControllerChangeListener
|
||||
|
||||
/**
|
||||
* A FrameLayout implementation that can be used to block user interactions while
|
||||
* [ControllerChangeHandler]s are performing changes. It is not required to use this
|
||||
* ViewGroup, but it can be helpful.
|
||||
*/
|
||||
open class ChangeHandlerFrameLayout : FrameLayout, ControllerChangeListener {
|
||||
|
||||
private var inProgressTransactionCount = 0
|
||||
|
||||
constructor(context: Context) : super(context)
|
||||
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
|
||||
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(
|
||||
context,
|
||||
attrs,
|
||||
defStyleAttr
|
||||
)
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int) : super(
|
||||
context,
|
||||
attrs,
|
||||
defStyleAttr,
|
||||
defStyleRes
|
||||
)
|
||||
|
||||
override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
|
||||
return inProgressTransactionCount > 0 || super.onInterceptTouchEvent(ev)
|
||||
}
|
||||
|
||||
override fun onChangeStarted(
|
||||
to: Controller?,
|
||||
from: Controller?,
|
||||
isPush: Boolean,
|
||||
container: ViewGroup,
|
||||
handler: ControllerChangeHandler
|
||||
) {
|
||||
inProgressTransactionCount++
|
||||
}
|
||||
|
||||
override fun onChangeCompleted(
|
||||
to: Controller?,
|
||||
from: Controller?,
|
||||
isPush: Boolean,
|
||||
container: ViewGroup,
|
||||
handler: ControllerChangeHandler
|
||||
) {
|
||||
inProgressTransactionCount--
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
package com.bluelinelabs.conductor;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.UiThread;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.bluelinelabs.conductor.internal.LifecycleHandler;
|
||||
import com.bluelinelabs.conductor.internal.ThreadUtils;
|
||||
|
||||
/**
|
||||
* Point of initial interaction with Conductor. Used to attach a {@link Router} to your Activity.
|
||||
*/
|
||||
public final class Conductor {
|
||||
|
||||
private Conductor() {}
|
||||
|
||||
/**
|
||||
* Conductor will create a {@link Router} that has been initialized for your Activity and containing ViewGroup.
|
||||
* If an existing {@link 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.
|
||||
*
|
||||
* @param activity The Activity that will host the {@link Router} being attached.
|
||||
* @param container The ViewGroup in which the {@link Router}'s {@link 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 {@link Router} instance for use with this Activity/ViewGroup pair.
|
||||
*/
|
||||
@NonNull @UiThread
|
||||
public static Router attachRouter(@NonNull Activity activity, @NonNull ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
ThreadUtils.ensureMainThread();
|
||||
|
||||
LifecycleHandler lifecycleHandler = LifecycleHandler.install(activity);
|
||||
|
||||
Router router = lifecycleHandler.getRouter(container, savedInstanceState);
|
||||
router.rebindIfNeeded();
|
||||
|
||||
return router;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.bluelinelabs.conductor
|
||||
|
||||
import android.app.Activity
|
||||
import android.os.Bundle
|
||||
import android.view.ViewGroup
|
||||
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.
|
||||
*
|
||||
* @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
|
||||
object Conductor {
|
||||
|
||||
@JvmStatic
|
||||
fun attachRouter(activity: Activity, container: ViewGroup, savedInstanceState: Bundle?): Router {
|
||||
ensureMainThread()
|
||||
return LifecycleHandler.install(activity)
|
||||
.getRouter(container, savedInstanceState)
|
||||
.also { it.rebindIfNeeded() }
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,6 @@ import android.content.res.Resources;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcelable;
|
||||
import android.text.TextUtils;
|
||||
import android.util.SparseArray;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
@@ -18,7 +17,12 @@ import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.IdRes;
|
||||
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,10 +36,6 @@ import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import androidx.annotation.IdRes;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* A Controller manages portions of the UI. It is similar to an Activity or Fragment in that it manages its
|
||||
* own lifecycle and controls interactions between the UI and whatever logic is required. It is, however,
|
||||
@@ -108,10 +108,10 @@ public abstract class Controller {
|
||||
Controller controller;
|
||||
try {
|
||||
if (bundleConstructor != null) {
|
||||
controller = (Controller)bundleConstructor.newInstance(args);
|
||||
controller = (Controller) bundleConstructor.newInstance(args);
|
||||
} else {
|
||||
//noinspection ConstantConditions
|
||||
controller = (Controller)getDefaultConstructor(constructors).newInstance();
|
||||
controller = (Controller) getDefaultConstructor(constructors).newInstance();
|
||||
|
||||
// Restore the args that existed before the last process death
|
||||
if (args != null) {
|
||||
@@ -142,6 +142,7 @@ public abstract class Controller {
|
||||
this.args = args != null ? args : new Bundle(getClass().getClassLoader());
|
||||
instanceId = UUID.randomUUID().toString();
|
||||
ensureRequiredConstructor();
|
||||
OwnViewTreeLifecycleAndRegistry.Companion.own(this);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -149,13 +150,15 @@ 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.
|
||||
*/
|
||||
@NonNull
|
||||
protected abstract View onCreateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container);
|
||||
protected abstract View onCreateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container, @Nullable Bundle savedViewState);
|
||||
|
||||
/**
|
||||
* Returns the {@link Router} object that can be used for pushing or popping other Controllers
|
||||
@@ -190,7 +193,7 @@ public abstract class Controller {
|
||||
* the same container unless you have a great reason to do so (ex: ViewPagers).
|
||||
*
|
||||
* @param container The ViewGroup that hosts the child Router
|
||||
* @param tag The router's tag or {@code null} if none is needed
|
||||
* @param tag The router's tag or {@code null} if none is needed
|
||||
*/
|
||||
@NonNull
|
||||
public final Router getChildRouter(@NonNull ViewGroup container, @Nullable String tag) {
|
||||
@@ -205,17 +208,37 @@ public abstract class Controller {
|
||||
* The only time this method will return {@code null} is when the child router does not exist prior
|
||||
* to calling this method and the createIfNeeded parameter is set to false.
|
||||
*
|
||||
* @param container The ViewGroup that hosts the child Router
|
||||
* @param tag The router's tag or {@code null} if none is needed
|
||||
* @param container The ViewGroup that hosts the child Router
|
||||
* @param tag The router's tag or {@code null} if none is needed
|
||||
* @param createIfNeeded If true, a router will be created if one does not yet exist. Else {@code null} will be returned in this case.
|
||||
*/
|
||||
@Nullable
|
||||
public final Router getChildRouter(@NonNull ViewGroup container, @Nullable String tag, boolean createIfNeeded) {
|
||||
return getChildRouter(container, tag, createIfNeeded, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the child {@link Router} for the given container/tag combination. Note that multiple
|
||||
* routers should not exist in the same container unless a lot of care is taken to maintain order
|
||||
* between them. Avoid using the same container unless you have a great reason to do so (ex: ViewPagers).
|
||||
* The only time this method will return {@code null} is when the child router does not exist prior
|
||||
* to calling this method and the createIfNeeded parameter is set to false.
|
||||
*
|
||||
* @param container The ViewGroup that hosts the child Router
|
||||
* @param tag The router's tag or {@code null} if none is needed
|
||||
* @param createIfNeeded If true, a router will be created if one does not yet exist. Else {@code null} will be returned in this case.
|
||||
* @param boundToHostContainerId If true, a router will only ever rebind with a container with the same view id on state restoration. Note that this must be set to true if the tag is null.
|
||||
*/
|
||||
@Nullable
|
||||
public final Router getChildRouter(@NonNull ViewGroup container, @Nullable String tag, boolean createIfNeeded, boolean boundToHostContainerId) {
|
||||
@IdRes final int containerId = container.getId();
|
||||
if (containerId == View.NO_ID) {
|
||||
throw new IllegalStateException("You must set an id on your container.");
|
||||
}
|
||||
|
||||
ControllerHostedRouter childRouter = null;
|
||||
for (ControllerHostedRouter router : childRouters) {
|
||||
if (router.getHostId() == containerId && TextUtils.equals(tag, router.getTag())) {
|
||||
if (router.matches(containerId, tag)) {
|
||||
childRouter = router;
|
||||
break;
|
||||
}
|
||||
@@ -223,8 +246,8 @@ public abstract class Controller {
|
||||
|
||||
if (childRouter == null) {
|
||||
if (createIfNeeded) {
|
||||
childRouter = new ControllerHostedRouter(container.getId(), tag);
|
||||
childRouter.setHost(this, container);
|
||||
childRouter = new ControllerHostedRouter(container.getId(), tag, boundToHostContainerId);
|
||||
childRouter.setHostContainer(this, container);
|
||||
childRouters.add(childRouter);
|
||||
|
||||
if (isPerformingExitTransition) {
|
||||
@@ -232,7 +255,7 @@ public abstract class Controller {
|
||||
}
|
||||
}
|
||||
} else if (!childRouter.hasHost()) {
|
||||
childRouter.setHost(this, container);
|
||||
childRouter.setHostContainer(this, container);
|
||||
childRouter.rebindIfNeeded();
|
||||
}
|
||||
|
||||
@@ -394,7 +417,8 @@ public abstract class Controller {
|
||||
*
|
||||
* @param view The View to which this Controller should be bound.
|
||||
*/
|
||||
protected void onDestroyView(@NonNull View view) { }
|
||||
protected void onDestroyView(@NonNull View view) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when this Controller begins the process of being swapped in or out of the host view.
|
||||
@@ -402,7 +426,8 @@ public abstract class Controller {
|
||||
* @param changeHandler The {@link ControllerChangeHandler} that's managing the swap
|
||||
* @param changeType The type of change that's occurring
|
||||
*/
|
||||
protected void onChangeStarted(@NonNull ControllerChangeHandler changeHandler, @NonNull ControllerChangeType changeType) { }
|
||||
protected void onChangeStarted(@NonNull ControllerChangeHandler changeHandler, @NonNull ControllerChangeType changeType) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when this Controller completes the process of being swapped in or out of the host view.
|
||||
@@ -410,59 +435,69 @@ public abstract class Controller {
|
||||
* @param changeHandler The {@link ControllerChangeHandler} that's managing the swap
|
||||
* @param changeType The type of change that occurred
|
||||
*/
|
||||
protected void onChangeEnded(@NonNull ControllerChangeHandler changeHandler, @NonNull ControllerChangeType changeType) { }
|
||||
protected void onChangeEnded(@NonNull ControllerChangeHandler changeHandler, @NonNull ControllerChangeType changeType) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when this Controller has a Context available to it. This will happen very early on in the lifecycle
|
||||
* (before a view is created). If the host activity is re-created (ex: for orientation change), this will be
|
||||
* called again when the new context is available.
|
||||
*/
|
||||
protected void onContextAvailable(@NonNull Context context) { }
|
||||
protected void onContextAvailable(@NonNull Context context) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when this Controller's Context is no longer available. This can happen when the Controller is
|
||||
* destroyed or when the host Activity is destroyed.
|
||||
*/
|
||||
protected void onContextUnavailable() { }
|
||||
protected void onContextUnavailable() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when this Controller is attached to its host ViewGroup
|
||||
*
|
||||
* @param view The View for this Controller (passed for convenience)
|
||||
*/
|
||||
protected void onAttach(@NonNull View view) { }
|
||||
protected void onAttach(@NonNull View view) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when this Controller is detached from its host ViewGroup
|
||||
*
|
||||
* @param view The View for this Controller (passed for convenience)
|
||||
*/
|
||||
protected void onDetach(@NonNull View view) { }
|
||||
protected void onDetach(@NonNull View view) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when this Controller has been destroyed.
|
||||
*/
|
||||
protected void onDestroy() { }
|
||||
protected void onDestroy() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when this Controller's host Activity is started
|
||||
*/
|
||||
protected void onActivityStarted(@NonNull Activity activity) { }
|
||||
protected void onActivityStarted(@NonNull Activity activity) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when this Controller's host Activity is resumed
|
||||
*/
|
||||
protected void onActivityResumed(@NonNull Activity activity) { }
|
||||
protected void onActivityResumed(@NonNull Activity activity) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when this Controller's host Activity is paused
|
||||
*/
|
||||
protected void onActivityPaused(@NonNull Activity activity) { }
|
||||
protected void onActivityPaused(@NonNull Activity activity) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when this Controller's host Activity is stopped
|
||||
*/
|
||||
protected void onActivityStopped(@NonNull Activity activity) { }
|
||||
protected void onActivityStopped(@NonNull Activity activity) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to save this Controller's View state. As Views can be detached and destroyed as part of the
|
||||
@@ -472,7 +507,8 @@ public abstract class Controller {
|
||||
* @param view This Controller's View, passed for convenience
|
||||
* @param outState The Bundle into which the View state should be saved
|
||||
*/
|
||||
protected void onSaveViewState(@NonNull View view, @NonNull Bundle outState) { }
|
||||
protected void onSaveViewState(@NonNull View view, @NonNull Bundle outState) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Restores data that was saved in the {@link #onSaveViewState(View, Bundle)} method. This should be overridden
|
||||
@@ -481,14 +517,16 @@ public abstract class Controller {
|
||||
* @param view This Controller's View, passed for convenience
|
||||
* @param savedViewState The bundle that has data to be restored
|
||||
*/
|
||||
protected void onRestoreViewState(@NonNull View view, @NonNull Bundle savedViewState) { }
|
||||
protected void onRestoreViewState(@NonNull View view, @NonNull Bundle savedViewState) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to save this Controller's state in the event that its host Activity is destroyed.
|
||||
*
|
||||
* @param outState The Bundle into which data should be saved
|
||||
*/
|
||||
protected void onSaveInstanceState(@NonNull Bundle outState) { }
|
||||
protected void onSaveInstanceState(@NonNull Bundle outState) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Restores data that was saved in the {@link #onSaveInstanceState(Bundle)} method. This should be overridden
|
||||
@@ -496,14 +534,18 @@ public abstract class Controller {
|
||||
*
|
||||
* @param savedInstanceState The bundle that has data to be restored
|
||||
*/
|
||||
protected void onRestoreInstanceState(@NonNull Bundle savedInstanceState) { }
|
||||
protected void onRestoreInstanceState(@NonNull Bundle savedInstanceState) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls startActivity(Intent) from this Controller's host Activity.
|
||||
*/
|
||||
public final void startActivity(@NonNull final Intent intent) {
|
||||
executeWithRouter(new RouterRequiringFunc() {
|
||||
@Override public void execute() { router.startActivity(intent); }
|
||||
@Override
|
||||
public void execute() {
|
||||
router.startActivity(intent);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -512,7 +554,10 @@ public abstract class Controller {
|
||||
*/
|
||||
public final void startActivityForResult(@NonNull final Intent intent, final int requestCode) {
|
||||
executeWithRouter(new RouterRequiringFunc() {
|
||||
@Override public void execute() { router.startActivityForResult(instanceId, intent, requestCode); }
|
||||
@Override
|
||||
public void execute() {
|
||||
router.startActivityForResult(instanceId, intent, requestCode);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -521,7 +566,10 @@ public abstract class Controller {
|
||||
*/
|
||||
public final void startActivityForResult(@NonNull final Intent intent, final int requestCode, @Nullable final Bundle options) {
|
||||
executeWithRouter(new RouterRequiringFunc() {
|
||||
@Override public void execute() { router.startActivityForResult(instanceId, intent, requestCode, options); }
|
||||
@Override
|
||||
public void execute() {
|
||||
router.startActivityForResult(instanceId, intent, requestCode, options);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -541,7 +589,10 @@ public abstract class Controller {
|
||||
*/
|
||||
public final void registerForActivityResult(final int requestCode) {
|
||||
executeWithRouter(new RouterRequiringFunc() {
|
||||
@Override public void execute() { router.registerForActivityResult(instanceId, requestCode); }
|
||||
@Override
|
||||
public void execute() {
|
||||
router.registerForActivityResult(instanceId, requestCode);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -553,7 +604,8 @@ public abstract class Controller {
|
||||
* @param resultCode The resultCode that was returned to the host Activity's onActivityResult method
|
||||
* @param data The data Intent that was returned to the host Activity's onActivityResult method
|
||||
*/
|
||||
public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { }
|
||||
public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls requestPermission(String[], int) from this Controller's host Activity. Results for this request,
|
||||
@@ -565,7 +617,10 @@ public abstract class Controller {
|
||||
requestedPermissions.addAll(Arrays.asList(permissions));
|
||||
|
||||
executeWithRouter(new RouterRequiringFunc() {
|
||||
@Override public void execute() { router.requestPermissions(instanceId, permissions, requestCode); }
|
||||
@Override
|
||||
public void execute() {
|
||||
router.requestPermissions(instanceId, permissions, requestCode);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -586,7 +641,8 @@ public abstract class Controller {
|
||||
* @param permissions The array of permissions requested
|
||||
* @param grantResults The results for each permission requested
|
||||
*/
|
||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { }
|
||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Should be overridden if this Controller needs to handle the back button being pressed.
|
||||
@@ -603,12 +659,12 @@ public abstract class Controller {
|
||||
Collections.sort(childTransactions, new Comparator<RouterTransaction>() {
|
||||
@Override
|
||||
public int compare(RouterTransaction o1, RouterTransaction o2) {
|
||||
return o2.transactionIndex - o1.transactionIndex;
|
||||
return o2.getTransactionIndex() - o1.getTransactionIndex();
|
||||
}
|
||||
});
|
||||
|
||||
for (RouterTransaction transaction : childTransactions) {
|
||||
Controller childController = transaction.controller;
|
||||
Controller childController = transaction.controller();
|
||||
|
||||
if (childController.isAttached() && childController.getRouter().handleBack()) {
|
||||
return true;
|
||||
@@ -653,7 +709,7 @@ public abstract class Controller {
|
||||
public void setRetainViewMode(@NonNull RetainViewMode retainViewMode) {
|
||||
this.retainViewMode = retainViewMode != null ? retainViewMode : RetainViewMode.RELEASE_DETACH;
|
||||
if (this.retainViewMode == RetainViewMode.RELEASE_DETACH && !attached) {
|
||||
removeViewReference();
|
||||
removeViewReference(null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -728,10 +784,11 @@ public abstract class Controller {
|
||||
* Adds option items to the host Activity's standard options menu. This will only be called if
|
||||
* {@link #setHasOptionsMenu(boolean)} has been called.
|
||||
*
|
||||
* @param menu The menu into which your options should be placed.
|
||||
* @param menu The menu into which your options should be placed.
|
||||
* @param inflater The inflater that can be used to inflate your menu items.
|
||||
*/
|
||||
public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) { }
|
||||
public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the screen's options menu to be displayed. This is called directly before showing the
|
||||
@@ -739,7 +796,8 @@ public abstract class Controller {
|
||||
*
|
||||
* @param menu The menu that will be displayed
|
||||
*/
|
||||
public void onPrepareOptionsMenu(@NonNull Menu menu) { }
|
||||
public void onPrepareOptionsMenu(@NonNull Menu menu) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when an option menu item has been selected by the user.
|
||||
@@ -814,6 +872,27 @@ public abstract class Controller {
|
||||
}
|
||||
}
|
||||
|
||||
final void onContextUnavailable(@NonNull Context context) {
|
||||
for (Router childRouter : childRouters) {
|
||||
childRouter.onContextUnavailable(context);
|
||||
}
|
||||
|
||||
if (isContextAvailable) {
|
||||
List<LifecycleListener> listeners = new ArrayList<>(lifecycleListeners);
|
||||
for (LifecycleListener lifecycleListener : listeners) {
|
||||
lifecycleListener.preContextUnavailable(this, context);
|
||||
}
|
||||
|
||||
isContextAvailable = false;
|
||||
onContextUnavailable();
|
||||
|
||||
listeners = new ArrayList<>(lifecycleListeners);
|
||||
for (LifecycleListener lifecycleListener : listeners) {
|
||||
lifecycleListener.postContextUnavailable(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final void executeWithRouter(@NonNull RouterRequiringFunc listener) {
|
||||
if (router != null) {
|
||||
listener.execute();
|
||||
@@ -866,20 +945,7 @@ public abstract class Controller {
|
||||
destroy(true);
|
||||
}
|
||||
|
||||
if (isContextAvailable) {
|
||||
List<LifecycleListener> listeners = new ArrayList<>(lifecycleListeners);
|
||||
for (LifecycleListener lifecycleListener : listeners) {
|
||||
lifecycleListener.preContextUnavailable(this, activity);
|
||||
}
|
||||
|
||||
isContextAvailable = false;
|
||||
onContextUnavailable();
|
||||
|
||||
listeners = new ArrayList<>(lifecycleListeners);
|
||||
for (LifecycleListener lifecycleListener : listeners) {
|
||||
lifecycleListener.postContextUnavailable(this);
|
||||
}
|
||||
}
|
||||
onContextUnavailable(activity);
|
||||
}
|
||||
|
||||
void attach(@NonNull View view) {
|
||||
@@ -918,14 +984,18 @@ public abstract class Controller {
|
||||
|
||||
for (ControllerHostedRouter childRouter : childRouters) {
|
||||
for (RouterTransaction childTransaction : childRouter.backstack) {
|
||||
if (childTransaction.controller.awaitingParentAttach) {
|
||||
childTransaction.controller.attach(childTransaction.controller.view);
|
||||
if (childTransaction.controller().awaitingParentAttach) {
|
||||
childTransaction.controller().attach(childTransaction.controller().view);
|
||||
}
|
||||
}
|
||||
|
||||
if (childRouter.hasHost()) {
|
||||
childRouter.rebindIfNeeded();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void detach(@NonNull View view, boolean forceViewRefRemoval, boolean blockViewRefRemoval) {
|
||||
void detach(View view, boolean forceViewRefRemoval, boolean blockViewRefRemoval) {
|
||||
if (!attachedToUnownedParent) {
|
||||
for (ControllerHostedRouter router : childRouters) {
|
||||
router.prepareForHostDetach();
|
||||
@@ -935,34 +1005,41 @@ public abstract class Controller {
|
||||
final boolean removeViewRef = !blockViewRefRemoval && (forceViewRefRemoval || retainViewMode == RetainViewMode.RELEASE_DETACH || isBeingDestroyed);
|
||||
|
||||
if (attached) {
|
||||
List<LifecycleListener> listeners = new ArrayList<>(lifecycleListeners);
|
||||
for (LifecycleListener lifecycleListener : listeners) {
|
||||
lifecycleListener.preDetach(this, view);
|
||||
}
|
||||
|
||||
attached = false;
|
||||
|
||||
if (!awaitingParentAttach) {
|
||||
List<LifecycleListener> listeners = new ArrayList<>(lifecycleListeners);
|
||||
for (LifecycleListener lifecycleListener : listeners) {
|
||||
lifecycleListener.preDetach(this, view);
|
||||
}
|
||||
|
||||
attached = false;
|
||||
onDetach(view);
|
||||
}
|
||||
|
||||
if (hasOptionsMenu && !optionsMenuHidden) {
|
||||
router.invalidateOptionsMenu();
|
||||
}
|
||||
if (hasOptionsMenu && !optionsMenuHidden) {
|
||||
router.invalidateOptionsMenu();
|
||||
}
|
||||
|
||||
listeners = new ArrayList<>(lifecycleListeners);
|
||||
for (LifecycleListener lifecycleListener : listeners) {
|
||||
lifecycleListener.postDetach(this, view);
|
||||
listeners = new ArrayList<>(lifecycleListeners);
|
||||
for (LifecycleListener lifecycleListener : listeners) {
|
||||
lifecycleListener.postDetach(this, view);
|
||||
}
|
||||
} else {
|
||||
attached = false;
|
||||
}
|
||||
}
|
||||
|
||||
awaitingParentAttach = false;
|
||||
|
||||
if (removeViewRef) {
|
||||
removeViewReference();
|
||||
removeViewReference(view != null ? view.getContext() : null);
|
||||
}
|
||||
}
|
||||
|
||||
private void removeViewReference() {
|
||||
private void removeViewReference(@Nullable Context context) {
|
||||
if (view != null) {
|
||||
if (context == null) {
|
||||
context = view.getContext();
|
||||
}
|
||||
|
||||
if (!isBeingDestroyed && !hasSavedViewState) {
|
||||
saveViewState(view);
|
||||
}
|
||||
@@ -974,7 +1051,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;
|
||||
|
||||
@@ -994,14 +1075,15 @@ public abstract class Controller {
|
||||
}
|
||||
|
||||
if (isBeingDestroyed) {
|
||||
performDestroy();
|
||||
performDestroy(context);
|
||||
}
|
||||
}
|
||||
|
||||
final View inflate(@NonNull ViewGroup parent) {
|
||||
if (view != null && view.getParent() != null && view.getParent() != parent) {
|
||||
View viewRef = view;
|
||||
detach(view, true, false);
|
||||
removeViewReference();
|
||||
removeViewReference(viewRef.getContext());
|
||||
}
|
||||
|
||||
if (view == null) {
|
||||
@@ -1010,7 +1092,8 @@ public abstract class Controller {
|
||||
lifecycleListener.preCreateView(this);
|
||||
}
|
||||
|
||||
view = onCreateView(LayoutInflater.from(parent.getContext()), parent);
|
||||
Bundle savedViewState = viewState == null ? null : viewState.getBundle(KEY_VIEW_STATE_BUNDLE);
|
||||
view = onCreateView(LayoutInflater.from(parent.getContext()), parent, savedViewState);
|
||||
if (view == parent) {
|
||||
throw new IllegalStateException("Controller's onCreateView method returned the parent ViewGroup. Perhaps you forgot to pass false for LayoutInflater.inflate's attachToRoot parameter?");
|
||||
}
|
||||
@@ -1022,33 +1105,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();
|
||||
}
|
||||
|
||||
@@ -1060,28 +1145,21 @@ 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void performDestroy() {
|
||||
private void performDestroy(@Nullable Context context) {
|
||||
if (context == null) {
|
||||
context = getActivity();
|
||||
}
|
||||
|
||||
if (isContextAvailable) {
|
||||
List<LifecycleListener> listeners = new ArrayList<>(lifecycleListeners);
|
||||
for (LifecycleListener lifecycleListener : listeners) {
|
||||
lifecycleListener.preContextUnavailable(this, getActivity());
|
||||
}
|
||||
|
||||
isContextAvailable = false;
|
||||
onContextUnavailable();
|
||||
|
||||
listeners = new ArrayList<>(lifecycleListeners);
|
||||
for (LifecycleListener lifecycleListener : listeners) {
|
||||
lifecycleListener.postContextUnavailable(this);
|
||||
}
|
||||
onContextUnavailable(context);
|
||||
}
|
||||
|
||||
if (!destroyed) {
|
||||
@@ -1119,7 +1197,7 @@ public abstract class Controller {
|
||||
}
|
||||
|
||||
if (!attached) {
|
||||
removeViewReference();
|
||||
removeViewReference(null);
|
||||
} else if (removeViews) {
|
||||
detach(view, true, false);
|
||||
}
|
||||
@@ -1220,6 +1298,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);
|
||||
}
|
||||
@@ -1295,7 +1374,11 @@ public abstract class Controller {
|
||||
}
|
||||
|
||||
if (!frozen && view != null && viewWasDetached) {
|
||||
View aView = view;
|
||||
detach(view, false, false);
|
||||
if (view == null && aView.getParent() == router.container) {
|
||||
router.container.removeView(aView); // need to remove the view when this controller is a child controller
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1347,46 +1430,84 @@ public abstract class Controller {
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Modes that will influence when the Controller will allow its view to be destroyed */
|
||||
/**
|
||||
* Modes that will influence when the Controller will allow its view to be destroyed
|
||||
*/
|
||||
public enum RetainViewMode {
|
||||
/** The Controller will release its reference to its view as soon as it is detached. */
|
||||
/**
|
||||
* The Controller will release its reference to its view as soon as it is detached.
|
||||
*/
|
||||
RELEASE_DETACH,
|
||||
/** The Controller will retain its reference to its view when detached, but will still release the reference when a config change occurs. */
|
||||
/**
|
||||
* The Controller will retain its reference to its view when detached, but will still release the reference when a config change occurs.
|
||||
*/
|
||||
RETAIN_DETACH
|
||||
}
|
||||
|
||||
/** Allows external classes to listen for lifecycle events in a Controller */
|
||||
/**
|
||||
* Allows external classes to listen for lifecycle events in a Controller
|
||||
*/
|
||||
public static abstract class LifecycleListener {
|
||||
|
||||
public void onChangeStart(@NonNull Controller controller, @NonNull ControllerChangeHandler changeHandler, @NonNull ControllerChangeType changeType) { }
|
||||
public void onChangeEnd(@NonNull Controller controller, @NonNull ControllerChangeHandler changeHandler, @NonNull ControllerChangeType changeType) { }
|
||||
public void onChangeStart(@NonNull Controller controller, @NonNull ControllerChangeHandler changeHandler, @NonNull ControllerChangeType changeType) {
|
||||
}
|
||||
|
||||
public void preCreateView(@NonNull Controller controller) { }
|
||||
public void postCreateView(@NonNull Controller controller, @NonNull View view) { }
|
||||
public void onChangeEnd(@NonNull Controller controller, @NonNull ControllerChangeHandler changeHandler, @NonNull ControllerChangeType changeType) {
|
||||
}
|
||||
|
||||
public void preAttach(@NonNull Controller controller, @NonNull View view) { }
|
||||
public void postAttach(@NonNull Controller controller, @NonNull View view) { }
|
||||
public void preCreateView(@NonNull Controller controller) {
|
||||
}
|
||||
|
||||
public void preDetach(@NonNull Controller controller, @NonNull View view) { }
|
||||
public void postDetach(@NonNull Controller controller, @NonNull View view) { }
|
||||
public void postCreateView(@NonNull Controller controller, @NonNull View view) {
|
||||
}
|
||||
|
||||
public void preDestroyView(@NonNull Controller controller, @NonNull View view) { }
|
||||
public void postDestroyView(@NonNull Controller controller) { }
|
||||
public void preAttach(@NonNull Controller controller, @NonNull View view) {
|
||||
}
|
||||
|
||||
public void preDestroy(@NonNull Controller controller) { }
|
||||
public void postDestroy(@NonNull Controller controller) { }
|
||||
public void postAttach(@NonNull Controller controller, @NonNull View view) {
|
||||
}
|
||||
|
||||
public void preContextAvailable(@NonNull Controller controller) { }
|
||||
public void postContextAvailable(@NonNull Controller controller, @NonNull Context context) { }
|
||||
public void preDetach(@NonNull Controller controller, @NonNull View view) {
|
||||
}
|
||||
|
||||
public void preContextUnavailable(@NonNull Controller controller, @NonNull Context context) { }
|
||||
public void postContextUnavailable(@NonNull Controller controller) { }
|
||||
public void postDetach(@NonNull Controller controller, @NonNull View view) {
|
||||
}
|
||||
|
||||
public void onSaveInstanceState(@NonNull Controller controller, @NonNull Bundle outState) { }
|
||||
public void onRestoreInstanceState(@NonNull Controller controller, @NonNull Bundle savedInstanceState) { }
|
||||
public void preDestroyView(@NonNull Controller controller, @NonNull View view) {
|
||||
}
|
||||
|
||||
public void onSaveViewState(@NonNull Controller controller, @NonNull Bundle outState) { }
|
||||
public void onRestoreViewState(@NonNull Controller controller, @NonNull Bundle savedViewState) { }
|
||||
public void postDestroyView(@NonNull Controller controller) {
|
||||
}
|
||||
|
||||
public void preDestroy(@NonNull Controller controller) {
|
||||
}
|
||||
|
||||
public void postDestroy(@NonNull Controller controller) {
|
||||
}
|
||||
|
||||
public void preContextAvailable(@NonNull Controller controller) {
|
||||
}
|
||||
|
||||
public void postContextAvailable(@NonNull Controller controller, @NonNull Context context) {
|
||||
}
|
||||
|
||||
public void preContextUnavailable(@NonNull Controller controller, @NonNull Context context) {
|
||||
}
|
||||
|
||||
public void postContextUnavailable(@NonNull Controller controller) {
|
||||
}
|
||||
|
||||
public void onSaveInstanceState(@NonNull Controller controller, @NonNull Bundle outState) {
|
||||
}
|
||||
|
||||
public void onRestoreInstanceState(@NonNull Controller controller, @NonNull Bundle savedInstanceState) {
|
||||
}
|
||||
|
||||
public void onSaveViewState(@NonNull Controller controller, @NonNull Bundle outState) {
|
||||
}
|
||||
|
||||
public void onRestoreViewState(@NonNull Controller controller, @NonNull Bundle savedViewState) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
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 android.view.ViewParent;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.bluelinelabs.conductor.changehandler.SimpleSwapChangeHandler;
|
||||
import com.bluelinelabs.conductor.internal.ClassUtils;
|
||||
|
||||
@@ -49,14 +50,16 @@ public abstract class ControllerChangeHandler {
|
||||
*
|
||||
* @param bundle The Bundle into which data should be stored.
|
||||
*/
|
||||
public void saveToBundle(@NonNull Bundle bundle) { }
|
||||
public void saveToBundle(@NonNull Bundle bundle) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Restores data that was saved in the {@link #saveToBundle(Bundle bundle)} method.
|
||||
*
|
||||
* @param bundle The bundle that has data to be restored
|
||||
*/
|
||||
public void restoreFromBundle(@NonNull Bundle bundle) { }
|
||||
public void restoreFromBundle(@NonNull Bundle bundle) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Will be called on change handlers that push a controller if the controller being pushed is
|
||||
@@ -66,13 +69,15 @@ public abstract class ControllerChangeHandler {
|
||||
* @param newTop The Controller that will now be at the top of the backstack or {@code null}
|
||||
* if there will be no new Controller at the top
|
||||
*/
|
||||
public void onAbortPush(@NonNull ControllerChangeHandler newHandler, @Nullable Controller newTop) { }
|
||||
public void onAbortPush(@NonNull ControllerChangeHandler newHandler, @Nullable Controller newTop) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Will be called on change handlers that push a controller if the controller being pushed is
|
||||
* needs to be attached immediately, without any animations or transitions.
|
||||
*/
|
||||
public void completeImmediately() { }
|
||||
public void completeImmediately() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy of this ControllerChangeHandler. This method is internally used by the library, so
|
||||
@@ -220,7 +225,7 @@ public abstract class ControllerChangeHandler {
|
||||
if (handler.forceRemoveViewOnPush && fromView != null) {
|
||||
ViewParent fromParent = fromView.getParent();
|
||||
if (fromParent != null && fromParent instanceof ViewGroup) {
|
||||
((ViewGroup)fromParent).removeView(fromView);
|
||||
((ViewGroup) fromParent).removeView(fromView);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -232,7 +237,8 @@ public abstract class ControllerChangeHandler {
|
||||
}
|
||||
}
|
||||
|
||||
protected void onEnd(){ }
|
||||
protected void onEnd() {
|
||||
}
|
||||
|
||||
public boolean removesFromViewOnPush() {
|
||||
return true;
|
||||
@@ -242,24 +248,6 @@ public abstract class ControllerChangeHandler {
|
||||
forceRemoveViewOnPush = force;
|
||||
}
|
||||
|
||||
static class ChangeTransaction {
|
||||
@Nullable final Controller to;
|
||||
@Nullable final Controller from;
|
||||
final boolean isPush;
|
||||
@Nullable final ViewGroup container;
|
||||
@Nullable final ControllerChangeHandler changeHandler;
|
||||
@NonNull final List<ControllerChangeListener> listeners;
|
||||
|
||||
public ChangeTransaction(@Nullable Controller to, @Nullable Controller from, boolean isPush, @Nullable ViewGroup container, @Nullable ControllerChangeHandler changeHandler, @NonNull List<ControllerChangeListener> listeners) {
|
||||
this.to = to;
|
||||
this.from = from;
|
||||
this.isPush = isPush;
|
||||
this.container = container;
|
||||
this.changeHandler = changeHandler;
|
||||
this.listeners = listeners;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A listener interface useful for allowing external classes to be notified of change events.
|
||||
*/
|
||||
@@ -287,6 +275,29 @@ public abstract class ControllerChangeHandler {
|
||||
void onChangeCompleted(@Nullable Controller to, @Nullable Controller from, boolean isPush, @NonNull ViewGroup container, @NonNull ControllerChangeHandler handler);
|
||||
}
|
||||
|
||||
static class ChangeTransaction {
|
||||
@Nullable
|
||||
final Controller to;
|
||||
@Nullable
|
||||
final Controller from;
|
||||
final boolean isPush;
|
||||
@Nullable
|
||||
final ViewGroup container;
|
||||
@Nullable
|
||||
final ControllerChangeHandler changeHandler;
|
||||
@NonNull
|
||||
final List<ControllerChangeListener> listeners;
|
||||
|
||||
public ChangeTransaction(@Nullable Controller to, @Nullable Controller from, boolean isPush, @Nullable ViewGroup container, @Nullable ControllerChangeHandler changeHandler, @NonNull List<ControllerChangeListener> listeners) {
|
||||
this.to = to;
|
||||
this.from = from;
|
||||
this.isPush = isPush;
|
||||
this.container = container;
|
||||
this.changeHandler = changeHandler;
|
||||
this.listeners = listeners;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A simplified listener for being notified when the change is complete. This MUST be called by any custom
|
||||
* ControllerChangeHandlers in order to ensure that {@link Controller}s will be notified of this change.
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
package com.bluelinelabs.conductor;
|
||||
|
||||
/**
|
||||
* All possible types of {@link Controller} changes to be used in {@link ControllerChangeHandler}s
|
||||
*/
|
||||
public enum ControllerChangeType {
|
||||
/** The Controller is being pushed to the host container */
|
||||
PUSH_ENTER(true, true),
|
||||
|
||||
/** The Controller is being pushed to the backstack as another Controller is pushed to the host container */
|
||||
PUSH_EXIT(true, false),
|
||||
|
||||
/** The Controller is being popped from the backstack and placed in the host container as another Controller is popped */
|
||||
POP_ENTER(false, true),
|
||||
|
||||
/** The Controller is being popped from the host container */
|
||||
POP_EXIT(false, false);
|
||||
|
||||
public boolean isPush;
|
||||
public boolean isEnter;
|
||||
|
||||
ControllerChangeType(boolean isPush, boolean isEnter) {
|
||||
this.isPush = isPush;
|
||||
this.isEnter = isEnter;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.bluelinelabs.conductor
|
||||
|
||||
/**
|
||||
* All possible types of [Controller] changes to be used in [ControllerChangeHandler]s
|
||||
*/
|
||||
enum class ControllerChangeType(@JvmField val isPush: Boolean, @JvmField val isEnter: Boolean) {
|
||||
/** The Controller is being pushed to the host container */
|
||||
PUSH_ENTER(true, true),
|
||||
|
||||
/** The Controller is being pushed to the backstack as another Controller is pushed to the host container */
|
||||
PUSH_EXIT(true, false),
|
||||
|
||||
/** The Controller is being popped from the backstack and placed in the host container as another Controller is popped */
|
||||
POP_ENTER(false, true),
|
||||
|
||||
/** The Controller is being popped from the host container */
|
||||
POP_EXIT(false, false);
|
||||
}
|
||||
@@ -5,10 +5,12 @@ import android.content.Intent;
|
||||
import android.content.IntentSender;
|
||||
import android.content.IntentSender.SendIntentException;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.IdRes;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.bluelinelabs.conductor.ControllerChangeHandler.ControllerChangeListener;
|
||||
import com.bluelinelabs.conductor.internal.TransactionIndexer;
|
||||
@@ -21,33 +23,45 @@ class ControllerHostedRouter extends Router {
|
||||
|
||||
private final String KEY_HOST_ID = "ControllerHostedRouter.hostId";
|
||||
private final String KEY_TAG = "ControllerHostedRouter.tag";
|
||||
private final String KEY_BOUND_TO_CONTAINER = "ControllerHostedRouter.boundToContainer";
|
||||
|
||||
private Controller hostController;
|
||||
|
||||
@IdRes private int hostId;
|
||||
private String tag;
|
||||
private boolean isDetachFrozen;
|
||||
private boolean boundToContainer;
|
||||
|
||||
ControllerHostedRouter() { }
|
||||
|
||||
ControllerHostedRouter(int hostId, @Nullable String tag) {
|
||||
ControllerHostedRouter(int hostId, @Nullable String tag, boolean boundToContainer) {
|
||||
if (!boundToContainer && tag == null) {
|
||||
throw new IllegalStateException("ControllerHostedRouter can't be created without a tag if not bounded to its container");
|
||||
}
|
||||
this.hostId = hostId;
|
||||
this.tag = tag;
|
||||
this.boundToContainer = boundToContainer;
|
||||
}
|
||||
|
||||
final void setHost(@NonNull Controller controller, @NonNull ViewGroup container) {
|
||||
final void setHostController(@NonNull Controller controller) {
|
||||
if (hostController == null) {
|
||||
hostController = controller;
|
||||
}
|
||||
}
|
||||
|
||||
final void setHostContainer(@NonNull Controller controller, @NonNull ViewGroup container) {
|
||||
if (hostController != controller || this.container != container) {
|
||||
removeHost();
|
||||
|
||||
if (container instanceof ControllerChangeListener) {
|
||||
addChangeListener((ControllerChangeListener)container);
|
||||
addChangeListener((ControllerChangeListener) container);
|
||||
}
|
||||
|
||||
hostController = controller;
|
||||
this.container = container;
|
||||
|
||||
for (RouterTransaction transaction : backstack) {
|
||||
transaction.controller.setParentController(controller);
|
||||
transaction.controller().setParentController(controller);
|
||||
}
|
||||
|
||||
watchContainerAttach();
|
||||
@@ -56,7 +70,7 @@ class ControllerHostedRouter extends Router {
|
||||
|
||||
final void removeHost() {
|
||||
if (container != null && container instanceof ControllerChangeListener) {
|
||||
removeChangeListener((ControllerChangeListener)container);
|
||||
removeChangeListener((ControllerChangeListener) container);
|
||||
}
|
||||
|
||||
final List<Controller> controllersToDestroy = new ArrayList<>(destroyingControllers);
|
||||
@@ -66,20 +80,19 @@ class ControllerHostedRouter extends Router {
|
||||
}
|
||||
}
|
||||
for (RouterTransaction transaction : backstack) {
|
||||
if (transaction.controller.getView() != null) {
|
||||
transaction.controller.detach(transaction.controller.getView(), true, false);
|
||||
if (transaction.controller().getView() != null) {
|
||||
transaction.controller().detach(transaction.controller().getView(), true, false);
|
||||
}
|
||||
}
|
||||
|
||||
prepareForContainerRemoval();
|
||||
hostController = null;
|
||||
container = null;
|
||||
}
|
||||
|
||||
final void setDetachFrozen(boolean frozen) {
|
||||
isDetachFrozen = frozen;
|
||||
for (RouterTransaction transaction : backstack) {
|
||||
transaction.controller.setDetachFrozen(frozen);
|
||||
transaction.controller().setDetachFrozen(frozen);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,7 +105,7 @@ class ControllerHostedRouter extends Router {
|
||||
@Override
|
||||
protected void pushToBackstack(@NonNull RouterTransaction entry) {
|
||||
if (isDetachFrozen) {
|
||||
entry.controller.setDetachFrozen(true);
|
||||
entry.controller().setDetachFrozen(true);
|
||||
}
|
||||
super.pushToBackstack(entry);
|
||||
}
|
||||
@@ -101,20 +114,35 @@ class ControllerHostedRouter extends Router {
|
||||
public void setBackstack(@NonNull List<RouterTransaction> newBackstack, @Nullable ControllerChangeHandler changeHandler) {
|
||||
if (isDetachFrozen) {
|
||||
for (RouterTransaction transaction : newBackstack) {
|
||||
transaction.controller.setDetachFrozen(true);
|
||||
transaction.controller().setDetachFrozen(true);
|
||||
}
|
||||
}
|
||||
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();
|
||||
}
|
||||
@@ -184,7 +212,7 @@ class ControllerHostedRouter extends Router {
|
||||
|
||||
@Override
|
||||
boolean hasHost() {
|
||||
return hostController != null;
|
||||
return hostController != null && container != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -192,6 +220,7 @@ class ControllerHostedRouter extends Router {
|
||||
super.saveInstanceState(outState);
|
||||
|
||||
outState.putInt(KEY_HOST_ID, hostId);
|
||||
outState.putBoolean(KEY_BOUND_TO_CONTAINER, boundToContainer);
|
||||
outState.putString(KEY_TAG, tag);
|
||||
}
|
||||
|
||||
@@ -200,22 +229,32 @@ class ControllerHostedRouter extends Router {
|
||||
super.restoreInstanceState(savedInstanceState);
|
||||
|
||||
hostId = savedInstanceState.getInt(KEY_HOST_ID);
|
||||
boundToContainer = savedInstanceState.getBoolean(KEY_BOUND_TO_CONTAINER);
|
||||
tag = savedInstanceState.getString(KEY_TAG);
|
||||
}
|
||||
|
||||
@Override
|
||||
void setControllerRouter(@NonNull Controller controller) {
|
||||
void setRouterOnController(@NonNull Controller controller) {
|
||||
controller.setParentController(hostController);
|
||||
super.setControllerRouter(controller);
|
||||
super.setRouterOnController(controller);
|
||||
}
|
||||
|
||||
int getHostId() {
|
||||
return hostId;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
String getTag() {
|
||||
return tag;
|
||||
boolean matches(int hostId, @Nullable String tag) {
|
||||
if (!boundToContainer && container == null) {
|
||||
if (this.tag == null) {
|
||||
throw new IllegalStateException("Host ID can't be variable with a null tag");
|
||||
}
|
||||
if (this.tag.equals(tag)) {
|
||||
this.hostId = hostId;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return this.hostId == hostId && TextUtils.equals(tag, this.tag);
|
||||
}
|
||||
|
||||
@Override @NonNull
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
package com.bluelinelabs.conductor;
|
||||
|
||||
import android.os.Bundle;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
/**
|
||||
* A simple controller subclass that changes the onCreateView signature to include a saved view state parameter.
|
||||
* This is necessary for some third party libraries like Google Maps, which require passing in a saved state
|
||||
* bundle at the time of creation.
|
||||
*/
|
||||
abstract public class RestoreViewOnCreateController extends Controller {
|
||||
|
||||
/**
|
||||
* Convenience constructor for use when no arguments are needed.
|
||||
*/
|
||||
protected RestoreViewOnCreateController() {
|
||||
super(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor that takes arguments that need to be retained across restarts.
|
||||
*
|
||||
* @param args Any arguments that need to be retained.
|
||||
*/
|
||||
protected RestoreViewOnCreateController(@Nullable Bundle args) {
|
||||
super(args);
|
||||
}
|
||||
|
||||
@Override @NonNull
|
||||
protected final View onCreateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) {
|
||||
return onCreateView(inflater, container, viewState == null ? null : viewState.getBundle(KEY_VIEW_STATE_BUNDLE));
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the controller is ready to display its view. A valid view must be returned. The standard body
|
||||
* for this method will be {@code return inflater.inflate(R.layout.my_layout, container, false);}, plus
|
||||
* any binding and state restoration code.
|
||||
*
|
||||
* @param inflater The LayoutInflater that should be used to inflate views
|
||||
* @param container The parent view that this Controller's view will eventually be attached to.
|
||||
* This Controller's view should NOT be added in this method. It is simply passed in
|
||||
* so that valid LayoutParams can be used during inflation.
|
||||
* @param savedViewState A bundle for the view's state, which would have been created in {@link #onSaveViewState(View, Bundle)},
|
||||
* or {@code null} if no saved state exists.
|
||||
*/
|
||||
@NonNull
|
||||
protected abstract View onCreateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container, @Nullable Bundle savedViewState);
|
||||
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.bluelinelabs.conductor;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentSender;
|
||||
import android.os.Bundle;
|
||||
@@ -10,7 +11,10 @@ import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.bluelinelabs.conductor.Controller.LifecycleListener;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.UiThread;
|
||||
|
||||
import com.bluelinelabs.conductor.ControllerChangeHandler.ChangeTransaction;
|
||||
import com.bluelinelabs.conductor.ControllerChangeHandler.ControllerChangeListener;
|
||||
import com.bluelinelabs.conductor.changehandler.SimpleSwapChangeHandler;
|
||||
@@ -23,10 +27,6 @@ import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.UiThread;
|
||||
|
||||
/**
|
||||
* A Router implements navigation and backstack handling for {@link Controller}s. Router objects are attached
|
||||
* to Activity/containing ViewGroup pairs. Routers do not directly render or push Views to the container ViewGroup,
|
||||
@@ -35,14 +35,14 @@ import androidx.annotation.UiThread;
|
||||
public abstract class Router {
|
||||
|
||||
private static final String KEY_BACKSTACK = "Router.backstack";
|
||||
private static final String KEY_POPS_LAST_VIEW = "Router.popsLastView";
|
||||
private static final String KEY_POP_ROOT_CONTROLLER_MODE = "Router.popRootControllerMode";
|
||||
|
||||
final Backstack backstack = new Backstack();
|
||||
private final List<ControllerChangeListener> changeListeners = new ArrayList<>();
|
||||
private final List<ChangeTransaction> pendingControllerChanges = new ArrayList<>();
|
||||
final List<Controller> destroyingControllers = new ArrayList<>();
|
||||
|
||||
private boolean popsLastView = false;
|
||||
private PopRootControllerMode popRootControllerMode = PopRootControllerMode.POP_ROOT_CONTROLLER_BUT_NOT_VIEW;
|
||||
boolean containerFullyAttached = false;
|
||||
boolean isActivityStopped = false;
|
||||
|
||||
@@ -93,9 +93,9 @@ public abstract class Router {
|
||||
|
||||
if (!backstack.isEmpty()) {
|
||||
//noinspection ConstantConditions
|
||||
if (backstack.peek().controller.handleBack()) {
|
||||
if (backstack.peek().controller().handleBack()) {
|
||||
return true;
|
||||
} else if (popCurrentController()) {
|
||||
} else if ((backstack.getSize() > 1 || popRootControllerMode != PopRootControllerMode.NEVER) && popCurrentController()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -117,7 +117,7 @@ public abstract class Router {
|
||||
if (transaction == null) {
|
||||
throw new IllegalStateException("Trying to pop the current controller when there are none on the backstack.");
|
||||
}
|
||||
return popController(transaction.controller);
|
||||
return popController(transaction.controller());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -131,7 +131,7 @@ public abstract class Router {
|
||||
ThreadUtils.ensureMainThread();
|
||||
|
||||
RouterTransaction topTransaction = backstack.peek();
|
||||
boolean poppingTopController = topTransaction != null && topTransaction.controller == controller;
|
||||
boolean poppingTopController = topTransaction != null && topTransaction.controller() == controller;
|
||||
|
||||
if (poppingTopController) {
|
||||
trackDestroyingController(backstack.pop());
|
||||
@@ -145,14 +145,12 @@ public abstract class Router {
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
RouterTransaction transaction = iterator.next();
|
||||
if (transaction.controller == controller) {
|
||||
if (controller.isAttached()) {
|
||||
trackDestroyingController(transaction);
|
||||
}
|
||||
if (transaction.controller() == controller) {
|
||||
trackDestroyingController(transaction);
|
||||
iterator.remove();
|
||||
removedTransaction = transaction;
|
||||
} else if (removedTransaction != null) {
|
||||
if (needsNextTransactionAttach && !transaction.controller.isAttached()) {
|
||||
if (needsNextTransactionAttach && !transaction.controller().isAttached()) {
|
||||
nextTransaction = transaction;
|
||||
}
|
||||
break;
|
||||
@@ -164,7 +162,7 @@ public abstract class Router {
|
||||
}
|
||||
}
|
||||
|
||||
if (popsLastView) {
|
||||
if (popRootControllerMode == PopRootControllerMode.POP_ROOT_CONTROLLER_AND_VIEW) {
|
||||
return topTransaction != null;
|
||||
} else {
|
||||
return !backstack.isEmpty();
|
||||
@@ -208,7 +206,7 @@ public abstract class Router {
|
||||
final boolean oldHandlerRemovedViews = topTransaction.pushChangeHandler() == null || topTransaction.pushChangeHandler().removesFromViewOnPush();
|
||||
final boolean newHandlerRemovesViews = handler == null || handler.removesFromViewOnPush();
|
||||
if (!oldHandlerRemovedViews && newHandlerRemovesViews) {
|
||||
for (RouterTransaction visibleTransaction : getVisibleTransactions(backstack.iterator())) {
|
||||
for (RouterTransaction visibleTransaction : getVisibleTransactions(backstack.iterator(), true)) {
|
||||
performControllerChange(null, visibleTransaction, true, handler);
|
||||
}
|
||||
}
|
||||
@@ -223,13 +221,14 @@ public abstract class Router {
|
||||
}
|
||||
|
||||
void destroy(boolean popViews) {
|
||||
popsLastView = true;
|
||||
popRootControllerMode = PopRootControllerMode.POP_ROOT_CONTROLLER_AND_VIEW;
|
||||
final List<RouterTransaction> poppedControllers = backstack.popAll();
|
||||
trackDestroyingControllers(poppedControllers);
|
||||
|
||||
RouterTransaction topTransaction = null;
|
||||
if (popViews && poppedControllers.size() > 0) {
|
||||
RouterTransaction topTransaction = poppedControllers.get(0);
|
||||
topTransaction.controller().addLifecycleListener(new LifecycleListener() {
|
||||
topTransaction = poppedControllers.get(0);
|
||||
topTransaction.controller().addLifecycleListener(new Controller.LifecycleListener() {
|
||||
@Override
|
||||
public void onChangeEnd(@NonNull Controller controller, @NonNull ControllerChangeHandler changeHandler, @NonNull ControllerChangeType changeType) {
|
||||
if (changeType == ControllerChangeType.POP_EXIT) {
|
||||
@@ -243,6 +242,16 @@ public abstract class Router {
|
||||
|
||||
performControllerChange(null, topTransaction, false, topTransaction.popChangeHandler());
|
||||
}
|
||||
|
||||
if (poppedControllers.size() > 0) {
|
||||
NoOpControllerChangeHandler changeHandler = new NoOpControllerChangeHandler();
|
||||
for (RouterTransaction routerTransaction : poppedControllers) {
|
||||
if (routerTransaction != topTransaction) {
|
||||
routerTransaction.controller().changeStarted(changeHandler, ControllerChangeType.POP_EXIT);
|
||||
routerTransaction.controller().changeEnded(changeHandler, ControllerChangeType.POP_EXIT);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int getContainerId() {
|
||||
@@ -253,10 +262,24 @@ public abstract class Router {
|
||||
* If set to true, this router will handle back presses by performing a change handler on the last controller and view
|
||||
* in the stack. This defaults to false so that the developer can either finish its containing Activity or otherwise
|
||||
* hide its parent view without any strange artifacting.
|
||||
*
|
||||
* Note: This method has been deprecated and should be replaced with setPopRootControllerMode.
|
||||
*/
|
||||
@NonNull
|
||||
@Deprecated
|
||||
public Router setPopsLastView(boolean popsLastView) {
|
||||
this.popsLastView = popsLastView;
|
||||
this.popRootControllerMode = popsLastView ? PopRootControllerMode.POP_ROOT_CONTROLLER_AND_VIEW : PopRootControllerMode.POP_ROOT_CONTROLLER_BUT_NOT_VIEW;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the method this router will use to handle back presses when there is only one controller left in the backstack.
|
||||
* Defaults to POP_ROOT_CONTROLLER_LEAVING_VIEW so that the developer can either finish its containing Activity or
|
||||
* otherwise hide its parent view without any strange artifacting.
|
||||
*/
|
||||
@NonNull
|
||||
public Router setPopRootControllerMode(@NonNull PopRootControllerMode popRootControllerMode) {
|
||||
this.popRootControllerMode = popRootControllerMode;
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -283,7 +306,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;
|
||||
@@ -349,7 +372,7 @@ public abstract class Router {
|
||||
@Nullable
|
||||
public Controller getControllerWithInstanceId(@NonNull String instanceId) {
|
||||
for (RouterTransaction transaction : backstack) {
|
||||
Controller controllerWithId = transaction.controller.findController(instanceId);
|
||||
Controller controllerWithId = transaction.controller().findController(instanceId);
|
||||
if (controllerWithId != null) {
|
||||
return controllerWithId;
|
||||
}
|
||||
@@ -367,7 +390,7 @@ public abstract class Router {
|
||||
public Controller getControllerWithTag(@NonNull String tag) {
|
||||
for (RouterTransaction transaction : backstack) {
|
||||
if (tag.equals(transaction.tag())) {
|
||||
return transaction.controller;
|
||||
return transaction.controller();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
@@ -378,7 +401,7 @@ public abstract class Router {
|
||||
*/
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public int getBackstackSize() {
|
||||
return backstack.size();
|
||||
return backstack.getSize();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -386,7 +409,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());
|
||||
@@ -407,7 +430,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);
|
||||
@@ -419,7 +442,7 @@ public abstract class Router {
|
||||
for (RouterTransaction oldTransaction : oldTransactions) {
|
||||
boolean contains = false;
|
||||
for (RouterTransaction newTransaction : newBackstack) {
|
||||
if (oldTransaction.controller == newTransaction.controller) {
|
||||
if (oldTransaction.controller() == newTransaction.controller()) {
|
||||
contains = true;
|
||||
break;
|
||||
}
|
||||
@@ -427,7 +450,7 @@ public abstract class Router {
|
||||
|
||||
if (!contains) {
|
||||
// Inform the controller that it will be destroyed soon
|
||||
oldTransaction.controller.isBeingDestroyed = true;
|
||||
oldTransaction.controller().isBeingDestroyed = true;
|
||||
transactionsToBeRemoved.add(oldTransaction);
|
||||
}
|
||||
}
|
||||
@@ -437,13 +460,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);
|
||||
@@ -452,10 +475,10 @@ public abstract class Router {
|
||||
RouterTransaction newRootTransaction = newVisibleTransactions.get(0);
|
||||
|
||||
// Replace the old root with the new one
|
||||
if (oldRootTransaction == null || oldRootTransaction.controller != newRootTransaction.controller) {
|
||||
if (oldRootTransaction == null || oldRootTransaction.controller() != newRootTransaction.controller()) {
|
||||
// Ensure the existing root controller is fully pushed to the view hierarchy
|
||||
if (oldRootTransaction != null) {
|
||||
ControllerChangeHandler.completeHandlerImmediately(oldRootTransaction.controller.getInstanceId());
|
||||
ControllerChangeHandler.completeHandlerImmediately(oldRootTransaction.controller().getInstanceId());
|
||||
}
|
||||
performControllerChange(newRootTransaction, oldRootTransaction, newRootRequiresPush, changeHandler);
|
||||
}
|
||||
@@ -466,8 +489,11 @@ public abstract class Router {
|
||||
if (!newVisibleTransactions.contains(transaction)) {
|
||||
ControllerChangeHandler localHandler = changeHandler != null ? changeHandler.copy() : new SimpleSwapChangeHandler();
|
||||
localHandler.setForceRemoveViewOnPush(true);
|
||||
ControllerChangeHandler.completeHandlerImmediately(transaction.controller.getInstanceId());
|
||||
performControllerChange(null, transaction, newRootRequiresPush, localHandler);
|
||||
ControllerChangeHandler.completeHandlerImmediately(transaction.controller().getInstanceId());
|
||||
|
||||
if (transaction.controller().view != null) {
|
||||
performControllerChange(null, transaction, newRootRequiresPush, localHandler);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -485,7 +511,7 @@ public abstract class Router {
|
||||
for (int i = oldVisibleTransactions.size() - 1; i >= 0; i--) {
|
||||
RouterTransaction transaction = oldVisibleTransactions.get(i);
|
||||
ControllerChangeHandler localHandler = changeHandler != null ? changeHandler.copy() : new SimpleSwapChangeHandler();
|
||||
ControllerChangeHandler.completeHandlerImmediately(transaction.controller.getInstanceId());
|
||||
ControllerChangeHandler.completeHandlerImmediately(transaction.controller().getInstanceId());
|
||||
performControllerChange(null, transaction, false, localHandler);
|
||||
}
|
||||
}
|
||||
@@ -498,13 +524,13 @@ public abstract class Router {
|
||||
// Still need to ensure the controller isn't queued up to be removed later on.
|
||||
boolean willBeRemoved = false;
|
||||
for (ChangeTransaction pendingTransaction : pendingControllerChanges) {
|
||||
if (pendingTransaction.from == removedTransaction.controller) {
|
||||
if (pendingTransaction.from == removedTransaction.controller()) {
|
||||
willBeRemoved = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!willBeRemoved) {
|
||||
removedTransaction.controller.destroy();
|
||||
removedTransaction.controller().destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -545,14 +571,13 @@ public abstract class Router {
|
||||
public void rebindIfNeeded() {
|
||||
ThreadUtils.ensureMainThread();
|
||||
|
||||
Iterator<RouterTransaction> backstackIterator = backstack.reverseIterator();
|
||||
while (backstackIterator.hasNext()) {
|
||||
RouterTransaction transaction = backstackIterator.next();
|
||||
|
||||
if (transaction.controller.getNeedsAttach()) {
|
||||
// Not directly using the iterator in order to prevent ConcurrentModificationExceptions if controllers pop
|
||||
// themselves on re-attach.
|
||||
for (RouterTransaction transaction : getTransactions()) {
|
||||
if (transaction.controller().getNeedsAttach()) {
|
||||
performControllerChange(transaction, null, true, new SimpleSwapChangeHandler(false));
|
||||
} else {
|
||||
setControllerRouter(transaction.controller);
|
||||
setRouterOnController(transaction.controller());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -568,9 +593,9 @@ public abstract class Router {
|
||||
isActivityStopped = false;
|
||||
|
||||
for (RouterTransaction transaction : backstack) {
|
||||
transaction.controller.activityStarted(activity);
|
||||
transaction.controller().activityStarted(activity);
|
||||
|
||||
for (Router childRouter : transaction.controller.getChildRouters()) {
|
||||
for (Router childRouter : transaction.controller().getChildRouters()) {
|
||||
childRouter.onActivityStarted(activity);
|
||||
}
|
||||
}
|
||||
@@ -578,9 +603,9 @@ public abstract class Router {
|
||||
|
||||
public final void onActivityResumed(@NonNull Activity activity) {
|
||||
for (RouterTransaction transaction : backstack) {
|
||||
transaction.controller.activityResumed(activity);
|
||||
transaction.controller().activityResumed(activity);
|
||||
|
||||
for (Router childRouter : transaction.controller.getChildRouters()) {
|
||||
for (Router childRouter : transaction.controller().getChildRouters()) {
|
||||
childRouter.onActivityResumed(activity);
|
||||
}
|
||||
}
|
||||
@@ -588,9 +613,9 @@ public abstract class Router {
|
||||
|
||||
public final void onActivityPaused(@NonNull Activity activity) {
|
||||
for (RouterTransaction transaction : backstack) {
|
||||
transaction.controller.activityPaused(activity);
|
||||
transaction.controller().activityPaused(activity);
|
||||
|
||||
for (Router childRouter : transaction.controller.getChildRouters()) {
|
||||
for (Router childRouter : transaction.controller().getChildRouters()) {
|
||||
childRouter.onActivityPaused(activity);
|
||||
}
|
||||
}
|
||||
@@ -598,9 +623,9 @@ public abstract class Router {
|
||||
|
||||
public final void onActivityStopped(@NonNull Activity activity) {
|
||||
for (RouterTransaction transaction : backstack) {
|
||||
transaction.controller.activityStopped(activity);
|
||||
transaction.controller().activityStopped(activity);
|
||||
|
||||
for (Router childRouter : transaction.controller.getChildRouters()) {
|
||||
for (Router childRouter : transaction.controller().getChildRouters()) {
|
||||
childRouter.onActivityStopped(activity);
|
||||
}
|
||||
}
|
||||
@@ -608,15 +633,15 @@ public abstract class Router {
|
||||
isActivityStopped = true;
|
||||
}
|
||||
|
||||
public void onActivityDestroyed(@NonNull Activity activity) {
|
||||
public void onActivityDestroyed(@NonNull Activity activity, boolean isConfigurationChange) {
|
||||
prepareForContainerRemoval();
|
||||
changeListeners.clear();
|
||||
|
||||
for (RouterTransaction transaction : backstack) {
|
||||
transaction.controller.activityDestroyed(activity);
|
||||
transaction.controller().activityDestroyed(activity);
|
||||
|
||||
for (Router childRouter : transaction.controller.getChildRouters()) {
|
||||
childRouter.onActivityDestroyed(activity);
|
||||
for (Router childRouter : transaction.controller().getChildRouters()) {
|
||||
childRouter.onActivityDestroyed(activity, isConfigurationChange);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -625,7 +650,7 @@ public abstract class Router {
|
||||
controller.activityDestroyed(activity);
|
||||
|
||||
for (Router childRouter : controller.getChildRouters()) {
|
||||
childRouter.onActivityDestroyed(activity);
|
||||
childRouter.onActivityDestroyed(activity, isConfigurationChange);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -633,11 +658,14 @@ public abstract class Router {
|
||||
}
|
||||
|
||||
public void prepareForHostDetach() {
|
||||
pendingControllerChanges.clear(); // rely on backstack based restoration in rebindIfNeeded
|
||||
|
||||
for (RouterTransaction transaction : backstack) {
|
||||
if (ControllerChangeHandler.completeHandlerImmediately(transaction.controller.getInstanceId())) {
|
||||
transaction.controller.setNeedsAttach(true);
|
||||
if (ControllerChangeHandler.completeHandlerImmediately(transaction.controller().getInstanceId())) {
|
||||
transaction.controller().setNeedsAttach(true);
|
||||
}
|
||||
transaction.controller.prepareForHostDetach();
|
||||
|
||||
transaction.controller().prepareForHostDetach();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -646,26 +674,26 @@ public abstract class Router {
|
||||
backstack.saveInstanceState(backstackState);
|
||||
|
||||
outState.putParcelable(KEY_BACKSTACK, backstackState);
|
||||
outState.putBoolean(KEY_POPS_LAST_VIEW, popsLastView);
|
||||
outState.putInt(KEY_POP_ROOT_CONTROLLER_MODE, popRootControllerMode.ordinal());
|
||||
}
|
||||
|
||||
public void restoreInstanceState(@NonNull Bundle savedInstanceState) {
|
||||
Bundle backstackBundle = savedInstanceState.getParcelable(KEY_BACKSTACK);
|
||||
//noinspection ConstantConditions
|
||||
backstack.restoreInstanceState(backstackBundle);
|
||||
popsLastView = savedInstanceState.getBoolean(KEY_POPS_LAST_VIEW);
|
||||
popRootControllerMode = PopRootControllerMode.values()[savedInstanceState.getInt(KEY_POP_ROOT_CONTROLLER_MODE)];
|
||||
|
||||
Iterator<RouterTransaction> backstackIterator = backstack.reverseIterator();
|
||||
while (backstackIterator.hasNext()) {
|
||||
setControllerRouter(backstackIterator.next().controller);
|
||||
setRouterOnController(backstackIterator.next().controller());
|
||||
}
|
||||
}
|
||||
|
||||
public final void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
|
||||
for (RouterTransaction transaction : backstack) {
|
||||
transaction.controller.createOptionsMenu(menu, inflater);
|
||||
transaction.controller().createOptionsMenu(menu, inflater);
|
||||
|
||||
for (Router childRouter : transaction.controller.getChildRouters()) {
|
||||
for (Router childRouter : transaction.controller().getChildRouters()) {
|
||||
childRouter.onCreateOptionsMenu(menu, inflater);
|
||||
}
|
||||
}
|
||||
@@ -673,9 +701,9 @@ public abstract class Router {
|
||||
|
||||
public final void onPrepareOptionsMenu(@NonNull Menu menu) {
|
||||
for (RouterTransaction transaction : backstack) {
|
||||
transaction.controller.prepareOptionsMenu(menu);
|
||||
transaction.controller().prepareOptionsMenu(menu);
|
||||
|
||||
for (Router childRouter : transaction.controller.getChildRouters()) {
|
||||
for (Router childRouter : transaction.controller().getChildRouters()) {
|
||||
childRouter.onPrepareOptionsMenu(menu);
|
||||
}
|
||||
}
|
||||
@@ -683,11 +711,11 @@ public abstract class Router {
|
||||
|
||||
public final boolean onOptionsItemSelected(@NonNull MenuItem item) {
|
||||
for (RouterTransaction transaction : backstack) {
|
||||
if (transaction.controller.optionsItemSelected(item)) {
|
||||
if (transaction.controller().optionsItemSelected(item)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (Router childRouter : transaction.controller.getChildRouters()) {
|
||||
for (Router childRouter : transaction.controller().getChildRouters()) {
|
||||
if (childRouter.onOptionsItemSelected(item)) {
|
||||
return true;
|
||||
}
|
||||
@@ -697,7 +725,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<>();
|
||||
@@ -724,6 +752,7 @@ public abstract class Router {
|
||||
@Override
|
||||
public void run() {
|
||||
containerFullyAttached = true;
|
||||
performPendingControllerChanges();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -738,33 +767,54 @@ public abstract class Router {
|
||||
|
||||
void onContextAvailable() {
|
||||
for (RouterTransaction transaction : backstack) {
|
||||
transaction.controller.onContextAvailable();
|
||||
transaction.controller().onContextAvailable();
|
||||
}
|
||||
}
|
||||
|
||||
void onContextUnavailable(@NonNull Context context) {
|
||||
for (RouterTransaction transaction : backstack) {
|
||||
transaction.controller().onContextUnavailable(context);
|
||||
}
|
||||
for (Controller controller : destroyingControllers) {
|
||||
controller.onContextUnavailable(context);
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
final List<Controller> getControllers() {
|
||||
List<Controller> controllers = new ArrayList<>(backstack.size());
|
||||
List<Controller> controllers = new ArrayList<>(backstack.getSize());
|
||||
|
||||
Iterator<RouterTransaction> backstackIterator = backstack.reverseIterator();
|
||||
while (backstackIterator.hasNext()) {
|
||||
controllers.add(backstackIterator.next().controller);
|
||||
controllers.add(backstackIterator.next().controller());
|
||||
}
|
||||
|
||||
return controllers;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
final List<RouterTransaction> getTransactions() {
|
||||
List<RouterTransaction> transactions = new ArrayList<>(backstack.getSize());
|
||||
|
||||
Iterator<RouterTransaction> backstackIterator = backstack.reverseIterator();
|
||||
while (backstackIterator.hasNext()) {
|
||||
transactions.add(backstackIterator.next());
|
||||
}
|
||||
|
||||
return transactions;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public final Boolean handleRequestedPermission(@NonNull String permission) {
|
||||
for (RouterTransaction transaction : backstack) {
|
||||
if (transaction.controller.didRequestPermission(permission)) {
|
||||
return transaction.controller.shouldShowRequestPermissionRationale(permission);
|
||||
if (transaction.controller().didRequestPermission(permission)) {
|
||||
return transaction.controller().shouldShowRequestPermissionRationale(permission);
|
||||
}
|
||||
}
|
||||
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();
|
||||
}
|
||||
@@ -782,25 +832,33 @@ public abstract class Router {
|
||||
performControllerChange(to, from, isPush, changeHandler);
|
||||
}
|
||||
|
||||
void performControllerChange(@Nullable RouterTransaction to, @Nullable RouterTransaction from, boolean isPush, @Nullable ControllerChangeHandler changeHandler) {
|
||||
Controller toController = to != null ? to.controller : null;
|
||||
Controller fromController = from != null ? from.controller : null;
|
||||
private void performControllerChange(@Nullable RouterTransaction to, @Nullable RouterTransaction from, boolean isPush, @Nullable ControllerChangeHandler changeHandler) {
|
||||
Controller toController = to != null ? to.controller() : null;
|
||||
Controller fromController = from != null ? from.controller() : null;
|
||||
boolean forceDetachDestroy = false;
|
||||
|
||||
if (to != null) {
|
||||
to.ensureValidIndex(getTransactionIndexer());
|
||||
setControllerRouter(toController);
|
||||
} else if (backstack.size() == 0 && !popsLastView) {
|
||||
setRouterOnController(toController);
|
||||
} else if (backstack.getSize() == 0 && popRootControllerMode == PopRootControllerMode.POP_ROOT_CONTROLLER_BUT_NOT_VIEW) {
|
||||
// We're emptying out the backstack. Views get weird if you transition them out, so just no-op it. The host
|
||||
// Activity or controller should be handling this by finishing or at least hiding this view.
|
||||
changeHandler = new NoOpControllerChangeHandler();
|
||||
forceDetachDestroy = true;
|
||||
} else if (!isPush && fromController != null && !fromController.isAttached()) {
|
||||
// We're popping fromController from the middle of the backstack,
|
||||
// need to do it immediately and destroy the controller
|
||||
forceDetachDestroy = true;
|
||||
}
|
||||
|
||||
performControllerChange(toController, fromController, isPush, changeHandler);
|
||||
|
||||
if (forceDetachDestroy && fromController != null && fromController.getView() != null) {
|
||||
fromController.detach(fromController.getView(), true, false);
|
||||
if (forceDetachDestroy && fromController != null) {
|
||||
if (fromController.getView() != null) {
|
||||
fromController.detach(fromController.getView(), true, false);
|
||||
} else {
|
||||
fromController.destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -814,18 +872,21 @@ public abstract class Router {
|
||||
if (pendingControllerChanges.size() > 0) {
|
||||
// If we already have changes queued up (awaiting full container attach), queue this one up as well so they don't happen
|
||||
// out of order.
|
||||
if (to != null) {
|
||||
to.setNeedsAttach(true);
|
||||
}
|
||||
pendingControllerChanges.add(transaction);
|
||||
} else if (from != null && (changeHandler == null || changeHandler.removesFromViewOnPush()) && !containerFullyAttached) {
|
||||
// If the change handler will remove the from view, we have to make sure the container is fully attached first so we avoid NPEs
|
||||
// within ViewGroup (details on issue #287). Post this to the container to ensure the attach is complete before we try to remove
|
||||
// anything.
|
||||
if (to != null) {
|
||||
to.setNeedsAttach(true);
|
||||
}
|
||||
pendingControllerChanges.add(transaction);
|
||||
container.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
performPendingControllerChanges();
|
||||
}
|
||||
});
|
||||
if (container != null) {
|
||||
container.post(this::performPendingControllerChanges);
|
||||
}
|
||||
} else {
|
||||
ControllerChangeHandler.executeChange(transaction);
|
||||
}
|
||||
@@ -841,17 +902,17 @@ public abstract class Router {
|
||||
}
|
||||
|
||||
protected void pushToBackstack(@NonNull RouterTransaction entry) {
|
||||
if (backstack.contains(entry.controller)) {
|
||||
if (backstack.contains(entry.controller())) {
|
||||
throw new IllegalStateException("Trying to push a controller that already exists on the backstack.");
|
||||
}
|
||||
backstack.push(entry);
|
||||
}
|
||||
|
||||
private void trackDestroyingController(@NonNull RouterTransaction transaction) {
|
||||
if (!transaction.controller.isDestroyed()) {
|
||||
destroyingControllers.add(transaction.controller);
|
||||
if (!transaction.controller().isDestroyed()) {
|
||||
destroyingControllers.add(transaction.controller());
|
||||
|
||||
transaction.controller.addLifecycleListener(new LifecycleListener() {
|
||||
transaction.controller().addLifecycleListener(new Controller.LifecycleListener() {
|
||||
@Override
|
||||
public void postDestroy(@NonNull Controller controller) {
|
||||
destroyingControllers.remove(controller);
|
||||
@@ -869,9 +930,9 @@ public abstract class Router {
|
||||
private void removeAllExceptVisibleAndUnowned() {
|
||||
List<View> views = new ArrayList<>();
|
||||
|
||||
for (RouterTransaction transaction : getVisibleTransactions(backstack.iterator())) {
|
||||
if (transaction.controller.getView() != null) {
|
||||
views.add(transaction.controller.getView());
|
||||
for (RouterTransaction transaction : getVisibleTransactions(backstack.iterator(), false)) {
|
||||
if (transaction.controller().getView() != null) {
|
||||
views.add(transaction.controller().getView());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -896,21 +957,21 @@ public abstract class Router {
|
||||
List<Integer> indices = new ArrayList<>(backstack.size());
|
||||
for (RouterTransaction transaction : backstack) {
|
||||
transaction.ensureValidIndex(getTransactionIndexer());
|
||||
indices.add(transaction.transactionIndex);
|
||||
indices.add(transaction.getTransactionIndex());
|
||||
}
|
||||
|
||||
Collections.sort(indices);
|
||||
|
||||
for (int i = 0; i < backstack.size(); i++) {
|
||||
backstack.get(i).transactionIndex = indices.get(i);
|
||||
backstack.get(i).setTransactionIndex(indices.get(i));
|
||||
}
|
||||
}
|
||||
|
||||
private void ensureNoDuplicateControllers(List<RouterTransaction> backstack) {
|
||||
for (int i = 0; i < backstack.size(); i++) {
|
||||
Controller controller = backstack.get(i).controller;
|
||||
Controller controller = backstack.get(i).controller();
|
||||
for (int j = i + 1; j < backstack.size(); j++) {
|
||||
if (backstack.get(j).controller == controller) {
|
||||
if (backstack.get(j).controller() == controller) {
|
||||
throw new IllegalStateException("Trying to push the same controller to the backstack more than once.");
|
||||
}
|
||||
}
|
||||
@@ -929,14 +990,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;
|
||||
}
|
||||
}
|
||||
@@ -959,7 +1026,7 @@ public abstract class Router {
|
||||
return true;
|
||||
}
|
||||
|
||||
void setControllerRouter(@NonNull Controller controller) {
|
||||
void setRouterOnController(@NonNull Controller controller) {
|
||||
controller.setRouter(this);
|
||||
controller.onContextAvailable();
|
||||
}
|
||||
@@ -978,4 +1045,25 @@ public abstract class Router {
|
||||
@NonNull abstract Router getRootRouter();
|
||||
@NonNull abstract TransactionIndexer getTransactionIndexer();
|
||||
|
||||
/**
|
||||
* Defines the way a Router will handle back button or pop events when there is only one controller
|
||||
* left in the backstack.
|
||||
*/
|
||||
public enum PopRootControllerMode {
|
||||
/**
|
||||
* The Router will not pop the final controller left on the backstack when the back button is pressed
|
||||
* or when pop events are called. This mode should generally be used for Activity-hosted routers.
|
||||
*/
|
||||
NEVER,
|
||||
/**
|
||||
* The Router will pop the final controller, but will leave its view in the hierarchy. This is useful
|
||||
* when the developer wishes to allow its containing Activity to finish or otherwise hide its parent
|
||||
* view without any strange artifacting.
|
||||
*/
|
||||
POP_ROOT_CONTROLLER_BUT_NOT_VIEW,
|
||||
/**
|
||||
* The Router will pop both the final controller as well as its view.
|
||||
*/
|
||||
POP_ROOT_CONTROLLER_AND_VIEW
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,140 +0,0 @@
|
||||
package com.bluelinelabs.conductor;
|
||||
|
||||
import android.os.Bundle;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.bluelinelabs.conductor.internal.TransactionIndexer;
|
||||
|
||||
/**
|
||||
* Metadata used for adding {@link Controller}s to a {@link Router}.
|
||||
*/
|
||||
public class RouterTransaction {
|
||||
|
||||
private static int INVALID_INDEX = -1;
|
||||
|
||||
private static final String KEY_VIEW_CONTROLLER_BUNDLE = "RouterTransaction.controller.bundle";
|
||||
private static final String KEY_PUSH_TRANSITION = "RouterTransaction.pushControllerChangeHandler";
|
||||
private static final String KEY_POP_TRANSITION = "RouterTransaction.popControllerChangeHandler";
|
||||
private static final String KEY_TAG = "RouterTransaction.tag";
|
||||
private static final String KEY_INDEX = "RouterTransaction.transactionIndex";
|
||||
private static final String KEY_ATTACHED_TO_ROUTER = "RouterTransaction.attachedToRouter";
|
||||
|
||||
@NonNull final Controller controller;
|
||||
private String tag;
|
||||
|
||||
private ControllerChangeHandler pushControllerChangeHandler;
|
||||
private ControllerChangeHandler popControllerChangeHandler;
|
||||
private boolean attachedToRouter;
|
||||
int transactionIndex = INVALID_INDEX;
|
||||
|
||||
@NonNull
|
||||
public static RouterTransaction with(@NonNull Controller controller) {
|
||||
return new RouterTransaction(controller);
|
||||
}
|
||||
|
||||
private RouterTransaction(@NonNull Controller controller) {
|
||||
this.controller = controller;
|
||||
}
|
||||
|
||||
RouterTransaction(@NonNull Bundle bundle) {
|
||||
controller = Controller.newInstance(bundle.getBundle(KEY_VIEW_CONTROLLER_BUNDLE));
|
||||
pushControllerChangeHandler = ControllerChangeHandler.fromBundle(bundle.getBundle(KEY_PUSH_TRANSITION));
|
||||
popControllerChangeHandler = ControllerChangeHandler.fromBundle(bundle.getBundle(KEY_POP_TRANSITION));
|
||||
tag = bundle.getString(KEY_TAG);
|
||||
transactionIndex = bundle.getInt(KEY_INDEX);
|
||||
attachedToRouter = bundle.getBoolean(KEY_ATTACHED_TO_ROUTER);
|
||||
}
|
||||
|
||||
void onAttachedToRouter() {
|
||||
attachedToRouter = true;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Controller controller() {
|
||||
return controller;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String tag() {
|
||||
return tag;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public RouterTransaction tag(@Nullable String tag) {
|
||||
if (!attachedToRouter) {
|
||||
this.tag = tag;
|
||||
return this;
|
||||
} else {
|
||||
throw new RuntimeException(getClass().getSimpleName() + "s can not be modified after being added to a Router.");
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public ControllerChangeHandler pushChangeHandler() {
|
||||
ControllerChangeHandler handler = controller.getOverriddenPushHandler();
|
||||
if (handler == null) {
|
||||
handler = pushControllerChangeHandler;
|
||||
}
|
||||
return handler;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public RouterTransaction pushChangeHandler(@Nullable ControllerChangeHandler handler) {
|
||||
if (!attachedToRouter) {
|
||||
pushControllerChangeHandler = handler;
|
||||
return this;
|
||||
} else {
|
||||
throw new RuntimeException(getClass().getSimpleName() + "s can not be modified after being added to a Router.");
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public ControllerChangeHandler popChangeHandler() {
|
||||
ControllerChangeHandler handler = controller.getOverriddenPopHandler();
|
||||
if (handler == null) {
|
||||
handler = popControllerChangeHandler;
|
||||
}
|
||||
return handler;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public RouterTransaction popChangeHandler(@Nullable ControllerChangeHandler handler) {
|
||||
if (!attachedToRouter) {
|
||||
popControllerChangeHandler = handler;
|
||||
return this;
|
||||
} else {
|
||||
throw new RuntimeException(getClass().getSimpleName() + "s can not be modified after being added to a Router.");
|
||||
}
|
||||
}
|
||||
|
||||
void ensureValidIndex(@NonNull TransactionIndexer indexer) {
|
||||
if (transactionIndex == INVALID_INDEX) {
|
||||
transactionIndex = indexer.nextIndex();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to serialize this transaction into a Bundle
|
||||
*/
|
||||
@NonNull
|
||||
public Bundle saveInstanceState() {
|
||||
Bundle bundle = new Bundle();
|
||||
|
||||
bundle.putBundle(KEY_VIEW_CONTROLLER_BUNDLE, controller.saveInstanceState());
|
||||
|
||||
if (pushControllerChangeHandler != null) {
|
||||
bundle.putBundle(KEY_PUSH_TRANSITION, pushControllerChangeHandler.toBundle());
|
||||
}
|
||||
if (popControllerChangeHandler != null) {
|
||||
bundle.putBundle(KEY_POP_TRANSITION, popControllerChangeHandler.toBundle());
|
||||
}
|
||||
|
||||
bundle.putString(KEY_TAG, tag);
|
||||
bundle.putInt(KEY_INDEX, transactionIndex);
|
||||
bundle.putBoolean(KEY_ATTACHED_TO_ROUTER, attachedToRouter);
|
||||
|
||||
return bundle;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
package com.bluelinelabs.conductor
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.annotation.RestrictTo
|
||||
import androidx.annotation.RestrictTo.Scope.LIBRARY
|
||||
import com.bluelinelabs.conductor.internal.TransactionIndexer
|
||||
|
||||
private const val INVALID_INDEX = -1
|
||||
private const val KEY_VIEW_CONTROLLER_BUNDLE = "RouterTransaction.controller.bundle"
|
||||
private const val KEY_PUSH_TRANSITION = "RouterTransaction.pushControllerChangeHandler"
|
||||
private const val KEY_POP_TRANSITION = "RouterTransaction.popControllerChangeHandler"
|
||||
private const val KEY_TAG = "RouterTransaction.tag"
|
||||
private const val KEY_INDEX = "RouterTransaction.transactionIndex"
|
||||
private const val KEY_ATTACHED_TO_ROUTER = "RouterTransaction.attachedToRouter"
|
||||
|
||||
/**
|
||||
* Metadata used for adding [Controller]s to a [Router].
|
||||
*/
|
||||
class RouterTransaction
|
||||
private constructor(
|
||||
@get:JvmName("controller")
|
||||
val controller: Controller,
|
||||
private var tag: String? = null,
|
||||
private var pushControllerChangeHandler: ControllerChangeHandler? = null,
|
||||
private var popControllerChangeHandler: ControllerChangeHandler? = null,
|
||||
private var attachedToRouter: Boolean = false,
|
||||
@get:RestrictTo(LIBRARY)
|
||||
@set:RestrictTo(LIBRARY)
|
||||
var transactionIndex: Int = INVALID_INDEX
|
||||
) {
|
||||
|
||||
|
||||
@RestrictTo(LIBRARY)
|
||||
internal constructor(bundle: Bundle) : this(
|
||||
controller = Controller.newInstance(bundle.getBundle(KEY_VIEW_CONTROLLER_BUNDLE)!!),
|
||||
pushControllerChangeHandler = ControllerChangeHandler.fromBundle(
|
||||
bundle.getBundle(
|
||||
KEY_PUSH_TRANSITION
|
||||
)
|
||||
),
|
||||
popControllerChangeHandler = ControllerChangeHandler.fromBundle(
|
||||
bundle.getBundle(
|
||||
KEY_POP_TRANSITION
|
||||
)
|
||||
),
|
||||
tag = bundle.getString(KEY_TAG),
|
||||
transactionIndex = bundle.getInt(KEY_INDEX),
|
||||
attachedToRouter = bundle.getBoolean(KEY_ATTACHED_TO_ROUTER)
|
||||
)
|
||||
|
||||
fun onAttachedToRouter() {
|
||||
attachedToRouter = true
|
||||
}
|
||||
|
||||
fun tag(): String? = tag
|
||||
|
||||
fun tag(tag: String?): RouterTransaction {
|
||||
return if (!attachedToRouter) {
|
||||
this.tag = tag
|
||||
this
|
||||
} else {
|
||||
throw RuntimeException(javaClass.simpleName + "s can not be modified after being added to a Router.")
|
||||
}
|
||||
}
|
||||
|
||||
fun pushChangeHandler(): ControllerChangeHandler? {
|
||||
return controller.overriddenPushHandler ?: pushControllerChangeHandler
|
||||
}
|
||||
|
||||
fun pushChangeHandler(handler: ControllerChangeHandler?): RouterTransaction {
|
||||
return if (!attachedToRouter) {
|
||||
pushControllerChangeHandler = handler
|
||||
this
|
||||
} else {
|
||||
throw RuntimeException("${javaClass.simpleName}s can not be modified after being added to a Router.")
|
||||
}
|
||||
}
|
||||
|
||||
fun popChangeHandler(): ControllerChangeHandler? {
|
||||
return controller.overriddenPopHandler ?: popControllerChangeHandler
|
||||
}
|
||||
|
||||
fun popChangeHandler(handler: ControllerChangeHandler?): RouterTransaction {
|
||||
return if (!attachedToRouter) {
|
||||
popControllerChangeHandler = handler
|
||||
this
|
||||
} else {
|
||||
throw RuntimeException("${javaClass.simpleName}s can not be modified after being added to a Router.")
|
||||
}
|
||||
}
|
||||
|
||||
fun ensureValidIndex(indexer: TransactionIndexer) {
|
||||
if (transactionIndex == INVALID_INDEX) {
|
||||
transactionIndex = indexer.nextIndex()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to serialize this transaction into a Bundle
|
||||
*/
|
||||
fun saveInstanceState(): Bundle = Bundle().apply {
|
||||
putBundle(KEY_VIEW_CONTROLLER_BUNDLE, controller.saveInstanceState())
|
||||
pushControllerChangeHandler?.let { putBundle(KEY_PUSH_TRANSITION, it.toBundle()) }
|
||||
popControllerChangeHandler?.let { putBundle(KEY_POP_TRANSITION, it.toBundle()) }
|
||||
putString(KEY_TAG, tag)
|
||||
putInt(KEY_INDEX, transactionIndex)
|
||||
putBoolean(KEY_ATTACHED_TO_ROUTER, attachedToRouter)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
@JvmStatic
|
||||
fun with(controller: Controller): RouterTransaction = RouterTransaction(controller)
|
||||
}
|
||||
}
|
||||
|
||||
fun Controller.asTransaction(
|
||||
popChangeHandler: ControllerChangeHandler? = null,
|
||||
pushChangeHandler: ControllerChangeHandler? = null
|
||||
): RouterTransaction {
|
||||
return RouterTransaction.with(this)
|
||||
.pushChangeHandler(pushChangeHandler)
|
||||
.popChangeHandler(popChangeHandler)
|
||||
}
|
||||
-658
@@ -1,658 +0,0 @@
|
||||
package com.bluelinelabs.conductor.changehandler;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.SharedElementCallback;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Build;
|
||||
import android.transition.Transition;
|
||||
import android.transition.Transition.TransitionListener;
|
||||
import android.transition.TransitionSet;
|
||||
import android.util.ArrayMap;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewTreeObserver;
|
||||
import android.view.ViewTreeObserver.OnPreDrawListener;
|
||||
|
||||
import com.bluelinelabs.conductor.Controller;
|
||||
import com.bluelinelabs.conductor.ControllerChangeHandler;
|
||||
import com.bluelinelabs.conductor.internal.LegacyTransitionUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* A TransitionChangeHandler that facilitates using different Transitions for the entering view, the exiting view,
|
||||
* and shared elements between the two.
|
||||
*
|
||||
* @deprecated See {@link TransitionChangeHandler}
|
||||
*/
|
||||
// Much of this class is based on FragmentTransition.java and FragmentTransitionCompat21.java from the Android support library
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
@Deprecated
|
||||
public abstract class SharedElementTransitionChangeHandler extends TransitionChangeHandler {
|
||||
|
||||
// A map of from -> to names. Generally these will be the same.
|
||||
@NonNull final ArrayMap<String, String> sharedElementNames = new ArrayMap<>();
|
||||
|
||||
@NonNull final List<String> waitForTransitionNames = new ArrayList<>();
|
||||
@NonNull final List<ViewParentPair> removedViews = new ArrayList<>();
|
||||
|
||||
@Nullable Transition exitTransition;
|
||||
@Nullable Transition enterTransition;
|
||||
@Nullable Transition sharedElementTransition;
|
||||
@Nullable private SharedElementCallback exitTransitionCallback;
|
||||
@Nullable private SharedElementCallback enterTransitionCallback;
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected final Transition getTransition(@NonNull ViewGroup container, @Nullable View from, @Nullable View to, boolean isPush) {
|
||||
exitTransition = getExitTransition(container, from, to, isPush);
|
||||
enterTransition = getEnterTransition(container, from, to, isPush);
|
||||
sharedElementTransition = getSharedElementTransition(container, from, to, isPush);
|
||||
exitTransitionCallback = getExitTransitionCallback(container, from, to, isPush);
|
||||
enterTransitionCallback = getEnterTransitionCallback(container, from, to, isPush);
|
||||
|
||||
if (enterTransition == null && sharedElementTransition == null && exitTransition == null) {
|
||||
throw new IllegalStateException("SharedElementTransitionChangeHandler must have at least one transaction.");
|
||||
}
|
||||
|
||||
return mergeTransitions(isPush);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void prepareForTransition(@NonNull final ViewGroup container, @Nullable final View from, @Nullable final View to, @NonNull final Transition transition, final boolean isPush, @NonNull final OnTransitionPreparedListener onTransitionPreparedListener) {
|
||||
OnTransitionPreparedListener listener = new OnTransitionPreparedListener() {
|
||||
@Override
|
||||
public void onPrepared() {
|
||||
configureTransition(container, from, to, transition, isPush);
|
||||
onTransitionPreparedListener.onPrepared();
|
||||
}
|
||||
};
|
||||
|
||||
configureSharedElements(container, from, to, isPush);
|
||||
|
||||
if (to != null && to.getParent() == null && waitForTransitionNames.size() > 0) {
|
||||
waitOnAllTransitionNames(to, listener);
|
||||
container.addView(to);
|
||||
} else {
|
||||
listener.onPrepared();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void executePropertyChanges(@NonNull ViewGroup container, @Nullable View from, @Nullable View to, @Nullable Transition transition, boolean isPush) {
|
||||
if (to != null && removedViews.size() > 0) {
|
||||
to.setVisibility(View.VISIBLE);
|
||||
|
||||
for (ViewParentPair removedView : removedViews) {
|
||||
removedView.parent.addView(removedView.view);
|
||||
}
|
||||
|
||||
removedViews.clear();
|
||||
}
|
||||
|
||||
super.executePropertyChanges(container, from, to, transition, isPush);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAbortPush(@NonNull ControllerChangeHandler newHandler, @Nullable Controller newTop) {
|
||||
super.onAbortPush(newHandler, newTop);
|
||||
|
||||
removedViews.clear();
|
||||
}
|
||||
|
||||
void configureTransition(@NonNull final ViewGroup container, @Nullable View from, @Nullable View to, @NonNull final Transition transition, boolean isPush) {
|
||||
final View nonExistentView = new View(container.getContext());
|
||||
|
||||
List<View> fromSharedElements = new ArrayList<>();
|
||||
List<View> toSharedElements = new ArrayList<>();
|
||||
|
||||
configureSharedElements(container, nonExistentView, to, from, isPush, fromSharedElements, toSharedElements);
|
||||
|
||||
List<View> exitingViews = exitTransition != null ? configureEnteringExitingViews(exitTransition, from, fromSharedElements, nonExistentView) : null;
|
||||
if (exitingViews == null || exitingViews.isEmpty()) {
|
||||
exitTransition = null;
|
||||
}
|
||||
|
||||
if (enterTransition != null) {
|
||||
enterTransition.addTarget(nonExistentView);
|
||||
}
|
||||
|
||||
final List<View> enteringViews = new ArrayList<>();
|
||||
scheduleRemoveTargets(transition, enterTransition, enteringViews, exitTransition, exitingViews, sharedElementTransition, toSharedElements);
|
||||
scheduleTargetChange(container, to, nonExistentView, toSharedElements, enteringViews, exitingViews);
|
||||
|
||||
setNameOverrides(container, toSharedElements);
|
||||
scheduleNameReset(container, toSharedElements);
|
||||
}
|
||||
|
||||
private void waitOnAllTransitionNames(@NonNull final View to, @NonNull final OnTransitionPreparedListener onTransitionPreparedListener) {
|
||||
OnPreDrawListener onPreDrawListener = new OnPreDrawListener() {
|
||||
boolean addedSubviewListeners;
|
||||
|
||||
@Override
|
||||
public boolean onPreDraw() {
|
||||
List<View> foundViews = new ArrayList<>();
|
||||
boolean allViewsFound = true;
|
||||
for (String transitionName : waitForTransitionNames) {
|
||||
View namedView = LegacyTransitionUtils.findNamedView(to, transitionName);
|
||||
if (namedView != null) {
|
||||
foundViews.add(LegacyTransitionUtils.findNamedView(to, transitionName));
|
||||
} else {
|
||||
allViewsFound = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (allViewsFound && !addedSubviewListeners) {
|
||||
addedSubviewListeners = true;
|
||||
waitOnChildTransitionNames(to, foundViews, this, onTransitionPreparedListener);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
to.getViewTreeObserver().addOnPreDrawListener(onPreDrawListener);
|
||||
}
|
||||
|
||||
void waitOnChildTransitionNames(@NonNull final View to, @NonNull List<View> foundViews, @NonNull final OnPreDrawListener parentPreDrawListener, @NonNull final OnTransitionPreparedListener onTransitionPreparedListener) {
|
||||
for (final View view : foundViews) {
|
||||
OneShotPreDrawListener.add(true, view, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
waitForTransitionNames.remove(view.getTransitionName());
|
||||
|
||||
removedViews.add(new ViewParentPair(view, (ViewGroup)view.getParent()));
|
||||
((ViewGroup)view.getParent()).removeView(view);
|
||||
|
||||
if (waitForTransitionNames.size() == 0) {
|
||||
to.getViewTreeObserver().removeOnPreDrawListener(parentPreDrawListener);
|
||||
to.setVisibility(View.INVISIBLE);
|
||||
onTransitionPreparedListener.onPrepared();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void scheduleTargetChange(@NonNull final ViewGroup container, @Nullable final View to, @NonNull final View nonExistentView,
|
||||
@NonNull final List<View> toSharedElements, @NonNull final List<View> enteringViews, @Nullable final List<View> exitingViews) {
|
||||
OneShotPreDrawListener.add(true, container, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (enterTransition != null) {
|
||||
enterTransition.removeTarget(nonExistentView);
|
||||
List<View> views = configureEnteringExitingViews(enterTransition, to, toSharedElements, nonExistentView);
|
||||
enteringViews.addAll(views);
|
||||
}
|
||||
|
||||
if (exitingViews != null) {
|
||||
if (exitTransition != null) {
|
||||
List<View> tempExiting = new ArrayList<>();
|
||||
tempExiting.add(nonExistentView);
|
||||
LegacyTransitionUtils.replaceTargets(exitTransition, exitingViews, tempExiting);
|
||||
}
|
||||
exitingViews.clear();
|
||||
exitingViews.add(nonExistentView);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private Transition mergeTransitions(boolean isPush) {
|
||||
boolean overlap = enterTransition == null || exitTransition == null || allowTransitionOverlap(isPush);
|
||||
|
||||
if (overlap) {
|
||||
return LegacyTransitionUtils.mergeTransitions(TransitionSet.ORDERING_TOGETHER, exitTransition, enterTransition, sharedElementTransition);
|
||||
} else {
|
||||
Transition staggered = LegacyTransitionUtils.mergeTransitions(TransitionSet.ORDERING_SEQUENTIAL, exitTransition, enterTransition);
|
||||
return LegacyTransitionUtils.mergeTransitions(TransitionSet.ORDERING_TOGETHER, staggered, sharedElementTransition);
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull List<View> configureEnteringExitingViews(@NonNull Transition transition, @Nullable View view, @NonNull List<View> sharedElements, @NonNull View nonExistentView) {
|
||||
List<View> viewList = new ArrayList<>();
|
||||
if (view != null) {
|
||||
captureTransitioningViews(viewList, view);
|
||||
}
|
||||
viewList.removeAll(sharedElements);
|
||||
if (!viewList.isEmpty()) {
|
||||
viewList.add(nonExistentView);
|
||||
LegacyTransitionUtils.addTargets(transition, viewList);
|
||||
}
|
||||
return viewList;
|
||||
}
|
||||
|
||||
private void configureSharedElements(@NonNull ViewGroup container, @NonNull final View nonExistentView, @Nullable final View to, @Nullable View from,
|
||||
final boolean isPush, @NonNull final List<View> fromSharedElements, @NonNull final List<View> toSharedElements) {
|
||||
|
||||
if (to == null || from == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
ArrayMap<String, View> capturedFromSharedElements = captureFromSharedElements(from);
|
||||
|
||||
if (sharedElementNames.isEmpty()) {
|
||||
sharedElementTransition = null;
|
||||
} else if (capturedFromSharedElements != null) {
|
||||
fromSharedElements.addAll(capturedFromSharedElements.values());
|
||||
}
|
||||
|
||||
if (enterTransition == null && exitTransition == null && sharedElementTransition == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
callSharedElementStartEnd(capturedFromSharedElements, true);
|
||||
|
||||
final Rect toEpicenter;
|
||||
if (sharedElementTransition != null) {
|
||||
toEpicenter = new Rect();
|
||||
LegacyTransitionUtils.setTargets(sharedElementTransition, nonExistentView, fromSharedElements);
|
||||
setFromEpicenter(capturedFromSharedElements);
|
||||
if (enterTransition != null) {
|
||||
enterTransition.setEpicenterCallback(new Transition.EpicenterCallback() {
|
||||
@Override
|
||||
public Rect onGetEpicenter(Transition transition) {
|
||||
if (toEpicenter.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return toEpicenter;
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
toEpicenter = null;
|
||||
}
|
||||
|
||||
OneShotPreDrawListener.add(true, container, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
ArrayMap<String, View> capturedToSharedElements = captureToSharedElements(to, isPush);
|
||||
|
||||
if (capturedToSharedElements != null) {
|
||||
toSharedElements.addAll(capturedToSharedElements.values());
|
||||
toSharedElements.add(nonExistentView);
|
||||
}
|
||||
|
||||
callSharedElementStartEnd(capturedToSharedElements, false);
|
||||
if (sharedElementTransition != null) {
|
||||
sharedElementTransition.getTargets().clear();
|
||||
sharedElementTransition.getTargets().addAll(toSharedElements);
|
||||
LegacyTransitionUtils.replaceTargets(sharedElementTransition, fromSharedElements, toSharedElements);
|
||||
|
||||
final View toEpicenterView = getToEpicenterView(capturedToSharedElements);
|
||||
if (toEpicenterView != null && toEpicenter != null) {
|
||||
LegacyTransitionUtils.getBoundsOnScreen(toEpicenterView, toEpicenter);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Nullable View getToEpicenterView(@Nullable ArrayMap<String, View> toSharedElements) {
|
||||
if (enterTransition != null && sharedElementNames.size() > 0 && toSharedElements != null) {
|
||||
return toSharedElements.get(sharedElementNames.valueAt(0));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void setFromEpicenter(@Nullable ArrayMap<String, View> fromSharedElements) {
|
||||
if (sharedElementNames.size() > 0 && fromSharedElements != null) {
|
||||
final View fromEpicenterView = fromSharedElements.get(sharedElementNames.keyAt(0));
|
||||
|
||||
if (sharedElementTransition != null) {
|
||||
LegacyTransitionUtils.setEpicenter(sharedElementTransition, fromEpicenterView);
|
||||
}
|
||||
|
||||
if (exitTransition != null) {
|
||||
LegacyTransitionUtils.setEpicenter(exitTransition, fromEpicenterView);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable ArrayMap<String, View> captureToSharedElements(@Nullable final View to, boolean isPush) {
|
||||
if (sharedElementNames.isEmpty() || sharedElementTransition == null || to == null) {
|
||||
sharedElementNames.clear();
|
||||
return null;
|
||||
}
|
||||
|
||||
final ArrayMap<String, View> toSharedElements = new ArrayMap<>();
|
||||
LegacyTransitionUtils.findNamedViews(toSharedElements, to);
|
||||
for (ViewParentPair removedView : removedViews) {
|
||||
toSharedElements.put(removedView.view.getTransitionName(), removedView.view);
|
||||
}
|
||||
|
||||
final List<String> names = new ArrayList<>(sharedElementNames.values());
|
||||
|
||||
toSharedElements.retainAll(names);
|
||||
if (enterTransitionCallback != null) {
|
||||
enterTransitionCallback.onMapSharedElements(names, toSharedElements);
|
||||
for (int i = names.size() - 1; i >= 0; i--) {
|
||||
String name = names.get(i);
|
||||
View view = toSharedElements.get(name);
|
||||
if (view == null) {
|
||||
String key = findKeyForValue(sharedElementNames, name);
|
||||
if (key != null) {
|
||||
sharedElementNames.remove(key);
|
||||
}
|
||||
} else if (!name.equals(view.getTransitionName())) {
|
||||
String key = findKeyForValue(sharedElementNames, name);
|
||||
if (key != null) {
|
||||
sharedElementNames.put(key, view.getTransitionName());
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (int i = sharedElementNames.size() - 1; i >= 0; i--) {
|
||||
final String targetName = sharedElementNames.valueAt(i);
|
||||
if (!toSharedElements.containsKey(targetName)) {
|
||||
sharedElementNames.removeAt(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
return toSharedElements;
|
||||
}
|
||||
|
||||
@Nullable String findKeyForValue(@NonNull ArrayMap<String, String> map, @NonNull String value) {
|
||||
final int numElements = map.size();
|
||||
for (int i = 0; i < numElements; i++) {
|
||||
if (value.equals(map.valueAt(i))) {
|
||||
return map.keyAt(i);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private ArrayMap<String, View> captureFromSharedElements(@NonNull View from) {
|
||||
if (sharedElementNames.isEmpty() || sharedElementTransition == null) {
|
||||
sharedElementNames.clear();
|
||||
return null;
|
||||
}
|
||||
|
||||
final ArrayMap<String, View> fromSharedElements = new ArrayMap<>();
|
||||
LegacyTransitionUtils.findNamedViews(fromSharedElements, from);
|
||||
|
||||
final List<String> names = new ArrayList<>(sharedElementNames.keySet());
|
||||
|
||||
fromSharedElements.retainAll(names);
|
||||
if (exitTransitionCallback != null) {
|
||||
exitTransitionCallback.onMapSharedElements(names, fromSharedElements);
|
||||
for (int i = names.size() - 1; i >= 0; i--) {
|
||||
String name = names.get(i);
|
||||
View view = fromSharedElements.get(name);
|
||||
if (view == null) {
|
||||
sharedElementNames.remove(name);
|
||||
} else if (!name.equals(view.getTransitionName())) {
|
||||
String targetValue = sharedElementNames.remove(name);
|
||||
sharedElementNames.put(view.getTransitionName(), targetValue);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
sharedElementNames.retainAll(fromSharedElements.keySet());
|
||||
}
|
||||
return fromSharedElements;
|
||||
}
|
||||
|
||||
void callSharedElementStartEnd(@Nullable ArrayMap<String, View> sharedElements, boolean isStart) {
|
||||
if (enterTransitionCallback != null) {
|
||||
final int count = sharedElements == null ? 0 : sharedElements.size();
|
||||
List<View> views = new ArrayList<>(count);
|
||||
List<String> names = new ArrayList<>(count);
|
||||
for (int i = 0; i < count; i++) {
|
||||
names.add(sharedElements.keyAt(i));
|
||||
views.add(sharedElements.valueAt(i));
|
||||
}
|
||||
if (isStart) {
|
||||
enterTransitionCallback.onSharedElementStart(names, views, null);
|
||||
} else {
|
||||
enterTransitionCallback.onSharedElementEnd(names, views, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void captureTransitioningViews(@NonNull List<View> transitioningViews, @NonNull View view) {
|
||||
if (view.getVisibility() == View.VISIBLE) {
|
||||
if (view instanceof ViewGroup) {
|
||||
ViewGroup viewGroup = (ViewGroup) view;
|
||||
if (viewGroup.isTransitionGroup()) {
|
||||
transitioningViews.add(viewGroup);
|
||||
} else {
|
||||
int count = viewGroup.getChildCount();
|
||||
for (int i = 0; i < count; i++) {
|
||||
View child = viewGroup.getChildAt(i);
|
||||
captureTransitioningViews(transitioningViews, child);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
transitioningViews.add(view);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void scheduleRemoveTargets(@NonNull final Transition overallTransition,
|
||||
@Nullable final Transition enterTransition, @Nullable final List<View> enteringViews,
|
||||
@Nullable final Transition exitTransition, @Nullable final List<View> exitingViews,
|
||||
@Nullable final Transition sharedElementTransition, @Nullable final List<View> toSharedElements) {
|
||||
overallTransition.addListener(new TransitionListener() {
|
||||
@Override
|
||||
public void onTransitionStart(Transition transition) {
|
||||
if (enterTransition != null && enteringViews != null) {
|
||||
LegacyTransitionUtils.replaceTargets(enterTransition, enteringViews, null);
|
||||
}
|
||||
if (exitTransition != null && exitingViews != null) {
|
||||
LegacyTransitionUtils.replaceTargets(exitTransition, exitingViews, null);
|
||||
}
|
||||
if (sharedElementTransition != null && toSharedElements != null) {
|
||||
LegacyTransitionUtils.replaceTargets(sharedElementTransition, toSharedElements, null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTransitionEnd(Transition transition) { }
|
||||
|
||||
@Override
|
||||
public void onTransitionCancel(Transition transition) { }
|
||||
|
||||
@Override
|
||||
public void onTransitionPause(Transition transition) { }
|
||||
|
||||
@Override
|
||||
public void onTransitionResume(Transition transition) { }
|
||||
});
|
||||
}
|
||||
|
||||
private void setNameOverrides(@NonNull final View container, @NonNull final List<View> toSharedElements) {
|
||||
OneShotPreDrawListener.add(true, container, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
final int numSharedElements = toSharedElements.size();
|
||||
for (int i = 0; i < numSharedElements; i++) {
|
||||
View view = toSharedElements.get(i);
|
||||
String name = view.getTransitionName();
|
||||
if (name != null) {
|
||||
String inName = findKeyForValue(sharedElementNames, name);
|
||||
view.setTransitionName(inName);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void scheduleNameReset(@NonNull final ViewGroup container, @NonNull final List<View> toSharedElements) {
|
||||
OneShotPreDrawListener.add(true, container, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
final int numSharedElements = toSharedElements.size();
|
||||
for (int i = 0; i < numSharedElements; i++) {
|
||||
final View view = toSharedElements.get(i);
|
||||
final String name = view.getTransitionName();
|
||||
final String inName = sharedElementNames.get(name);
|
||||
view.setTransitionName(inName);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Will be called when views are ready to have their shared elements configured. Within this method one of the addSharedElement methods
|
||||
* should be called for each shared element that will be used. If one or more of these shared elements will not instantly be available in
|
||||
* the incoming view (for ex, in a RecyclerView), waitOnSharedElementNamed can be called to delay the transition until everything is available.
|
||||
*/
|
||||
public abstract void configureSharedElements(@NonNull ViewGroup container, @Nullable View from, @Nullable View to, boolean isPush);
|
||||
|
||||
/**
|
||||
* Should return the transition that will be used on the exiting ("from") view, if one is desired.
|
||||
*/
|
||||
@Nullable
|
||||
public abstract Transition getExitTransition(@NonNull ViewGroup container, @Nullable View from, @Nullable View to, boolean isPush);
|
||||
|
||||
/**
|
||||
* Should return the transition that will be used on shared elements between the from and to views.
|
||||
*/
|
||||
@Nullable
|
||||
public abstract Transition getSharedElementTransition(@NonNull ViewGroup container, @Nullable View from, @Nullable View to, boolean isPush);
|
||||
|
||||
/**
|
||||
* Should return the transition that will be used on the entering ("to") view, if one is desired.
|
||||
*/
|
||||
@Nullable
|
||||
public abstract Transition getEnterTransition(@NonNull ViewGroup container, @Nullable View from, @Nullable View to, boolean isPush);
|
||||
|
||||
/**
|
||||
* Should return a callback that can be used to customize transition behavior of the shared element transition for the "from" view.
|
||||
*/
|
||||
@Nullable
|
||||
public SharedElementCallback getExitTransitionCallback(@NonNull ViewGroup container, @Nullable View from, @Nullable View to, boolean isPush) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should return a callback that can be used to customize transition behavior of the shared element transition for the "to" view.
|
||||
*/
|
||||
@Nullable
|
||||
public SharedElementCallback getEnterTransitionCallback(@NonNull ViewGroup container, @Nullable View from, @Nullable View to, boolean isPush) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should return whether or not the the exit transition and enter transition should overlap. If true,
|
||||
* the enter transition will start as soon as possible. Otherwise, the enter transition will wait until the
|
||||
* completion of the exit transition. Defaults to true.
|
||||
*/
|
||||
public boolean allowTransitionOverlap(boolean isPush) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to register an element that will take part in the shared element transition.
|
||||
*
|
||||
* @param name The transition name that is used for both the entering and exiting views.
|
||||
*/
|
||||
protected final void addSharedElement(@NonNull String name) {
|
||||
sharedElementNames.put(name, name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to register an element that will take part in the shared element transition. Maps the name used in the
|
||||
* "from" view to the name used in the "to" view if they are not the same.
|
||||
*
|
||||
* @param fromName The transition name used in the "from" view
|
||||
* @param toName The transition name used in the "to" view
|
||||
*/
|
||||
protected final void addSharedElement(@NonNull String fromName, @NonNull String toName) {
|
||||
sharedElementNames.put(fromName, toName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to register an element that will take part in the shared element transition. Maps the name used in the
|
||||
* "from" view to the name used in the "to" view if they are not the same.
|
||||
*
|
||||
* @param sharedElement The view from the "from" view that will take part in the shared element transition
|
||||
* @param toName The transition name used in the "to" view
|
||||
*/
|
||||
protected final void addSharedElement(@NonNull View sharedElement, @NonNull String toName) {
|
||||
String transitionName = sharedElement.getTransitionName();
|
||||
if (transitionName == null) {
|
||||
throw new IllegalArgumentException("Unique transitionNames are required for all sharedElements");
|
||||
}
|
||||
sharedElementNames.put(transitionName, toName);
|
||||
}
|
||||
|
||||
/**
|
||||
* The transition will be delayed until the view with the name passed in is available in the "to" hierarchy. This is
|
||||
* particularly useful for views that don't load instantly, like RecyclerViews. Note that using this method can
|
||||
* potentially lock up your app indefinitely if the view never loads!
|
||||
*/
|
||||
protected final void waitOnSharedElementNamed(@NonNull String name) {
|
||||
if (!sharedElementNames.values().contains(name)) {
|
||||
throw new IllegalStateException("Can't wait on a shared element that hasn't been registered using addSharedElement");
|
||||
}
|
||||
waitForTransitionNames.add(name);
|
||||
}
|
||||
|
||||
private static class OneShotPreDrawListener implements OnPreDrawListener, View.OnAttachStateChangeListener {
|
||||
|
||||
private final View view;
|
||||
private ViewTreeObserver viewTreeObserver;
|
||||
private final Runnable runnable;
|
||||
private final boolean preDrawReturnValue;
|
||||
|
||||
private OneShotPreDrawListener(boolean preDrawReturnValue, @NonNull View view, @NonNull Runnable runnable) {
|
||||
this.preDrawReturnValue = preDrawReturnValue;
|
||||
this.view = view;
|
||||
viewTreeObserver = view.getViewTreeObserver();
|
||||
this.runnable = runnable;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static OneShotPreDrawListener add(boolean preDrawReturnValue, @NonNull View view, @NonNull Runnable runnable) {
|
||||
OneShotPreDrawListener listener = new OneShotPreDrawListener(preDrawReturnValue, view, runnable);
|
||||
view.getViewTreeObserver().addOnPreDrawListener(listener);
|
||||
view.addOnAttachStateChangeListener(listener);
|
||||
return listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPreDraw() {
|
||||
removeListener();
|
||||
runnable.run();
|
||||
return preDrawReturnValue;
|
||||
}
|
||||
|
||||
private void removeListener() {
|
||||
if (viewTreeObserver.isAlive()) {
|
||||
viewTreeObserver.removeOnPreDrawListener(this);
|
||||
} else {
|
||||
view.getViewTreeObserver().removeOnPreDrawListener(this);
|
||||
}
|
||||
view.removeOnAttachStateChangeListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewAttachedToWindow(View v) {
|
||||
viewTreeObserver = v.getViewTreeObserver();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewDetachedFromWindow(View v) {
|
||||
removeListener();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class ViewParentPair {
|
||||
@NonNull final View view;
|
||||
@NonNull final ViewGroup parent;
|
||||
|
||||
ViewParentPair(@NonNull View view, ViewGroup parent) {
|
||||
this.view = view;
|
||||
this.parent = parent;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
-160
@@ -1,160 +0,0 @@
|
||||
package com.bluelinelabs.conductor.changehandler;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.os.Build;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import android.transition.Transition;
|
||||
import android.transition.Transition.TransitionListener;
|
||||
import android.transition.TransitionManager;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.bluelinelabs.conductor.Controller;
|
||||
import com.bluelinelabs.conductor.ControllerChangeHandler;
|
||||
|
||||
/**
|
||||
* A base {@link ControllerChangeHandler} that facilitates using {@link android.transition.Transition}s to replace Controller Views.
|
||||
*
|
||||
* @deprecated Transitions are backported via
|
||||
* <a href="https://developer.android.com/jetpack/androidx/releases/transition">AndroidX</a>.
|
||||
* They have the same API, but are preferred to the built-in framework transitions as they fix
|
||||
* platform-specific bugs, and even work on versions older than 5.0.
|
||||
* The {@code androidx-transition} module provides an equivalent {@link ControllerChangeHandler}.
|
||||
*/
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
@Deprecated
|
||||
public abstract class TransitionChangeHandler extends ControllerChangeHandler {
|
||||
|
||||
public interface OnTransitionPreparedListener {
|
||||
void onPrepared();
|
||||
}
|
||||
|
||||
boolean canceled;
|
||||
private boolean needsImmediateCompletion;
|
||||
|
||||
/**
|
||||
* Should be overridden to return the Transition to use while replacing Views.
|
||||
*
|
||||
* @param container The container these Views are hosted in
|
||||
* @param from The previous View in the container or {@code null} if there was no Controller before this transition
|
||||
* @param to The next View that should be put in the container or {@code null} if no Controller is being transitioned to
|
||||
* @param isPush True if this is a push transaction, false if it's a pop
|
||||
*/
|
||||
@NonNull
|
||||
protected abstract Transition getTransition(@NonNull ViewGroup container, @Nullable View from, @Nullable View to, boolean isPush);
|
||||
|
||||
@Override
|
||||
public void onAbortPush(@NonNull ControllerChangeHandler newHandler, @Nullable Controller newTop) {
|
||||
super.onAbortPush(newHandler, newTop);
|
||||
|
||||
canceled = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void completeImmediately() {
|
||||
super.completeImmediately();
|
||||
|
||||
needsImmediateCompletion = true;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private ControllerChangeCompletedListener listener;
|
||||
|
||||
@Override
|
||||
public void performChange(@NonNull final ViewGroup container, @Nullable final View from, @Nullable final View to, final boolean isPush, @NonNull final ControllerChangeCompletedListener changeListener) {
|
||||
listener = changeListener;
|
||||
if (canceled) {
|
||||
changeListener.onChangeCompleted();
|
||||
return;
|
||||
}
|
||||
if (needsImmediateCompletion) {
|
||||
executePropertyChanges(container, from, to, null, isPush);
|
||||
changeListener.onChangeCompleted();
|
||||
return;
|
||||
}
|
||||
|
||||
final Runnable onTransitionNotStarted = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
changeListener.onChangeCompleted();
|
||||
}
|
||||
};
|
||||
|
||||
final Transition transition = getTransition(container, from, to, isPush);
|
||||
transition.addListener(new TransitionListener() {
|
||||
@Override
|
||||
public void onTransitionStart(Transition transition) {
|
||||
container.removeCallbacks(onTransitionNotStarted);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTransitionEnd(Transition transition) {
|
||||
listener.onChangeCompleted();
|
||||
listener = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTransitionCancel(Transition transition) {
|
||||
listener.onChangeCompleted();
|
||||
listener = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTransitionPause(Transition transition) { }
|
||||
|
||||
@Override
|
||||
public void onTransitionResume(Transition transition) { }
|
||||
});
|
||||
|
||||
prepareForTransition(container, from, to, transition, isPush, new OnTransitionPreparedListener() {
|
||||
@Override
|
||||
public void onPrepared() {
|
||||
if (!canceled) {
|
||||
TransitionManager.beginDelayedTransition(container, transition);
|
||||
executePropertyChanges(container, from, to, transition, isPush);
|
||||
container.post(onTransitionNotStarted);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removesFromViewOnPush() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called before a transition occurs. This can be used to reorder views, set their transition names, etc. The transition will begin
|
||||
* when {@code onTransitionPreparedListener} is called.
|
||||
*
|
||||
* @param container The container these Views are hosted in
|
||||
* @param from The previous View in the container or {@code null} if there was no Controller before this transition
|
||||
* @param to The next View that should be put in the container or {@code null} if no Controller is being transitioned to
|
||||
* @param transition The transition that is being prepared for
|
||||
* @param isPush True if this is a push transaction, false if it's a pop
|
||||
*/
|
||||
public void prepareForTransition(@NonNull ViewGroup container, @Nullable View from, @Nullable View to, @NonNull Transition transition, boolean isPush, @NonNull OnTransitionPreparedListener onTransitionPreparedListener) {
|
||||
onTransitionPreparedListener.onPrepared();
|
||||
}
|
||||
|
||||
/**
|
||||
* This should set all view properties needed for the transition to work properly. By default it removes the "from" view
|
||||
* and adds the "to" view.
|
||||
*
|
||||
* @param container The container these Views are hosted in
|
||||
* @param from The previous View in the container or {@code null} if there was no Controller before this transition
|
||||
* @param to The next View that should be put in the container or {@code null} if no Controller is being transitioned to
|
||||
* @param transition The transition with which {@code TransitionManager.beginDelayedTransition} has been called. This will be null only if another ControllerChangeHandler immediately overrides this one.
|
||||
* @param isPush True if this is a push transaction, false if it's a pop
|
||||
*/
|
||||
public void executePropertyChanges(@NonNull ViewGroup container, @Nullable View from, @Nullable View to, @Nullable Transition transition, boolean isPush) {
|
||||
if (from != null && (removesFromViewOnPush() || !isPush) && from.getParent() == container) {
|
||||
container.removeView(from);
|
||||
}
|
||||
if (to != null && to.getParent() == null) {
|
||||
container.addView(to);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
-117
@@ -1,117 +0,0 @@
|
||||
package com.bluelinelabs.conductor.changehandler;
|
||||
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.bluelinelabs.conductor.Controller;
|
||||
import com.bluelinelabs.conductor.ControllerChangeHandler;
|
||||
import com.bluelinelabs.conductor.internal.ClassUtils;
|
||||
|
||||
/**
|
||||
* A base {@link ControllerChangeHandler} that facilitates using {@link android.transition.Transition}s to replace Controller Views.
|
||||
* If the target device is running on a version of Android that doesn't support transitions, a fallback {@link ControllerChangeHandler} will be used.
|
||||
*/
|
||||
public class TransitionChangeHandlerCompat extends ControllerChangeHandler {
|
||||
|
||||
private static final String KEY_CHANGE_HANDLER_CLASS = "TransitionChangeHandlerCompat.changeHandler.class";
|
||||
private static final String KEY_HANDLER_STATE = "TransitionChangeHandlerCompat.changeHandler.state";
|
||||
|
||||
@Nullable
|
||||
private ControllerChangeHandler changeHandler;
|
||||
|
||||
public TransitionChangeHandlerCompat() { }
|
||||
|
||||
/**
|
||||
* Constructor that takes a {@link TransitionChangeHandler} for use with compatible devices, as well as a fallback
|
||||
* {@link ControllerChangeHandler} for use with older devices.
|
||||
*
|
||||
* @param transitionChangeHandler The change handler that will be used on API 21 and above
|
||||
* @param fallbackChangeHandler The change handler that will be used on APIs below 21
|
||||
*/
|
||||
public TransitionChangeHandlerCompat(@NonNull TransitionChangeHandler transitionChangeHandler, @NonNull ControllerChangeHandler fallbackChangeHandler) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
changeHandler = transitionChangeHandler;
|
||||
} else {
|
||||
changeHandler = fallbackChangeHandler;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void performChange(@NonNull final ViewGroup container, @Nullable View from, @Nullable View to, boolean isPush, @NonNull final ControllerChangeCompletedListener changeListener) {
|
||||
if (changeHandler != null) {
|
||||
changeHandler.performChange(container, from, to, isPush, changeListener);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveToBundle(@NonNull Bundle bundle) {
|
||||
super.saveToBundle(bundle);
|
||||
|
||||
if (changeHandler != null) {
|
||||
bundle.putString(KEY_CHANGE_HANDLER_CLASS, changeHandler.getClass().getName());
|
||||
}
|
||||
|
||||
Bundle stateBundle = new Bundle();
|
||||
if (changeHandler != null) {
|
||||
changeHandler.saveToBundle(stateBundle);
|
||||
}
|
||||
bundle.putBundle(KEY_HANDLER_STATE, stateBundle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void restoreFromBundle(@NonNull Bundle bundle) {
|
||||
super.restoreFromBundle(bundle);
|
||||
|
||||
String className = bundle.getString(KEY_CHANGE_HANDLER_CLASS);
|
||||
changeHandler = ClassUtils.newInstance(className);
|
||||
//noinspection ConstantConditions
|
||||
changeHandler.restoreFromBundle(bundle.getBundle(KEY_HANDLER_STATE));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removesFromViewOnPush() {
|
||||
if (changeHandler != null) {
|
||||
return changeHandler.removesFromViewOnPush();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override @NonNull
|
||||
public ControllerChangeHandler copy() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
return new TransitionChangeHandlerCompat((TransitionChangeHandler)changeHandler.copy(), null);
|
||||
} else {
|
||||
return new TransitionChangeHandlerCompat(null, changeHandler.copy());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAbortPush(@NonNull ControllerChangeHandler newHandler, @Nullable Controller newTop) {
|
||||
if (changeHandler != null) {
|
||||
changeHandler.onAbortPush(newHandler, newTop);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void completeImmediately() {
|
||||
if (changeHandler != null) {
|
||||
changeHandler.completeImmediately();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setForceRemoveViewOnPush(boolean force) {
|
||||
if (changeHandler != null) {
|
||||
changeHandler.setForceRemoveViewOnPush(force);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onEnd() {
|
||||
changeHandler = null;
|
||||
}
|
||||
}
|
||||
-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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -17,6 +17,9 @@ import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.bluelinelabs.conductor.ActivityHostedRouter;
|
||||
import com.bluelinelabs.conductor.Router;
|
||||
|
||||
@@ -25,9 +28,6 @@ import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
public class LifecycleHandler extends Fragment implements ActivityLifecycleCallbacks {
|
||||
|
||||
private static final String FRAGMENT_TAG = "LifecycleHandler";
|
||||
@@ -159,14 +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();
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public void onAttach(Activity activity) {
|
||||
this.activity = activity;
|
||||
super.onAttach(activity);
|
||||
destroyed = false;
|
||||
setAttached();
|
||||
@@ -174,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();
|
||||
@@ -184,7 +190,10 @@ public class LifecycleHandler extends Fragment implements ActivityLifecycleCallb
|
||||
super.onDetach();
|
||||
|
||||
attached = false;
|
||||
destroyRouters();
|
||||
|
||||
if (activity != null) {
|
||||
destroyRouters(activity.isChangingConfigurations());
|
||||
}
|
||||
}
|
||||
|
||||
private void setAttached() {
|
||||
@@ -195,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -320,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())) {
|
||||
|
||||
-27
@@ -1,27 +0,0 @@
|
||||
package com.bluelinelabs.conductor.internal;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.bluelinelabs.conductor.ControllerChangeHandler;
|
||||
|
||||
public class NoOpControllerChangeHandler extends ControllerChangeHandler {
|
||||
|
||||
@Override
|
||||
public void performChange(@NonNull ViewGroup container, @Nullable View from, @Nullable View to, boolean isPush, @NonNull ControllerChangeCompletedListener changeListener) {
|
||||
changeListener.onChangeCompleted();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public ControllerChangeHandler copy() {
|
||||
return new NoOpControllerChangeHandler();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isReusable() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
package com.bluelinelabs.conductor.internal
|
||||
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import com.bluelinelabs.conductor.ControllerChangeHandler
|
||||
|
||||
class NoOpControllerChangeHandler : ControllerChangeHandler() {
|
||||
|
||||
override fun performChange(
|
||||
container: ViewGroup,
|
||||
from: View?,
|
||||
to: View?,
|
||||
isPush: Boolean,
|
||||
changeListener: ControllerChangeCompletedListener
|
||||
) {
|
||||
changeListener.onChangeCompleted()
|
||||
}
|
||||
|
||||
override fun copy(): ControllerChangeHandler = NoOpControllerChangeHandler()
|
||||
|
||||
override fun isReusable() = true
|
||||
}
|
||||
+251
@@ -0,0 +1,251 @@
|
||||
package com.bluelinelabs.conductor.internal
|
||||
|
||||
import android.content.Context
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onChangeStart(
|
||||
changeController: Controller,
|
||||
changeHandler: ControllerChangeHandler,
|
||||
changeType: ControllerChangeType,
|
||||
) {
|
||||
pauseOnChangeStart(
|
||||
targetController = controller,
|
||||
changeController = changeController,
|
||||
changeHandler = changeHandler,
|
||||
changeType = changeType,
|
||||
)
|
||||
|
||||
GlobalChangeStartListener.onChangeStart(changeController, changeHandler, changeType)
|
||||
}
|
||||
|
||||
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 postContextAvailable(controller: Controller, context: Context) {
|
||||
listenForAncestorChangeStart(controller)
|
||||
}
|
||||
|
||||
override fun preContextUnavailable(controller: Controller, context: Context) {
|
||||
stopListeningForAncestorChangeStart(controller)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
override fun getLifecycle() = lifecycleRegistry
|
||||
|
||||
override fun getSavedStateRegistry() = savedStateRegistryController.savedStateRegistry
|
||||
|
||||
private fun listenForAncestorChangeStart(controller: Controller) {
|
||||
GlobalChangeStartListener.subscribe(controller, controller.ancestors()) { ancestor, changeHandler, changeType ->
|
||||
// No-op on the case where we (the child controller) hasn't yet created a View as our parent is being
|
||||
// changed out.
|
||||
if (::lifecycleRegistry.isInitialized) {
|
||||
pauseOnChangeStart(
|
||||
targetController = ancestor,
|
||||
changeController = ancestor,
|
||||
changeHandler = changeHandler,
|
||||
changeType = changeType,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun stopListeningForAncestorChangeStart(controller: Controller) {
|
||||
GlobalChangeStartListener.unsubscribe(controller)
|
||||
}
|
||||
|
||||
// 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.
|
||||
private fun pauseOnChangeStart(
|
||||
targetController: Controller,
|
||||
changeController: Controller,
|
||||
changeHandler: ControllerChangeHandler,
|
||||
changeType: ControllerChangeType,
|
||||
) {
|
||||
if (
|
||||
targetController === 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
|
||||
}
|
||||
}
|
||||
|
||||
private fun Controller.ancestors(): Collection<String> {
|
||||
return buildList {
|
||||
var ancestor = parentController
|
||||
while (ancestor != null) {
|
||||
add(ancestor.instanceId)
|
||||
ancestor = ancestor.parentController
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val KEY_SAVED_STATE = "Registry.savedState"
|
||||
|
||||
fun own(target: Controller): OwnViewTreeLifecycleAndRegistry {
|
||||
return OwnViewTreeLifecycleAndRegistry(target)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// In order to prevent child controllers from having strong references to all of their ancestors, some of which may
|
||||
// break their connection before the child is made aware, this shared listener is used to call all interested parties
|
||||
// when a controller begins transitioning.
|
||||
private object GlobalChangeStartListener {
|
||||
private val listeners = mutableMapOf<String, Listener>()
|
||||
|
||||
fun subscribe(
|
||||
controller: Controller,
|
||||
targetControllers: Collection<String>,
|
||||
listener: (Controller, ControllerChangeHandler, ControllerChangeType) -> Unit,
|
||||
) {
|
||||
listeners[controller.instanceId] = Listener(targetControllers, listener)
|
||||
}
|
||||
|
||||
fun unsubscribe(controller: Controller) {
|
||||
listeners.remove(controller.instanceId)
|
||||
}
|
||||
|
||||
fun onChangeStart(controller: Controller, changeHandler: ControllerChangeHandler, changeType: ControllerChangeType) {
|
||||
listeners.values.forEach { it.call(controller, changeHandler, changeType) }
|
||||
}
|
||||
|
||||
private class Listener(
|
||||
private val targetControllers: Collection<String>,
|
||||
private val listener: (Controller, ControllerChangeHandler, ControllerChangeType) -> Unit,
|
||||
) {
|
||||
fun call(controller: Controller, changeHandler: ControllerChangeHandler, changeType: ControllerChangeType) {
|
||||
if (targetControllers.contains(controller.instanceId)) {
|
||||
listener(controller, changeHandler, changeType)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
package com.bluelinelabs.conductor.internal;
|
||||
|
||||
public interface RouterRequiringFunc {
|
||||
void execute();
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.bluelinelabs.conductor.internal
|
||||
|
||||
import androidx.annotation.RestrictTo
|
||||
import androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP
|
||||
|
||||
@RestrictTo(LIBRARY_GROUP)
|
||||
interface RouterRequiringFunc {
|
||||
fun execute()
|
||||
}
|
||||
-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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
package com.bluelinelabs.conductor.internal;
|
||||
|
||||
import android.os.Looper;
|
||||
import android.util.AndroidRuntimeException;
|
||||
|
||||
public class ThreadUtils {
|
||||
|
||||
public static void ensureMainThread() {
|
||||
if (Looper.getMainLooper().getThread() != Thread.currentThread()) {
|
||||
throw new CalledFromWrongThreadException("Methods that affect the view hierarchy can can only be called from the main thread.");
|
||||
}
|
||||
}
|
||||
|
||||
private static final class CalledFromWrongThreadException extends AndroidRuntimeException {
|
||||
CalledFromWrongThreadException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
@file:JvmName("ThreadUtils")
|
||||
|
||||
package com.bluelinelabs.conductor.internal
|
||||
|
||||
import android.os.Looper
|
||||
import android.util.AndroidRuntimeException
|
||||
import androidx.annotation.RestrictTo
|
||||
import androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP
|
||||
|
||||
@RestrictTo(LIBRARY_GROUP)
|
||||
internal fun ensureMainThread() {
|
||||
if (Looper.getMainLooper().thread !== Thread.currentThread()) {
|
||||
throw CalledFromWrongThreadException("Methods that affect the view hierarchy can can only be called from the main thread.")
|
||||
}
|
||||
}
|
||||
|
||||
private class CalledFromWrongThreadException(msg: String?) : AndroidRuntimeException(msg)
|
||||
@@ -1,24 +0,0 @@
|
||||
package com.bluelinelabs.conductor.internal;
|
||||
|
||||
import android.os.Bundle;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
public class TransactionIndexer {
|
||||
|
||||
private static final String KEY_INDEX = "TransactionIndexer.currentIndex";
|
||||
|
||||
private int currentIndex;
|
||||
|
||||
public int nextIndex() {
|
||||
return ++currentIndex;
|
||||
}
|
||||
|
||||
public void saveInstanceState(@NonNull Bundle outState) {
|
||||
outState.putInt(KEY_INDEX, currentIndex);
|
||||
}
|
||||
|
||||
public void restoreInstanceState(@NonNull Bundle savedInstanceState) {
|
||||
currentIndex = savedInstanceState.getInt(KEY_INDEX);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.bluelinelabs.conductor.internal
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.annotation.RestrictTo
|
||||
import androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP
|
||||
|
||||
@RestrictTo(LIBRARY_GROUP)
|
||||
class TransactionIndexer {
|
||||
|
||||
private var currentIndex = 0
|
||||
|
||||
fun nextIndex(): Int {
|
||||
return ++currentIndex
|
||||
}
|
||||
|
||||
fun saveInstanceState(outState: Bundle) {
|
||||
outState.putInt(KEY_INDEX, currentIndex)
|
||||
}
|
||||
|
||||
fun restoreInstanceState(savedInstanceState: Bundle) {
|
||||
currentIndex = savedInstanceState.getInt(KEY_INDEX)
|
||||
}
|
||||
}
|
||||
|
||||
private const val KEY_INDEX = "TransactionIndexer.currentIndex"
|
||||
@@ -45,7 +45,6 @@ public class ViewAttachHandler implements OnAttachStateChangeListener {
|
||||
public void onAttached() {
|
||||
childrenAttached = true;
|
||||
reportAttached();
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -68,6 +67,7 @@ public class ViewAttachHandler implements OnAttachStateChangeListener {
|
||||
|
||||
if (childOnAttachStateChangeListener != null && view instanceof ViewGroup) {
|
||||
findDeepestChild((ViewGroup)view).removeOnAttachStateChangeListener(childOnAttachStateChangeListener);
|
||||
childOnAttachStateChangeListener = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,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())
|
||||
}
|
||||
}
|
||||
+5
-3
@@ -2,12 +2,14 @@ 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;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public class ControllerChangeHandlerTests {
|
||||
|
||||
@Test
|
||||
@@ -26,8 +28,8 @@ public class ControllerChangeHandlerTests {
|
||||
assertEquals(horizontalChangeHandler.getClass(), restoredHorizontal.getClass());
|
||||
assertEquals(fadeChangeHandler.getClass(), restoredFade.getClass());
|
||||
|
||||
HorizontalChangeHandler restoredHorizontalCast = (HorizontalChangeHandler)restoredHorizontal;
|
||||
FadeChangeHandler restoredFadeCast = (FadeChangeHandler)restoredFade;
|
||||
HorizontalChangeHandler restoredHorizontalCast = (HorizontalChangeHandler) restoredHorizontal;
|
||||
FadeChangeHandler restoredFadeCast = (FadeChangeHandler) restoredFade;
|
||||
|
||||
assertEquals(horizontalChangeHandler.getAnimationDuration(), restoredHorizontalCast.getAnimationDuration());
|
||||
assertEquals(horizontalChangeHandler.removesFromViewOnPush(), restoredHorizontalCast.removesFromViewOnPush());
|
||||
|
||||
-269
@@ -1,269 +0,0 @@
|
||||
package com.bluelinelabs.conductor;
|
||||
|
||||
import android.os.Bundle;
|
||||
import androidx.annotation.NonNull;
|
||||
import android.view.View;
|
||||
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 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((ViewGroup)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((ViewGroup)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((ViewGroup)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((ViewGroup)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 extends Controller.LifecycleListener {
|
||||
final List<Boolean> changeEndReferences = new ArrayList<>();
|
||||
final List<Boolean> postCreateViewReferences = new ArrayList<>();
|
||||
final List<Boolean> postAttachReferences = new ArrayList<>();
|
||||
final List<Boolean> postDetachReferences = new ArrayList<>();
|
||||
final List<Boolean> postDestroyViewReferences = new ArrayList<>();
|
||||
final List<Boolean> postDestroyReferences = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public void onChangeEnd(@NonNull Controller controller, @NonNull ControllerChangeHandler changeHandler, @NonNull ControllerChangeType changeType) {
|
||||
changeEndReferences.add(controller.getActivity() != null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postCreateView(@NonNull Controller controller, @NonNull View view) {
|
||||
postCreateViewReferences.add(controller.getActivity() != null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postAttach(@NonNull Controller controller, @NonNull View view) {
|
||||
postAttachReferences.add(controller.getActivity() != null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postDetach(@NonNull Controller controller, @NonNull View view) {
|
||||
postDetachReferences.add(controller.getActivity() != null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postDestroyView(@NonNull Controller controller) {
|
||||
postDestroyViewReferences.add(controller.getActivity() != null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postDestroy(@NonNull Controller controller) {
|
||||
postDestroyReferences.add(controller.getActivity() != null);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+255
@@ -0,0 +1,255 @@
|
||||
package com.bluelinelabs.conductor
|
||||
|
||||
import android.os.Looper.getMainLooper
|
||||
import android.view.View
|
||||
import com.bluelinelabs.conductor.Controller.LifecycleListener
|
||||
import com.bluelinelabs.conductor.util.MockChangeHandler
|
||||
import com.bluelinelabs.conductor.util.TestActivity
|
||||
import org.junit.Assert
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.Robolectric
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import org.robolectric.Shadows.shadowOf
|
||||
import org.robolectric.annotation.Config
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
@Config(manifest = Config.NONE)
|
||||
class ControllerLifecycleActivityReferenceTests {
|
||||
|
||||
private val activityController = Robolectric.buildActivity(TestActivity::class.java).setup()
|
||||
private val activity = activityController.get()
|
||||
|
||||
@Test
|
||||
fun testSingleControllerActivityOnPush() {
|
||||
val controller = TestController()
|
||||
Assert.assertNull(controller.activity)
|
||||
|
||||
val listener = ActivityReferencingLifecycleListener()
|
||||
controller.addLifecycleListener(listener)
|
||||
|
||||
activity.router.pushController(
|
||||
controller.asTransaction(
|
||||
pushChangeHandler = MockChangeHandler.defaultHandler(),
|
||||
popChangeHandler = MockChangeHandler.defaultHandler()
|
||||
)
|
||||
)
|
||||
|
||||
Assert.assertEquals(listOf(true), listener.changeEndReferences)
|
||||
Assert.assertEquals(listOf(true), listener.postCreateViewReferences)
|
||||
Assert.assertEquals(listOf(true), listener.postAttachReferences)
|
||||
Assert.assertEquals(emptyList<Any>(), listener.postDetachReferences)
|
||||
Assert.assertEquals(emptyList<Any>(), listener.postDestroyViewReferences)
|
||||
Assert.assertEquals(emptyList<Any>(), listener.postDestroyReferences)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testChildControllerActivityOnPush() {
|
||||
val parent = TestController()
|
||||
activity.router.pushController(
|
||||
parent.asTransaction(
|
||||
pushChangeHandler = MockChangeHandler.defaultHandler(),
|
||||
popChangeHandler = MockChangeHandler.defaultHandler()
|
||||
)
|
||||
)
|
||||
|
||||
val child = TestController()
|
||||
Assert.assertNull(child.activity)
|
||||
|
||||
val listener = ActivityReferencingLifecycleListener()
|
||||
child.addLifecycleListener(listener)
|
||||
|
||||
val childRouter = parent.getChildRouter((parent.view!!.findViewById(TestController.VIEW_ID)))
|
||||
childRouter.pushController(
|
||||
child.asTransaction(
|
||||
pushChangeHandler = MockChangeHandler.defaultHandler(),
|
||||
popChangeHandler = MockChangeHandler.defaultHandler()
|
||||
)
|
||||
)
|
||||
|
||||
Assert.assertEquals(listOf(true), listener.changeEndReferences)
|
||||
Assert.assertEquals(listOf(true), listener.postCreateViewReferences)
|
||||
Assert.assertEquals(listOf(true), listener.postAttachReferences)
|
||||
Assert.assertEquals(emptyList<Any>(), listener.postDetachReferences)
|
||||
Assert.assertEquals(emptyList<Any>(), listener.postDestroyViewReferences)
|
||||
Assert.assertEquals(emptyList<Any>(), listener.postDestroyReferences)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSingleControllerActivityOnPop() {
|
||||
val controller = TestController()
|
||||
val listener = ActivityReferencingLifecycleListener()
|
||||
controller.addLifecycleListener(listener)
|
||||
|
||||
activity.router.pushController(
|
||||
controller.asTransaction(
|
||||
pushChangeHandler = MockChangeHandler.defaultHandler(),
|
||||
popChangeHandler = MockChangeHandler.defaultHandler()
|
||||
)
|
||||
)
|
||||
activity.router.popCurrentController()
|
||||
|
||||
Assert.assertEquals(listOf(true, true), listener.changeEndReferences)
|
||||
Assert.assertEquals(listOf(true), listener.postCreateViewReferences)
|
||||
Assert.assertEquals(listOf(true), listener.postAttachReferences)
|
||||
Assert.assertEquals(listOf(true), listener.postDetachReferences)
|
||||
Assert.assertEquals(listOf(true), listener.postDestroyViewReferences)
|
||||
Assert.assertEquals(listOf(true), listener.postDestroyReferences)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testChildControllerActivityOnPop() {
|
||||
val parent = TestController()
|
||||
activity.router.pushController(
|
||||
parent.asTransaction(
|
||||
pushChangeHandler = MockChangeHandler.defaultHandler(),
|
||||
popChangeHandler = MockChangeHandler.defaultHandler()
|
||||
)
|
||||
)
|
||||
|
||||
val child = TestController()
|
||||
val listener = ActivityReferencingLifecycleListener()
|
||||
child.addLifecycleListener(listener)
|
||||
|
||||
val childRouter = parent.getChildRouter(parent.view!!.findViewById(TestController.VIEW_ID))
|
||||
childRouter.setPopRootControllerMode(Router.PopRootControllerMode.POP_ROOT_CONTROLLER_AND_VIEW)
|
||||
childRouter.pushController(
|
||||
child.asTransaction(
|
||||
pushChangeHandler = MockChangeHandler.defaultHandler(),
|
||||
popChangeHandler = MockChangeHandler.defaultHandler()
|
||||
)
|
||||
)
|
||||
childRouter.popCurrentController()
|
||||
|
||||
shadowOf(getMainLooper()).idle()
|
||||
|
||||
Assert.assertEquals(listOf(true, true), listener.changeEndReferences)
|
||||
Assert.assertEquals(listOf(true), listener.postCreateViewReferences)
|
||||
Assert.assertEquals(listOf(true), listener.postAttachReferences)
|
||||
Assert.assertEquals(listOf(true), listener.postDetachReferences)
|
||||
Assert.assertEquals(listOf(true), listener.postDestroyViewReferences)
|
||||
Assert.assertEquals(listOf(true), listener.postDestroyReferences)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testChildControllerActivityOnParentPop() {
|
||||
val parent = TestController()
|
||||
activity.router.pushController(
|
||||
parent.asTransaction(
|
||||
pushChangeHandler = MockChangeHandler.defaultHandler(),
|
||||
popChangeHandler = MockChangeHandler.defaultHandler()
|
||||
)
|
||||
)
|
||||
|
||||
val child = TestController()
|
||||
val listener = ActivityReferencingLifecycleListener()
|
||||
child.addLifecycleListener(listener)
|
||||
|
||||
val childRouter = parent.getChildRouter(parent.view!!.findViewById(TestController.VIEW_ID))
|
||||
childRouter.setPopRootControllerMode(Router.PopRootControllerMode.POP_ROOT_CONTROLLER_AND_VIEW)
|
||||
childRouter.pushController(
|
||||
child.asTransaction(
|
||||
pushChangeHandler = MockChangeHandler.defaultHandler(),
|
||||
popChangeHandler = MockChangeHandler.defaultHandler()
|
||||
)
|
||||
)
|
||||
activity.router.popCurrentController()
|
||||
|
||||
Assert.assertEquals(listOf(true, true), listener.changeEndReferences)
|
||||
Assert.assertEquals(listOf(true), listener.postCreateViewReferences)
|
||||
Assert.assertEquals(listOf(true), listener.postAttachReferences)
|
||||
Assert.assertEquals(listOf(true), listener.postDetachReferences)
|
||||
Assert.assertEquals(listOf(true), listener.postDestroyViewReferences)
|
||||
Assert.assertEquals(listOf(true), listener.postDestroyReferences)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSingleControllerActivityOnDestroy() {
|
||||
val controller = TestController()
|
||||
val listener = ActivityReferencingLifecycleListener()
|
||||
controller.addLifecycleListener(listener)
|
||||
|
||||
activity.router.pushController(
|
||||
controller.asTransaction(
|
||||
pushChangeHandler = MockChangeHandler.defaultHandler(),
|
||||
popChangeHandler = MockChangeHandler.defaultHandler()
|
||||
)
|
||||
)
|
||||
activityController.pause().stop().destroy()
|
||||
|
||||
Assert.assertEquals(listOf(true), listener.changeEndReferences)
|
||||
Assert.assertEquals(listOf(true), listener.postCreateViewReferences)
|
||||
Assert.assertEquals(listOf(true), listener.postAttachReferences)
|
||||
Assert.assertEquals(listOf(true), listener.postDetachReferences)
|
||||
Assert.assertEquals(listOf(true), listener.postDestroyViewReferences)
|
||||
Assert.assertEquals(listOf(true), listener.postDestroyReferences)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testChildControllerActivityOnDestroy() {
|
||||
val parent = TestController()
|
||||
activity.router.pushController(
|
||||
parent.asTransaction(
|
||||
pushChangeHandler = MockChangeHandler.defaultHandler(),
|
||||
popChangeHandler = MockChangeHandler.defaultHandler()
|
||||
)
|
||||
)
|
||||
|
||||
val child = TestController()
|
||||
val listener = ActivityReferencingLifecycleListener()
|
||||
child.addLifecycleListener(listener)
|
||||
val childRouter = parent.getChildRouter(parent.view!!.findViewById(TestController.VIEW_ID))
|
||||
childRouter.setPopRootControllerMode(Router.PopRootControllerMode.POP_ROOT_CONTROLLER_AND_VIEW)
|
||||
childRouter.pushController(
|
||||
child.asTransaction(
|
||||
pushChangeHandler = MockChangeHandler.defaultHandler(),
|
||||
popChangeHandler = MockChangeHandler.defaultHandler()
|
||||
)
|
||||
)
|
||||
activityController.pause().stop().destroy()
|
||||
|
||||
Assert.assertEquals(listOf(true, 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
-661
@@ -1,661 +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 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());
|
||||
}
|
||||
|
||||
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++;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
+704
@@ -0,0 +1,704 @@
|
||||
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.changeStartCalls++
|
||||
expectedCallState.changeEndCalls++
|
||||
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.transactionIndex, restoredChildTransaction1.transactionIndex);
|
||||
assertEquals(childTransaction1.controller.getInstanceId(), restoredChildTransaction1.controller.getInstanceId());
|
||||
assertEquals(childTransaction2.transactionIndex, restoredChildTransaction2.transactionIndex);
|
||||
assertEquals(childTransaction2.controller.getInstanceId(), restoredChildTransaction2.controller.getInstanceId());
|
||||
|
||||
assertTrue(parent.handleBack());
|
||||
assertEquals(1, childRouter.getBackstackSize());
|
||||
assertEquals(restoredChildTransaction1, childRouter.getBackstack().get(0));
|
||||
|
||||
assertTrue(parent.handleBack());
|
||||
assertEquals(0, childRouter.getBackstackSize());
|
||||
}
|
||||
|
||||
private void assertCalls(CallState callState, TestController controller) {
|
||||
assertEquals("Expected call counts and controller call counts do not match.", callState, controller.currentCallState);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,524 @@
|
||||
package com.bluelinelabs.conductor
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.os.Looper
|
||||
import android.view.KeyEvent
|
||||
import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.SubMenu
|
||||
import com.bluelinelabs.conductor.Controller.RetainViewMode
|
||||
import com.bluelinelabs.conductor.util.AttachFakingFrameLayout
|
||||
import com.bluelinelabs.conductor.util.CallState
|
||||
import com.bluelinelabs.conductor.util.TestActivity
|
||||
import com.bluelinelabs.conductor.util.ViewUtils
|
||||
import org.junit.Assert
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.Robolectric
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import org.robolectric.Shadows.shadowOf
|
||||
import org.robolectric.annotation.Config
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
@Config(manifest = Config.NONE)
|
||||
class ControllerTests {
|
||||
|
||||
private val router = Robolectric.buildActivity(TestActivity::class.java)
|
||||
.setup()
|
||||
.get()
|
||||
.router
|
||||
|
||||
@Test
|
||||
fun testViewRetention() {
|
||||
val controller = TestController()
|
||||
controller.setRouter(router)
|
||||
|
||||
// Test View getting released w/ RELEASE_DETACH
|
||||
controller.retainViewMode = RetainViewMode.RELEASE_DETACH
|
||||
Assert.assertNull(controller.getView())
|
||||
var view = controller.inflate(router.container)
|
||||
Assert.assertNotNull(controller.getView())
|
||||
ViewUtils.reportAttached(view, true)
|
||||
Assert.assertNotNull(controller.getView())
|
||||
ViewUtils.reportAttached(view, false)
|
||||
Assert.assertNull(controller.getView())
|
||||
|
||||
// Test View getting retained w/ RETAIN_DETACH
|
||||
controller.retainViewMode = RetainViewMode.RETAIN_DETACH
|
||||
view = controller.inflate(router.container)
|
||||
Assert.assertNotNull(controller.getView())
|
||||
ViewUtils.reportAttached(view, true)
|
||||
Assert.assertNotNull(controller.getView())
|
||||
ViewUtils.reportAttached(view, false)
|
||||
Assert.assertNotNull(controller.getView())
|
||||
|
||||
// Ensure re-setting RELEASE_DETACH releases
|
||||
controller.retainViewMode = RetainViewMode.RELEASE_DETACH
|
||||
Assert.assertNull(controller.getView())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testActivityResult() {
|
||||
val controller = TestController()
|
||||
val expectedCallState = CallState(true)
|
||||
router.pushController(controller.asTransaction())
|
||||
|
||||
// Ensure that calling onActivityResult w/o requesting a result doesn't do anything
|
||||
router.onActivityResult(1, Activity.RESULT_OK, null)
|
||||
assertCalls(expectedCallState, controller)
|
||||
|
||||
// Ensure starting an activity for result gets us the result back
|
||||
controller.startActivityForResult(Intent("action"), 1)
|
||||
router.onActivityResult(1, Activity.RESULT_OK, null)
|
||||
expectedCallState.onActivityResultCalls++
|
||||
assertCalls(expectedCallState, controller)
|
||||
|
||||
// Ensure requesting a result w/o calling startActivityForResult works
|
||||
controller.registerForActivityResult(2)
|
||||
router.onActivityResult(2, Activity.RESULT_OK, null)
|
||||
expectedCallState.onActivityResultCalls++
|
||||
assertCalls(expectedCallState, controller)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testActivityResultForChild() {
|
||||
val parent = TestController()
|
||||
val child = TestController()
|
||||
router.pushController(parent.asTransaction())
|
||||
val childContainer = parent.view!!.findViewById<AttachFakingFrameLayout>(TestController.VIEW_ID)
|
||||
childContainer.setAttached(true)
|
||||
parent.getChildRouter(childContainer)
|
||||
.setRoot(child.asTransaction())
|
||||
val childExpectedCallState = CallState(true)
|
||||
val parentExpectedCallState = CallState(true)
|
||||
|
||||
// Ensure that calling onActivityResult w/o requesting a result doesn't do anything
|
||||
router.onActivityResult(1, Activity.RESULT_OK, null)
|
||||
assertCalls(childExpectedCallState, child)
|
||||
assertCalls(parentExpectedCallState, parent)
|
||||
|
||||
// Ensure starting an activity for result gets us the result back
|
||||
child.startActivityForResult(Intent("action"), 1)
|
||||
router.onActivityResult(1, Activity.RESULT_OK, null)
|
||||
childExpectedCallState.onActivityResultCalls++
|
||||
assertCalls(childExpectedCallState, child)
|
||||
assertCalls(parentExpectedCallState, parent)
|
||||
|
||||
// Ensure requesting a result w/o calling startActivityForResult works
|
||||
child.registerForActivityResult(2)
|
||||
router.onActivityResult(2, Activity.RESULT_OK, null)
|
||||
childExpectedCallState.onActivityResultCalls++
|
||||
assertCalls(childExpectedCallState, child)
|
||||
assertCalls(parentExpectedCallState, parent)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testPermissionResult() {
|
||||
val requestedPermissions = arrayOf("test")
|
||||
val controller = TestController()
|
||||
val expectedCallState = CallState(true)
|
||||
router.pushController(controller.asTransaction())
|
||||
|
||||
// Ensure that calling handleRequestedPermission w/o requesting a result doesn't do anything
|
||||
router.onRequestPermissionsResult("anotherId", 1, requestedPermissions, intArrayOf(1))
|
||||
assertCalls(expectedCallState, controller)
|
||||
|
||||
// Ensure requesting the permission gets us the result back
|
||||
try {
|
||||
controller.requestPermissions(requestedPermissions, 1)
|
||||
} catch (ignored: NoSuchMethodError) { }
|
||||
|
||||
router.onRequestPermissionsResult(
|
||||
controller.instanceId,
|
||||
1,
|
||||
requestedPermissions,
|
||||
intArrayOf(1)
|
||||
)
|
||||
expectedCallState.onRequestPermissionsResultCalls++
|
||||
assertCalls(expectedCallState, controller)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testPermissionResultForChild() {
|
||||
val requestedPermissions = arrayOf("test")
|
||||
val parent = TestController()
|
||||
val child = TestController()
|
||||
router.pushController(parent.asTransaction())
|
||||
val childContainer = parent.view!!.findViewById<AttachFakingFrameLayout>(TestController.VIEW_ID)
|
||||
childContainer.setAttached(true)
|
||||
parent.getChildRouter(childContainer)
|
||||
.setRoot(child.asTransaction())
|
||||
val childExpectedCallState = CallState(true)
|
||||
val parentExpectedCallState = CallState(true)
|
||||
|
||||
// Ensure that calling handleRequestedPermission w/o requesting a result doesn't do anything
|
||||
router.onRequestPermissionsResult("anotherId", 1, requestedPermissions, intArrayOf(1))
|
||||
assertCalls(childExpectedCallState, child)
|
||||
assertCalls(parentExpectedCallState, parent)
|
||||
|
||||
// Ensure requesting the permission gets us the result back
|
||||
try {
|
||||
child.requestPermissions(requestedPermissions, 1)
|
||||
} catch (ignored: NoSuchMethodError) { }
|
||||
|
||||
router.onRequestPermissionsResult(child.instanceId, 1, requestedPermissions, intArrayOf(1))
|
||||
childExpectedCallState.onRequestPermissionsResultCalls++
|
||||
assertCalls(childExpectedCallState, child)
|
||||
assertCalls(parentExpectedCallState, parent)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOptionsMenu() {
|
||||
val controller = TestController()
|
||||
val expectedCallState = CallState(true)
|
||||
router.pushController(controller.asTransaction())
|
||||
|
||||
// Ensure that calling onCreateOptionsMenu w/o declaring that we have one doesn't do anything
|
||||
router.onCreateOptionsMenu(menu(), menuInflater(router.activity!!))
|
||||
assertCalls(expectedCallState, controller)
|
||||
|
||||
// Ensure calling onCreateOptionsMenu with a menu works
|
||||
controller.setHasOptionsMenu(true)
|
||||
expectedCallState.createOptionsMenuCalls++
|
||||
assertCalls(expectedCallState, controller)
|
||||
|
||||
// Ensure it'll still get called back next time onCreateOptionsMenu is called
|
||||
router.onCreateOptionsMenu(menu(), menuInflater(router.activity!!))
|
||||
expectedCallState.createOptionsMenuCalls++
|
||||
assertCalls(expectedCallState, controller)
|
||||
|
||||
// Ensure we stop getting them when we hide it
|
||||
controller.setOptionsMenuHidden(true)
|
||||
router.onCreateOptionsMenu(menu(), menuInflater(router.activity!!))
|
||||
assertCalls(expectedCallState, controller)
|
||||
|
||||
// Ensure we get the callback them when we un-hide it
|
||||
controller.setOptionsMenuHidden(false)
|
||||
expectedCallState.createOptionsMenuCalls++
|
||||
assertCalls(expectedCallState, controller)
|
||||
|
||||
router.onCreateOptionsMenu(menu(), menuInflater(router.activity!!))
|
||||
expectedCallState.createOptionsMenuCalls++
|
||||
assertCalls(expectedCallState, controller)
|
||||
|
||||
// Ensure we don't get the callback when we no longer have a menu
|
||||
controller.setHasOptionsMenu(false)
|
||||
router.onCreateOptionsMenu(menu(), menuInflater(router.activity!!))
|
||||
assertCalls(expectedCallState, controller)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOptionsMenuForChild() {
|
||||
val parent = TestController()
|
||||
val child = TestController()
|
||||
router.pushController(parent.asTransaction())
|
||||
val childContainer = parent.view!!.findViewById<AttachFakingFrameLayout>(TestController.VIEW_ID)
|
||||
childContainer.setAttached(true)
|
||||
parent.getChildRouter(childContainer)
|
||||
.setRoot(child.asTransaction())
|
||||
val childExpectedCallState = CallState(true)
|
||||
val parentExpectedCallState = CallState(true)
|
||||
|
||||
// Ensure that calling onCreateOptionsMenu w/o declaring that we have one doesn't do anything
|
||||
router.onCreateOptionsMenu(menu(), menuInflater(router.activity!!))
|
||||
assertCalls(childExpectedCallState, child)
|
||||
assertCalls(parentExpectedCallState, parent)
|
||||
|
||||
// Ensure calling onCreateOptionsMenu with a menu works
|
||||
child.setHasOptionsMenu(true)
|
||||
childExpectedCallState.createOptionsMenuCalls++
|
||||
assertCalls(childExpectedCallState, child)
|
||||
assertCalls(parentExpectedCallState, parent)
|
||||
|
||||
// Ensure it'll still get called back next time onCreateOptionsMenu is called
|
||||
router.onCreateOptionsMenu(menu(), menuInflater(router.activity!!))
|
||||
childExpectedCallState.createOptionsMenuCalls++
|
||||
assertCalls(childExpectedCallState, child)
|
||||
assertCalls(parentExpectedCallState, parent)
|
||||
|
||||
// Ensure we stop getting them when we hide it
|
||||
child.setOptionsMenuHidden(true)
|
||||
router.onCreateOptionsMenu(menu(), menuInflater(router.activity!!))
|
||||
assertCalls(childExpectedCallState, child)
|
||||
assertCalls(parentExpectedCallState, parent)
|
||||
|
||||
// Ensure we get the callback them when we un-hide it
|
||||
child.setOptionsMenuHidden(false)
|
||||
childExpectedCallState.createOptionsMenuCalls++
|
||||
assertCalls(childExpectedCallState, child)
|
||||
assertCalls(parentExpectedCallState, parent)
|
||||
|
||||
router.onCreateOptionsMenu(menu(), menuInflater(router.activity!!))
|
||||
childExpectedCallState.createOptionsMenuCalls++
|
||||
assertCalls(childExpectedCallState, child)
|
||||
assertCalls(parentExpectedCallState, parent)
|
||||
|
||||
// Ensure we don't get the callback when we no longer have a menu
|
||||
child.setHasOptionsMenu(false)
|
||||
router.onCreateOptionsMenu(menu(), menuInflater(router.activity!!))
|
||||
assertCalls(childExpectedCallState, child)
|
||||
assertCalls(parentExpectedCallState, parent)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testAddRemoveChildControllers() {
|
||||
val parent = TestController()
|
||||
val child1 = TestController()
|
||||
val child2 = TestController()
|
||||
router.pushController(parent.asTransaction())
|
||||
Assert.assertEquals(0, parent.childRouters.size)
|
||||
Assert.assertNull(child1.parentController)
|
||||
Assert.assertNull(child2.parentController)
|
||||
|
||||
var childRouter = parent.getChildRouter(parent.view!!.findViewById(TestController.VIEW_ID))
|
||||
childRouter.setPopRootControllerMode(Router.PopRootControllerMode.POP_ROOT_CONTROLLER_AND_VIEW)
|
||||
childRouter.setRoot(child1.asTransaction())
|
||||
Assert.assertEquals(1, parent.childRouters.size)
|
||||
Assert.assertEquals(childRouter, parent.childRouters[0])
|
||||
Assert.assertEquals(1, childRouter.backstackSize)
|
||||
Assert.assertEquals(child1, childRouter.controllers[0])
|
||||
Assert.assertEquals(parent, child1.parentController)
|
||||
Assert.assertNull(child2.parentController)
|
||||
|
||||
childRouter = parent.getChildRouter(parent.view!!.findViewById(TestController.VIEW_ID))
|
||||
childRouter.pushController(child2.asTransaction())
|
||||
Assert.assertEquals(1, parent.childRouters.size)
|
||||
Assert.assertEquals(childRouter, parent.childRouters[0])
|
||||
Assert.assertEquals(2, childRouter.backstackSize)
|
||||
Assert.assertEquals(child1, childRouter.controllers[0])
|
||||
Assert.assertEquals(child2, childRouter.controllers[1])
|
||||
Assert.assertEquals(parent, child1.parentController)
|
||||
Assert.assertEquals(parent, child2.parentController)
|
||||
|
||||
childRouter.popController(child2)
|
||||
Assert.assertEquals(1, parent.childRouters.size)
|
||||
Assert.assertEquals(childRouter, parent.childRouters[0])
|
||||
Assert.assertEquals(1, childRouter.backstackSize)
|
||||
Assert.assertEquals(child1, childRouter.controllers[0])
|
||||
Assert.assertEquals(parent, child1.parentController)
|
||||
Assert.assertNull(child2.parentController)
|
||||
|
||||
childRouter.popController(child1)
|
||||
shadowOf(Looper.getMainLooper()).idle()
|
||||
Assert.assertEquals(1, parent.childRouters.size)
|
||||
Assert.assertEquals(childRouter, parent.childRouters[0])
|
||||
Assert.assertEquals(0, childRouter.backstackSize)
|
||||
Assert.assertNull(child1.parentController)
|
||||
Assert.assertNull(child2.parentController)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testAddRemoveChildRouters() {
|
||||
val parent = TestController()
|
||||
val child1 = TestController()
|
||||
val child2 = TestController()
|
||||
router.pushController(parent.asTransaction())
|
||||
Assert.assertEquals(0, parent.childRouters.size)
|
||||
Assert.assertNull(child1.parentController)
|
||||
Assert.assertNull(child2.parentController)
|
||||
|
||||
val childRouter1 = parent.getChildRouter(parent.view!!.findViewById(TestController.CHILD_VIEW_ID_1))
|
||||
val childRouter2 = parent.getChildRouter(parent.view!!.findViewById(TestController.CHILD_VIEW_ID_2))
|
||||
childRouter1.setRoot(child1.asTransaction())
|
||||
childRouter2.setRoot(child2.asTransaction())
|
||||
Assert.assertEquals(2, parent.childRouters.size)
|
||||
Assert.assertEquals(childRouter1, parent.childRouters[0])
|
||||
Assert.assertEquals(childRouter2, parent.childRouters[1])
|
||||
Assert.assertEquals(1, childRouter1.backstackSize)
|
||||
Assert.assertEquals(1, childRouter2.backstackSize)
|
||||
Assert.assertEquals(child1, childRouter1.controllers[0])
|
||||
Assert.assertEquals(child2, childRouter2.controllers[0])
|
||||
Assert.assertEquals(parent, child1.parentController)
|
||||
Assert.assertEquals(parent, child2.parentController)
|
||||
|
||||
parent.removeChildRouter(childRouter2)
|
||||
shadowOf(Looper.getMainLooper()).idle()
|
||||
Assert.assertEquals(1, parent.childRouters.size)
|
||||
Assert.assertEquals(childRouter1, parent.childRouters[0])
|
||||
Assert.assertEquals(1, childRouter1.backstackSize)
|
||||
Assert.assertEquals(0, childRouter2.backstackSize)
|
||||
Assert.assertEquals(child1, childRouter1.controllers[0])
|
||||
Assert.assertEquals(parent, child1.parentController)
|
||||
Assert.assertNull(child2.parentController)
|
||||
parent.removeChildRouter(childRouter1)
|
||||
Assert.assertEquals(0, parent.childRouters.size)
|
||||
Assert.assertEquals(0, childRouter1.backstackSize)
|
||||
Assert.assertEquals(0, childRouter2.backstackSize)
|
||||
Assert.assertNull(child1.parentController)
|
||||
Assert.assertNull(child2.parentController)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testRestoredChildRouterBackstack() {
|
||||
val parent = TestController()
|
||||
router.pushController(parent.asTransaction())
|
||||
ViewUtils.reportAttached(parent.view, true)
|
||||
|
||||
val childTransaction1 = TestController().asTransaction()
|
||||
val childTransaction2 = TestController().asTransaction()
|
||||
var childRouter = parent.getChildRouter(parent.view!!.findViewById(TestController.CHILD_VIEW_ID_1))
|
||||
childRouter.setPopRootControllerMode(Router.PopRootControllerMode.POP_ROOT_CONTROLLER_AND_VIEW)
|
||||
childRouter.setRoot(childTransaction1)
|
||||
childRouter.pushController(childTransaction2)
|
||||
val savedState = Bundle()
|
||||
childRouter.saveInstanceState(savedState)
|
||||
parent.removeChildRouter(childRouter)
|
||||
childRouter = parent.getChildRouter(parent.view!!.findViewById(TestController.CHILD_VIEW_ID_1))
|
||||
Assert.assertEquals(0, childRouter.backstackSize)
|
||||
|
||||
childRouter.restoreInstanceState(savedState)
|
||||
childRouter.rebindIfNeeded()
|
||||
Assert.assertEquals(2, childRouter.backstackSize)
|
||||
val restoredChildTransaction1 = childRouter.getBackstack()[0]
|
||||
val restoredChildTransaction2 = childRouter.getBackstack()[1]
|
||||
Assert.assertEquals(
|
||||
childTransaction1.transactionIndex,
|
||||
restoredChildTransaction1.transactionIndex
|
||||
)
|
||||
Assert.assertEquals(
|
||||
childTransaction1.controller.getInstanceId(),
|
||||
restoredChildTransaction1.controller.getInstanceId()
|
||||
)
|
||||
Assert.assertEquals(
|
||||
childTransaction2.transactionIndex,
|
||||
restoredChildTransaction2.transactionIndex
|
||||
)
|
||||
Assert.assertEquals(
|
||||
childTransaction2.controller.getInstanceId(),
|
||||
restoredChildTransaction2.controller.getInstanceId()
|
||||
)
|
||||
Assert.assertTrue(parent.handleBack())
|
||||
Assert.assertEquals(1, childRouter.backstackSize)
|
||||
Assert.assertEquals(restoredChildTransaction1, childRouter.getBackstack()[0])
|
||||
Assert.assertTrue(parent.handleBack())
|
||||
Assert.assertEquals(0, childRouter.backstackSize)
|
||||
}
|
||||
|
||||
private fun assertCalls(callState: CallState, controller: TestController) {
|
||||
shadowOf(Looper.getMainLooper()).idle()
|
||||
|
||||
Assert.assertEquals(
|
||||
"Expected call counts and controller call counts do not match.",
|
||||
callState,
|
||||
controller.currentCallState
|
||||
)
|
||||
}
|
||||
|
||||
private fun menu(): Menu {
|
||||
return object : Menu {
|
||||
override fun add(p0: CharSequence?): MenuItem {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun add(p0: Int): MenuItem {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun add(p0: Int, p1: Int, p2: Int, p3: CharSequence?): MenuItem {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun add(p0: Int, p1: Int, p2: Int, p3: Int): MenuItem {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun addSubMenu(p0: CharSequence?): SubMenu {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun addSubMenu(p0: Int): SubMenu {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun addSubMenu(p0: Int, p1: Int, p2: Int, p3: CharSequence?): SubMenu {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun addSubMenu(p0: Int, p1: Int, p2: Int, p3: Int): SubMenu {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun addIntentOptions(
|
||||
p0: Int,
|
||||
p1: Int,
|
||||
p2: Int,
|
||||
p3: ComponentName?,
|
||||
p4: Array<out Intent>?,
|
||||
p5: Intent?,
|
||||
p6: Int,
|
||||
p7: Array<out MenuItem>?
|
||||
): Int {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun removeItem(p0: Int) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun removeGroup(p0: Int) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun clear() {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun setGroupCheckable(p0: Int, p1: Boolean, p2: Boolean) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun setGroupVisible(p0: Int, p1: Boolean) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun setGroupEnabled(p0: Int, p1: Boolean) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun hasVisibleItems(): Boolean {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun findItem(p0: Int): MenuItem {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun size(): Int {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun getItem(p0: Int): MenuItem {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun performShortcut(p0: Int, p1: KeyEvent?, p2: Int): Boolean {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun isShortcutKey(p0: Int, p1: KeyEvent?): Boolean {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun performIdentifierAction(p0: Int, p1: Int): Boolean {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun setQwertyMode(p0: Boolean) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun menuInflater(context: Context): MenuInflater {
|
||||
return MenuInflater(context)
|
||||
}
|
||||
}
|
||||
@@ -4,12 +4,14 @@ 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;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public class ControllerTransactionTests {
|
||||
|
||||
@Test
|
||||
@@ -23,10 +25,9 @@ public class ControllerTransactionTests {
|
||||
|
||||
RouterTransaction restoredTransaction = new RouterTransaction(bundle);
|
||||
|
||||
assertEquals(transaction.controller.getClass(), restoredTransaction.controller.getClass());
|
||||
assertEquals(transaction.controller().getClass(), restoredTransaction.controller().getClass());
|
||||
assertEquals(transaction.pushChangeHandler().getClass(), restoredTransaction.pushChangeHandler().getClass());
|
||||
assertEquals(transaction.popChangeHandler().getClass(), restoredTransaction.popChangeHandler().getClass());
|
||||
assertEquals(transaction.tag(), restoredTransaction.tag());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,295 +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 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());
|
||||
}
|
||||
|
||||
private void sleepWakeDevice() {
|
||||
activityProxy.saveInstanceState(new Bundle()).pause();
|
||||
activityProxy.resume();
|
||||
}
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user