Compare commits
48 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 |
@@ -31,8 +31,8 @@ jobs:
|
||||
java-version: '11'
|
||||
- name: Grant execute permission for gradlew
|
||||
run: chmod +x gradlew
|
||||
- name: Test with Gradle
|
||||
run: ./gradlew clean uploadArchives
|
||||
- name: Release with Gradle
|
||||
run: ./gradlew clean publishAllPublicationsToMavenCentral
|
||||
env:
|
||||
ORG_GRADLE_PROJECT_SONATYPE_NEXUS_USERNAME: ${{ secrets.SONATYPE_NEXUS_USERNAME }}
|
||||
ORG_GRADLE_PROJECT_SONATYPE_NEXUS_PASSWORD: ${{ secrets.SONATYPE_NEXUS_PASSWORD }}
|
||||
ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.SONATYPE_NEXUS_USERNAME }}
|
||||
ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.SONATYPE_NEXUS_PASSWORD }}
|
||||
|
||||
+3
-1
@@ -8,7 +8,9 @@
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
*.idea/dictionaries
|
||||
/.idea/*
|
||||
!/.idea/codeStyles/
|
||||
!/.idea/scopes/
|
||||
classes
|
||||
gen-external-apklibs
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -20,30 +20,29 @@ Conductor is architecture-agnostic and does not try to force any design decision
|
||||
## Installation
|
||||
|
||||
```gradle
|
||||
implementation 'com.bluelinelabs:conductor:3.1.0'
|
||||
def conductorVersion = '3.2.0'
|
||||
|
||||
implementation "com.bluelinelabs:conductor:$conductorVersion"
|
||||
|
||||
// AndroidX Transition change handlers:
|
||||
implementation 'com.bluelinelabs:conductor-androidx-transition:3.1.0'
|
||||
implementation "com.bluelinelabs:conductor-androidx-transition:$conductorVersion"
|
||||
|
||||
// ViewPager PagerAdapter:
|
||||
implementation 'com.bluelinelabs:conductor-viewpager:3.1.0'
|
||||
implementation "com.bluelinelabs:conductor-viewpager:$conductorVersion"
|
||||
|
||||
// ViewPager2 Adapter:
|
||||
implementation 'com.bluelinelabs:conductor-viewpager2:3.1.0'
|
||||
|
||||
// RxJava2 lifecycle support:
|
||||
implementation 'com.bluelinelabs:conductor-rxlifecycle2:3.1.0'
|
||||
implementation "com.bluelinelabs:conductor-viewpager2:$conductorVersion"
|
||||
|
||||
// RxJava2 Autodispose support:
|
||||
implementation 'com.bluelinelabs:conductor-autodispose:3.1.0'
|
||||
implementation "com.bluelinelabs:conductor-autodispose:$conductorVersion"
|
||||
|
||||
// Lifecycle-aware Controllers (architecture components):
|
||||
implementation 'com.bluelinelabs:conductor-archlifecycle:3.1.0'
|
||||
implementation "com.bluelinelabs:conductor-archlifecycle:$conductorVersion"
|
||||
```
|
||||
|
||||
**SNAPSHOT**
|
||||
|
||||
Just use `3.1.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()));
|
||||
}
|
||||
|
||||
+19
-8
@@ -1,15 +1,12 @@
|
||||
buildscript {
|
||||
apply from: rootProject.file('dependencies.gradle')
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
google()
|
||||
}
|
||||
dependencies {
|
||||
classpath "com.android.tools.build:gradle:$agpVersion"
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
|
||||
classpath "com.vanniktech:gradle-maven-publish-plugin:$mvnPublishVersion"
|
||||
classpath "org.jetbrains.dokka:dokka-gradle-plugin:$dokkaVersion"
|
||||
classpath libs.agp
|
||||
classpath libs.kotlin.plugin
|
||||
classpath libs.dokka
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +15,20 @@ allprojects {
|
||||
mavenCentral()
|
||||
google()
|
||||
}
|
||||
}
|
||||
|
||||
apply from: rootProject.file('dependencies.gradle')
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
apply plugin: 'java-library'
|
||||
apply plugin: 'kotlin'
|
||||
|
||||
configurations {
|
||||
lintChecks
|
||||
libs.lint.checks
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly rootProject.ext.lintapi
|
||||
compileOnly rootProject.ext.lintchecks
|
||||
compileOnly libs.lint.api
|
||||
compileOnly libs.lint.checks
|
||||
compileOnly libs.kotlin.stdlib
|
||||
|
||||
testImplementation rootProject.ext.junit
|
||||
testImplementation rootProject.ext.lint
|
||||
testImplementation rootProject.ext.lintTests
|
||||
testImplementation libs.junit
|
||||
testImplementation libs.lint
|
||||
testImplementation libs.lint.tests
|
||||
}
|
||||
|
||||
jar {
|
||||
@@ -18,5 +19,3 @@ jar {
|
||||
attributes('Lint-Registry-v2': 'com.bluelinelabs.conductor.lint.IssueRegistry')
|
||||
}
|
||||
}
|
||||
|
||||
apply from: rootProject.file('dependencies.gradle')
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
apply plugin: 'com.android.library'
|
||||
plugins {
|
||||
id("com.android.library")
|
||||
alias(libs.plugins.mvnpublish)
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion rootProject.ext.compileSdkVersion
|
||||
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 plugin: "com.vanniktech.maven.publish"
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
apply plugin: 'com.android.library'
|
||||
plugins {
|
||||
id("com.android.library")
|
||||
alias(libs.plugins.mvnpublish)
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion rootProject.ext.compileSdkVersion
|
||||
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')
|
||||
}
|
||||
|
||||
ext.artifactId = 'conductor-arch-components-lifecycle'
|
||||
|
||||
apply from: rootProject.file('dependencies.gradle')
|
||||
apply plugin: "com.vanniktech.maven.publish"
|
||||
|
||||
+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;
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
apply from: rootProject.file('dependencies.gradle')
|
||||
|
||||
apply plugin: 'com.android.library'
|
||||
apply plugin: "com.vanniktech.maven.publish"
|
||||
plugins {
|
||||
id("com.android.library")
|
||||
alias(libs.plugins.mvnpublish)
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion rootProject.ext.compileSdkVersion
|
||||
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,25 +0,0 @@
|
||||
apply from: rootProject.file('dependencies.gradle')
|
||||
|
||||
apply plugin: 'com.android.library'
|
||||
apply plugin: "com.vanniktech.maven.publish"
|
||||
|
||||
android {
|
||||
compileSdkVersion rootProject.ext.compileSdkVersion
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode Integer.parseInt(project.VERSION_CODE)
|
||||
versionName project.VERSION_NAME
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
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.");
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,26 +1,26 @@
|
||||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'kotlin-android'
|
||||
plugins {
|
||||
id("com.android.library")
|
||||
id("kotlin-android")
|
||||
alias(libs.plugins.mvnpublish)
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion rootProject.ext.compileSdkVersion
|
||||
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.robolectric
|
||||
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 plugin: "com.vanniktech.maven.publish"
|
||||
|
||||
+2
-1
@@ -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);
|
||||
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply plugin: 'kotlin-android-extensions'
|
||||
plugins {
|
||||
id("com.android.library")
|
||||
id("kotlin-android")
|
||||
id("kotlin-parcelize")
|
||||
alias(libs.plugins.mvnpublish)
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion rootProject.ext.compileSdkVersion
|
||||
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
|
||||
}
|
||||
@@ -20,15 +23,12 @@ android {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
testImplementation rootProject.ext.junit
|
||||
testImplementation rootProject.ext.robolectric
|
||||
testImplementation libs.junit
|
||||
testImplementation libs.robolectric
|
||||
|
||||
implementation rootProject.ext.androidxAppCompat
|
||||
implementation rootProject.ext.androidxViewPager2
|
||||
implementation libs.androidx.appcompat
|
||||
implementation libs.androidx.viewpager2
|
||||
implementation project(':conductor')
|
||||
}
|
||||
|
||||
ext.artifactId = 'conductor-viewpager2'
|
||||
|
||||
apply from: rootProject.file('dependencies.gradle')
|
||||
apply plugin: "com.vanniktech.maven.publish"
|
||||
|
||||
+7
-4
@@ -10,7 +10,7 @@ import androidx.viewpager2.adapter.StatefulAdapter
|
||||
import androidx.viewpager2.widget.ViewPager2
|
||||
import com.bluelinelabs.conductor.Controller
|
||||
import com.bluelinelabs.conductor.Router
|
||||
import kotlinx.android.parcel.Parcelize
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
/**
|
||||
* An ViewPager2 adapter that uses Routers as pages
|
||||
@@ -48,7 +48,8 @@ abstract class RouterStateAdapter(private val host: Controller) :
|
||||
}
|
||||
|
||||
private fun inferViewPager(recyclerView: RecyclerView): ViewPager2 {
|
||||
return recyclerView.parent as? ViewPager2 ?: error("Expected ViewPager2 instance. Got: ${recyclerView.parent}")
|
||||
return recyclerView.parent as? ViewPager2
|
||||
?: error("Expected ViewPager2 instance. Got: ${recyclerView.parent}")
|
||||
}
|
||||
|
||||
override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {
|
||||
@@ -119,7 +120,8 @@ abstract class RouterStateAdapter(private val host: Controller) :
|
||||
|
||||
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()
|
||||
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])
|
||||
@@ -152,7 +154,8 @@ abstract class RouterStateAdapter(private val host: Controller) :
|
||||
|
||||
private fun attachRouter(holder: RouterViewHolder, position: Int) {
|
||||
val itemId = getItemId(position)
|
||||
val router = host.getChildRouter(holder.container, "$itemId")
|
||||
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
|
||||
|
||||
+15
-14
@@ -1,12 +1,15 @@
|
||||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'kotlin-android'
|
||||
plugins {
|
||||
id("com.android.library")
|
||||
id("kotlin-android")
|
||||
alias(libs.plugins.mvnpublish)
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion rootProject.ext.compileSdkVersion
|
||||
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'
|
||||
@@ -14,19 +17,17 @@ android {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation savedState
|
||||
testImplementation rootProject.ext.junit
|
||||
testImplementation rootProject.ext.robolectric
|
||||
implementation libs.androidx.savedstate.ktx
|
||||
testImplementation libs.junit
|
||||
testImplementation libs.robolectric
|
||||
testImplementation libs.kotest
|
||||
|
||||
implementation archComponentsLifecycle
|
||||
implementation libs.androidx.lifecycle.runtime
|
||||
|
||||
api rootProject.ext.androidxAnnotations
|
||||
api kotlinStd
|
||||
api libs.androidx.annotation
|
||||
api libs.kotlin.stdlib
|
||||
|
||||
lintPublish project(':conductor-lint')
|
||||
}
|
||||
|
||||
ext.artifactId = 'conductor'
|
||||
|
||||
apply from: rootProject.file('dependencies.gradle')
|
||||
apply plugin: "com.vanniktech.maven.publish"
|
||||
|
||||
@@ -1,118 +0,0 @@
|
||||
package com.bluelinelabs.conductor;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Deque;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
class Backstack implements Iterable<RouterTransaction> {
|
||||
|
||||
private static final String KEY_ENTRIES = "Backstack.entries";
|
||||
|
||||
private final Deque<RouterTransaction> backstack = new ArrayDeque<>();
|
||||
|
||||
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
|
||||
boolean isEmpty() {
|
||||
return backstack.isEmpty();
|
||||
}
|
||||
|
||||
int size() {
|
||||
return backstack.size();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
RouterTransaction root() {
|
||||
return backstack.size() > 0 ? backstack.getLast() : null;
|
||||
}
|
||||
|
||||
@Override @NonNull
|
||||
public Iterator<RouterTransaction> iterator() {
|
||||
return backstack.iterator();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
Iterator<RouterTransaction> reverseIterator() {
|
||||
return backstack.descendingIterator();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
List<RouterTransaction> popTo(@NonNull RouterTransaction transaction) {
|
||||
List<RouterTransaction> popped = new ArrayList<>();
|
||||
if (backstack.contains(transaction)) {
|
||||
while (backstack.peek() != transaction) {
|
||||
RouterTransaction poppedTransaction = pop();
|
||||
popped.add(poppedTransaction);
|
||||
}
|
||||
} else {
|
||||
throw new RuntimeException("Tried to pop to a transaction that was not on the back stack");
|
||||
}
|
||||
return popped;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
RouterTransaction pop() {
|
||||
RouterTransaction popped = backstack.pop();
|
||||
popped.controller().destroy();
|
||||
return popped;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
RouterTransaction peek() {
|
||||
return backstack.peek();
|
||||
}
|
||||
|
||||
void push(@NonNull RouterTransaction transaction) {
|
||||
backstack.push(transaction);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
List<RouterTransaction> popAll() {
|
||||
List<RouterTransaction> list = new ArrayList<>();
|
||||
while (!isEmpty()) {
|
||||
list.add(pop());
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
void setBackstack(@NonNull List<RouterTransaction> backstack) {
|
||||
this.backstack.clear();
|
||||
for (RouterTransaction transaction : backstack) {
|
||||
this.backstack.push(transaction);
|
||||
}
|
||||
}
|
||||
|
||||
boolean contains(@NonNull Controller controller) {
|
||||
for (RouterTransaction transaction : backstack) {
|
||||
if (controller == transaction.controller()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void saveInstanceState(@NonNull Bundle outState) {
|
||||
ArrayList<Bundle> entryBundles = new ArrayList<>(backstack.size());
|
||||
for (RouterTransaction entry : backstack) {
|
||||
entryBundles.add(entry.saveInstanceState());
|
||||
}
|
||||
|
||||
outState.putParcelableArrayList(KEY_ENTRIES, entryBundles);
|
||||
}
|
||||
|
||||
void restoreInstanceState(@NonNull Bundle savedInstanceState) {
|
||||
ArrayList<Bundle> entryBundles = savedInstanceState.getParcelableArrayList(KEY_ENTRIES);
|
||||
if (entryBundles != null) {
|
||||
Collections.reverse(entryBundles);
|
||||
for (Bundle transactionBundle : entryBundles) {
|
||||
backstack.push(new RouterTransaction(transactionBundle));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package com.bluelinelabs.conductor
|
||||
|
||||
import android.os.Bundle
|
||||
import java.util.*
|
||||
|
||||
internal class Backstack : Iterable<RouterTransaction> {
|
||||
|
||||
private val backstack: Deque<RouterTransaction> = ArrayDeque()
|
||||
|
||||
val isEmpty: Boolean get() = backstack.isEmpty()
|
||||
|
||||
val size: Int get() = backstack.size
|
||||
|
||||
fun root(): RouterTransaction? = backstack.lastOrNull()
|
||||
|
||||
override fun iterator(): MutableIterator<RouterTransaction> {
|
||||
return backstack.iterator()
|
||||
}
|
||||
|
||||
fun reverseIterator(): Iterator<RouterTransaction> = backstack.descendingIterator()
|
||||
|
||||
fun popTo(transaction: RouterTransaction): List<RouterTransaction> {
|
||||
if (transaction in backstack) {
|
||||
val popped: MutableList<RouterTransaction> = ArrayList()
|
||||
while (backstack.peek() != transaction) {
|
||||
val poppedTransaction = pop()
|
||||
popped.add(poppedTransaction)
|
||||
}
|
||||
return popped
|
||||
} else {
|
||||
throw RuntimeException("Tried to pop to a transaction that was not on the back stack")
|
||||
}
|
||||
}
|
||||
|
||||
fun pop(): RouterTransaction {
|
||||
return backstack.pop().also {
|
||||
it.controller.destroy()
|
||||
}
|
||||
}
|
||||
|
||||
fun peek(): RouterTransaction? = backstack.peek()
|
||||
|
||||
fun push(transaction: RouterTransaction) {
|
||||
backstack.push(transaction)
|
||||
}
|
||||
|
||||
fun popAll(): List<RouterTransaction> {
|
||||
val list: MutableList<RouterTransaction> = ArrayList()
|
||||
while (!isEmpty) {
|
||||
list.add(pop())
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
fun setBackstack(backstack: List<RouterTransaction>) {
|
||||
this.backstack.clear()
|
||||
backstack.forEach { transaction ->
|
||||
this.backstack.push(transaction)
|
||||
}
|
||||
}
|
||||
|
||||
operator fun contains(controller: Controller): Boolean {
|
||||
return backstack.any {
|
||||
it.controller == controller
|
||||
}
|
||||
}
|
||||
|
||||
fun saveInstanceState(outState: Bundle) {
|
||||
val entryBundles = ArrayList<Bundle>(backstack.size)
|
||||
backstack.mapTo(entryBundles) {
|
||||
it.saveInstanceState()
|
||||
}
|
||||
outState.putParcelableArrayList(KEY_ENTRIES, entryBundles)
|
||||
}
|
||||
|
||||
fun restoreInstanceState(savedInstanceState: Bundle) {
|
||||
val entryBundles = savedInstanceState.getParcelableArrayList<Bundle?>(KEY_ENTRIES)
|
||||
if (entryBundles != null) {
|
||||
entryBundles.reverse()
|
||||
for (transactionBundle in entryBundles) {
|
||||
backstack.push(RouterTransaction(transactionBundle!!))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val KEY_ENTRIES = "Backstack.entries"
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,6 @@ import android.content.res.Resources;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcelable;
|
||||
import android.text.TextUtils;
|
||||
import android.util.SparseArray;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
@@ -215,6 +214,23 @@ public abstract class Controller {
|
||||
*/
|
||||
@Nullable
|
||||
public final Router getChildRouter(@NonNull ViewGroup container, @Nullable String tag, boolean createIfNeeded) {
|
||||
return getChildRouter(container, tag, createIfNeeded, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the child {@link Router} for the given container/tag combination. Note that multiple
|
||||
* routers should not exist in the same container unless a lot of care is taken to maintain order
|
||||
* between them. Avoid using the same container unless you have a great reason to do so (ex: ViewPagers).
|
||||
* The only time this method will return {@code null} is when the child router does not exist prior
|
||||
* to calling this method and the createIfNeeded parameter is set to false.
|
||||
*
|
||||
* @param container The ViewGroup that hosts the child Router
|
||||
* @param tag The router's tag or {@code null} if none is needed
|
||||
* @param createIfNeeded If true, a router will be created if one does not yet exist. Else {@code null} will be returned in this case.
|
||||
* @param boundToHostContainerId If true, a router will only ever rebind with a container with the same view id on state restoration. Note that this must be set to true if the tag is null.
|
||||
*/
|
||||
@Nullable
|
||||
public final Router getChildRouter(@NonNull ViewGroup container, @Nullable String tag, boolean createIfNeeded, boolean boundToHostContainerId) {
|
||||
@IdRes final int containerId = container.getId();
|
||||
if (containerId == View.NO_ID) {
|
||||
throw new IllegalStateException("You must set an id on your container.");
|
||||
@@ -222,7 +238,7 @@ public abstract class Controller {
|
||||
|
||||
ControllerHostedRouter childRouter = null;
|
||||
for (ControllerHostedRouter router : childRouters) {
|
||||
if (router.getHostId() == containerId && TextUtils.equals(tag, router.getTag())) {
|
||||
if (router.matches(containerId, tag)) {
|
||||
childRouter = router;
|
||||
break;
|
||||
}
|
||||
@@ -230,7 +246,7 @@ public abstract class Controller {
|
||||
|
||||
if (childRouter == null) {
|
||||
if (createIfNeeded) {
|
||||
childRouter = new ControllerHostedRouter(container.getId(), tag);
|
||||
childRouter = new ControllerHostedRouter(container.getId(), tag, boundToHostContainerId);
|
||||
childRouter.setHostContainer(this, container);
|
||||
childRouters.add(childRouter);
|
||||
|
||||
@@ -693,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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -856,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();
|
||||
@@ -908,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) {
|
||||
@@ -971,7 +995,7 @@ public abstract class Controller {
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
@@ -981,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);
|
||||
}
|
||||
@@ -1044,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) {
|
||||
@@ -1121,20 +1153,13 @@ public abstract class Controller {
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
@@ -1172,7 +1197,7 @@ public abstract class Controller {
|
||||
}
|
||||
|
||||
if (!attached) {
|
||||
removeViewReference();
|
||||
removeViewReference(null);
|
||||
} else if (removeViews) {
|
||||
detach(view, true, false);
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import android.content.Intent;
|
||||
import android.content.IntentSender;
|
||||
import android.content.IntentSender.SendIntentException;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.IdRes;
|
||||
@@ -22,18 +23,24 @@ 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 setHostController(@NonNull Controller controller) {
|
||||
@@ -213,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);
|
||||
}
|
||||
|
||||
@@ -221,6 +229,7 @@ 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);
|
||||
}
|
||||
|
||||
@@ -234,9 +243,18 @@ class ControllerHostedRouter extends Router {
|
||||
return hostId;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
String getTag() {
|
||||
return tag;
|
||||
boolean matches(int hostId, @Nullable String tag) {
|
||||
if (!boundToContainer && container == null) {
|
||||
if (this.tag == null) {
|
||||
throw new IllegalStateException("Host ID can't be variable with a null tag");
|
||||
}
|
||||
if (this.tag.equals(tag)) {
|
||||
this.hostId = hostId;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return this.hostId == hostId && TextUtils.equals(tag, this.tag);
|
||||
}
|
||||
|
||||
@Override @NonNull
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.bluelinelabs.conductor;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentSender;
|
||||
import android.os.Bundle;
|
||||
@@ -34,14 +35,14 @@ import java.util.List;
|
||||
public abstract class Router {
|
||||
|
||||
private static final String KEY_BACKSTACK = "Router.backstack";
|
||||
private static final String KEY_POPS_LAST_VIEW = "Router.popsLastView";
|
||||
private static final String KEY_POP_ROOT_CONTROLLER_MODE = "Router.popRootControllerMode";
|
||||
|
||||
final Backstack backstack = new Backstack();
|
||||
private final List<ControllerChangeListener> changeListeners = new ArrayList<>();
|
||||
private final List<ChangeTransaction> pendingControllerChanges = new ArrayList<>();
|
||||
final List<Controller> destroyingControllers = new ArrayList<>();
|
||||
|
||||
private boolean popsLastView = false;
|
||||
private PopRootControllerMode popRootControllerMode = PopRootControllerMode.POP_ROOT_CONTROLLER_BUT_NOT_VIEW;
|
||||
boolean containerFullyAttached = false;
|
||||
boolean isActivityStopped = false;
|
||||
|
||||
@@ -94,7 +95,7 @@ public abstract class Router {
|
||||
//noinspection ConstantConditions
|
||||
if (backstack.peek().controller().handleBack()) {
|
||||
return true;
|
||||
} else if (popCurrentController()) {
|
||||
} else if ((backstack.getSize() > 1 || popRootControllerMode != PopRootControllerMode.NEVER) && popCurrentController()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -161,7 +162,7 @@ public abstract class Router {
|
||||
}
|
||||
}
|
||||
|
||||
if (popsLastView) {
|
||||
if (popRootControllerMode == PopRootControllerMode.POP_ROOT_CONTROLLER_AND_VIEW) {
|
||||
return topTransaction != null;
|
||||
} else {
|
||||
return !backstack.isEmpty();
|
||||
@@ -220,12 +221,13 @@ 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 = poppedControllers.get(0);
|
||||
topTransaction.controller().addLifecycleListener(new Controller.LifecycleListener() {
|
||||
@Override
|
||||
public void onChangeEnd(@NonNull Controller controller, @NonNull ControllerChangeHandler changeHandler, @NonNull ControllerChangeType changeType) {
|
||||
@@ -240,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() {
|
||||
@@ -250,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;
|
||||
}
|
||||
|
||||
@@ -280,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;
|
||||
@@ -375,7 +401,7 @@ public abstract class Router {
|
||||
*/
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public int getBackstackSize() {
|
||||
return backstack.size();
|
||||
return backstack.getSize();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -383,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());
|
||||
@@ -545,10 +571,9 @@ public abstract class Router {
|
||||
public void rebindIfNeeded() {
|
||||
ThreadUtils.ensureMainThread();
|
||||
|
||||
Iterator<RouterTransaction> backstackIterator = backstack.reverseIterator();
|
||||
while (backstackIterator.hasNext()) {
|
||||
RouterTransaction transaction = backstackIterator.next();
|
||||
|
||||
// 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 {
|
||||
@@ -649,14 +674,14 @@ 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()) {
|
||||
@@ -700,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<>();
|
||||
@@ -727,6 +752,7 @@ public abstract class Router {
|
||||
@Override
|
||||
public void run() {
|
||||
containerFullyAttached = true;
|
||||
performPendingControllerChanges();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -745,9 +771,18 @@ public abstract class Router {
|
||||
}
|
||||
}
|
||||
|
||||
void onContextUnavailable(@NonNull Context context) {
|
||||
for (RouterTransaction transaction : backstack) {
|
||||
transaction.controller().onContextUnavailable(context);
|
||||
}
|
||||
for (Controller controller : destroyingControllers) {
|
||||
controller.onContextUnavailable(context);
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
final List<Controller> getControllers() {
|
||||
List<Controller> controllers = new ArrayList<>(backstack.size());
|
||||
List<Controller> controllers = new ArrayList<>(backstack.getSize());
|
||||
|
||||
Iterator<RouterTransaction> backstackIterator = backstack.reverseIterator();
|
||||
while (backstackIterator.hasNext()) {
|
||||
@@ -757,6 +792,18 @@ public abstract class Router {
|
||||
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) {
|
||||
@@ -793,7 +840,7 @@ public abstract class Router {
|
||||
if (to != null) {
|
||||
to.ensureValidIndex(getTransactionIndexer());
|
||||
setRouterOnController(toController);
|
||||
} else if (backstack.size() == 0 && !popsLastView) {
|
||||
} 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();
|
||||
@@ -837,12 +884,9 @@ public abstract class Router {
|
||||
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);
|
||||
}
|
||||
@@ -1001,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
|
||||
}
|
||||
}
|
||||
|
||||
+110
-22
@@ -1,5 +1,6 @@
|
||||
package com.bluelinelabs.conductor.internal
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.lifecycle.Lifecycle
|
||||
@@ -74,7 +75,7 @@ internal class OwnViewTreeLifecycleAndRegistry private constructor(
|
||||
) {
|
||||
// Should only happen if pushing another controller over this one was aborted
|
||||
if (
|
||||
controller == changeController &&
|
||||
controller === changeController &&
|
||||
changeType.isEnter &&
|
||||
changeHandler.removesFromViewOnPush() &&
|
||||
changeController.view?.windowToken != null &&
|
||||
@@ -84,28 +85,19 @@ internal class OwnViewTreeLifecycleAndRegistry private constructor(
|
||||
}
|
||||
}
|
||||
|
||||
// AbstractComposeView adds its own OnAttachStateChangeListener by default. Since it
|
||||
// does this on init, its detach callbacks get called before ours, which prevents us
|
||||
// from saving state in onDetach. The if statement in here should detect upcoming
|
||||
// detachment.
|
||||
override fun onChangeStart(
|
||||
changeController: Controller,
|
||||
changeHandler: ControllerChangeHandler,
|
||||
changeType: ControllerChangeType
|
||||
changeType: ControllerChangeType,
|
||||
) {
|
||||
if (
|
||||
controller == changeController &&
|
||||
!changeType.isEnter &&
|
||||
changeHandler.removesFromViewOnPush() &&
|
||||
lifecycleRegistry.currentState == Lifecycle.State.RESUMED
|
||||
) {
|
||||
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_PAUSE)
|
||||
pauseOnChangeStart(
|
||||
targetController = controller,
|
||||
changeController = changeController,
|
||||
changeHandler = changeHandler,
|
||||
changeType = changeType,
|
||||
)
|
||||
|
||||
savedRegistryState = Bundle()
|
||||
savedStateRegistryController.performSave(savedRegistryState)
|
||||
|
||||
hasSavedState = true
|
||||
}
|
||||
GlobalChangeStartListener.onChangeStart(changeController, changeHandler, changeType)
|
||||
}
|
||||
|
||||
override fun preDetach(controller: Controller, view: View) {
|
||||
@@ -134,8 +126,7 @@ internal class OwnViewTreeLifecycleAndRegistry private constructor(
|
||||
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 {
|
||||
parent?.addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener {
|
||||
override fun onViewAttachedToWindow(v: View?) = Unit
|
||||
override fun onViewDetachedFromWindow(v: View?) {
|
||||
parent.removeOnAttachStateChangeListener(this)
|
||||
@@ -146,6 +137,14 @@ internal class OwnViewTreeLifecycleAndRegistry private constructor(
|
||||
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY)
|
||||
}
|
||||
}
|
||||
|
||||
override fun postContextAvailable(controller: Controller, context: Context) {
|
||||
listenForAncestorChangeStart(controller)
|
||||
}
|
||||
|
||||
override fun preContextUnavailable(controller: Controller, context: Context) {
|
||||
stopListeningForAncestorChangeStart(controller)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -153,11 +152,100 @@ internal class OwnViewTreeLifecycleAndRegistry private constructor(
|
||||
|
||||
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(target)
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
-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,67 +0,0 @@
|
||||
package com.bluelinelabs.conductor;
|
||||
|
||||
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
-5
@@ -112,7 +112,7 @@ class ControllerLifecycleActivityReferenceTests {
|
||||
child.addLifecycleListener(listener)
|
||||
|
||||
val childRouter = parent.getChildRouter(parent.view!!.findViewById(TestController.VIEW_ID))
|
||||
childRouter.setPopsLastView(true)
|
||||
childRouter.setPopRootControllerMode(Router.PopRootControllerMode.POP_ROOT_CONTROLLER_AND_VIEW)
|
||||
childRouter.pushController(
|
||||
child.asTransaction(
|
||||
pushChangeHandler = MockChangeHandler.defaultHandler(),
|
||||
@@ -146,7 +146,7 @@ class ControllerLifecycleActivityReferenceTests {
|
||||
child.addLifecycleListener(listener)
|
||||
|
||||
val childRouter = parent.getChildRouter(parent.view!!.findViewById(TestController.VIEW_ID))
|
||||
childRouter.setPopsLastView(true)
|
||||
childRouter.setPopRootControllerMode(Router.PopRootControllerMode.POP_ROOT_CONTROLLER_AND_VIEW)
|
||||
childRouter.pushController(
|
||||
child.asTransaction(
|
||||
pushChangeHandler = MockChangeHandler.defaultHandler(),
|
||||
@@ -155,7 +155,7 @@ class ControllerLifecycleActivityReferenceTests {
|
||||
)
|
||||
activity.router.popCurrentController()
|
||||
|
||||
Assert.assertEquals(listOf(true), listener.changeEndReferences)
|
||||
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)
|
||||
@@ -199,7 +199,7 @@ class ControllerLifecycleActivityReferenceTests {
|
||||
val listener = ActivityReferencingLifecycleListener()
|
||||
child.addLifecycleListener(listener)
|
||||
val childRouter = parent.getChildRouter(parent.view!!.findViewById(TestController.VIEW_ID))
|
||||
childRouter.setPopsLastView(true)
|
||||
childRouter.setPopRootControllerMode(Router.PopRootControllerMode.POP_ROOT_CONTROLLER_AND_VIEW)
|
||||
childRouter.pushController(
|
||||
child.asTransaction(
|
||||
pushChangeHandler = MockChangeHandler.defaultHandler(),
|
||||
@@ -208,7 +208,7 @@ class ControllerLifecycleActivityReferenceTests {
|
||||
)
|
||||
activityController.pause().stop().destroy()
|
||||
|
||||
Assert.assertEquals(listOf(true), listener.changeEndReferences)
|
||||
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)
|
||||
|
||||
+2
@@ -465,6 +465,8 @@ class ControllerLifecycleCallbacksTests {
|
||||
assertCalls(expectedCallState, child)
|
||||
|
||||
activityController.get().router.popCurrentController()
|
||||
expectedCallState.changeStartCalls++
|
||||
expectedCallState.changeEndCalls++
|
||||
expectedCallState.detachCalls++
|
||||
expectedCallState.destroyViewCalls++
|
||||
expectedCallState.contextUnavailableCalls++
|
||||
|
||||
@@ -276,7 +276,7 @@ class ControllerTests {
|
||||
Assert.assertNull(child2.parentController)
|
||||
|
||||
var childRouter = parent.getChildRouter(parent.view!!.findViewById(TestController.VIEW_ID))
|
||||
childRouter.setPopsLastView(true)
|
||||
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])
|
||||
@@ -362,7 +362,7 @@ class ControllerTests {
|
||||
val childTransaction1 = TestController().asTransaction()
|
||||
val childTransaction2 = TestController().asTransaction()
|
||||
var childRouter = parent.getChildRouter(parent.view!!.findViewById(TestController.CHILD_VIEW_ID_1))
|
||||
childRouter.setPopsLastView(true)
|
||||
childRouter.setPopRootControllerMode(Router.PopRootControllerMode.POP_ROOT_CONTROLLER_AND_VIEW)
|
||||
childRouter.setRoot(childTransaction1)
|
||||
childRouter.pushController(childTransaction2)
|
||||
val savedState = Bundle()
|
||||
|
||||
@@ -124,7 +124,7 @@ class ReattachCaseTests {
|
||||
val childRouter = controllerB.getChildRouter(
|
||||
controllerB.view!!.findViewById(TestController.VIEW_ID)
|
||||
)
|
||||
childRouter.setPopsLastView(true)
|
||||
childRouter.setPopRootControllerMode(Router.PopRootControllerMode.POP_ROOT_CONTROLLER_AND_VIEW)
|
||||
childRouter.pushController(
|
||||
childController.asTransaction(
|
||||
pushChangeHandler = MockChangeHandler.defaultHandler(),
|
||||
@@ -179,7 +179,7 @@ class ReattachCaseTests {
|
||||
val childRouter = controllerB.getChildRouter(
|
||||
controllerB.view!!.findViewById(TestController.VIEW_ID)
|
||||
)
|
||||
childRouter.setPopsLastView(true)
|
||||
childRouter.setPopRootControllerMode(Router.PopRootControllerMode.POP_ROOT_CONTROLLER_AND_VIEW)
|
||||
childRouter.pushController(
|
||||
childController.asTransaction(
|
||||
pushChangeHandler = MockChangeHandler.defaultHandler(),
|
||||
|
||||
+190
@@ -0,0 +1,190 @@
|
||||
package com.bluelinelabs.conductor.internal
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Looper
|
||||
import android.view.View
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import com.bluelinelabs.conductor.Controller
|
||||
import com.bluelinelabs.conductor.ControllerChangeHandler
|
||||
import com.bluelinelabs.conductor.ControllerChangeType
|
||||
import com.bluelinelabs.conductor.TestController
|
||||
import com.bluelinelabs.conductor.asTransaction
|
||||
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
|
||||
import org.robolectric.annotation.Config
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
@Config(manifest = Config.NONE)
|
||||
class OwnViewTreeLifecycleAndRegistryTest {
|
||||
|
||||
private val router = Robolectric.buildActivity(TestActivity::class.java)
|
||||
.setup()
|
||||
.get()
|
||||
.router
|
||||
|
||||
@Test
|
||||
fun `onCreate lifecycle event before create view`() {
|
||||
assertControllerState(
|
||||
preCreateViewAssertedState = Lifecycle.State.CREATED,
|
||||
setup = { router.setRoot(it.asTransaction()) }
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `onStart lifecycle event after create view`() {
|
||||
assertControllerState(
|
||||
postCreateViewAssertedState = Lifecycle.State.STARTED,
|
||||
setup = { router.setRoot(it.asTransaction()) }
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `onResume lifecycle event on attach`() {
|
||||
assertControllerState(
|
||||
postAttachAssertedState = Lifecycle.State.RESUMED,
|
||||
setup = { router.setRoot(it.asTransaction()) }
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `onPause lifecycle event on exit change start`() {
|
||||
assertControllerState(
|
||||
onChangeStartAssertedState = Lifecycle.State.STARTED,
|
||||
setup = {
|
||||
router.setRoot(it.asTransaction())
|
||||
router.pushController(TestController().asTransaction())
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `onPause lifecycle event on parent exit change start`() {
|
||||
val parent = TestController()
|
||||
val controller = TestController()
|
||||
var hasAsserted = false
|
||||
val ownViewTreeLifecycleAndRegistry = OwnViewTreeLifecycleAndRegistry.own(controller)
|
||||
|
||||
// Ensure our listener gets added after OwnViewTreeLifecycleAndRegistry's by waiting until
|
||||
// postContextAvailable to add the lifecycle listener on the parent controller
|
||||
controller.addLifecycleListener(object : Controller.LifecycleListener() {
|
||||
override fun postContextAvailable(controller: Controller, context: Context) {
|
||||
parent.addLifecycleListener(object : Controller.LifecycleListener() {
|
||||
override fun onChangeStart(
|
||||
controller: Controller,
|
||||
changeHandler: ControllerChangeHandler,
|
||||
changeType: ControllerChangeType
|
||||
) {
|
||||
Assert.assertEquals(Lifecycle.State.STARTED, ownViewTreeLifecycleAndRegistry.lifecycle.currentState)
|
||||
hasAsserted = true
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
router.setRoot(parent.asTransaction())
|
||||
parent.getChildRouter(parent.view!!.findViewById(TestController.VIEW_ID)).setRoot(controller.asTransaction())
|
||||
router.pushController(TestController().asTransaction())
|
||||
Shadows.shadowOf(Looper.getMainLooper()).idle()
|
||||
|
||||
Assert.assertTrue(hasAsserted)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `onStop lifecycle event on detach`() {
|
||||
assertControllerState(
|
||||
preDetachAssertedState = Lifecycle.State.CREATED,
|
||||
setup = {
|
||||
router.setRoot(it.asTransaction())
|
||||
router.pushController(TestController().asTransaction())
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `onDestroy lifecycle event on destroy view`() {
|
||||
assertControllerState(
|
||||
preDestroyViewAssertedState = Lifecycle.State.DESTROYED,
|
||||
setup = {
|
||||
router.setRoot(it.asTransaction())
|
||||
router.pushController(TestController().asTransaction())
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private fun assertControllerState(
|
||||
preCreateViewAssertedState: Lifecycle.State? = null,
|
||||
postCreateViewAssertedState: Lifecycle.State? = null,
|
||||
postAttachAssertedState: Lifecycle.State? = null,
|
||||
preDetachAssertedState: Lifecycle.State? = null,
|
||||
preDestroyViewAssertedState: Lifecycle.State? = null,
|
||||
onChangeStartAssertedState: Lifecycle.State? = null,
|
||||
setup: (Controller) -> Unit = { },
|
||||
) {
|
||||
val controller = TestController()
|
||||
val ownViewTreeLifecycleAndRegistry = OwnViewTreeLifecycleAndRegistry.own(controller)
|
||||
var hasAsserted = false
|
||||
|
||||
val assertState: (Lifecycle.State) -> Unit = {
|
||||
Assert.assertEquals(it, ownViewTreeLifecycleAndRegistry.lifecycle.currentState)
|
||||
}
|
||||
|
||||
controller.addLifecycleListener(object : Controller.LifecycleListener() {
|
||||
override fun preCreateView(controller: Controller) {
|
||||
preCreateViewAssertedState?.let {
|
||||
assertState(it)
|
||||
hasAsserted = true
|
||||
}
|
||||
}
|
||||
|
||||
override fun postCreateView(controller: Controller, view: View) {
|
||||
postCreateViewAssertedState?.let {
|
||||
assertState(it)
|
||||
hasAsserted = true
|
||||
}
|
||||
}
|
||||
|
||||
override fun postAttach(controller: Controller, view: View) {
|
||||
postAttachAssertedState?.let {
|
||||
assertState(it)
|
||||
hasAsserted = true
|
||||
}
|
||||
}
|
||||
|
||||
override fun preDetach(controller: Controller, view: View) {
|
||||
preDetachAssertedState?.let {
|
||||
assertState(it)
|
||||
hasAsserted = true
|
||||
}
|
||||
}
|
||||
|
||||
override fun preDestroyView(controller: Controller, view: View) {
|
||||
preDestroyViewAssertedState?.let {
|
||||
assertState(it)
|
||||
hasAsserted = true
|
||||
}
|
||||
}
|
||||
|
||||
override fun onChangeStart(
|
||||
controller: Controller,
|
||||
changeHandler: ControllerChangeHandler,
|
||||
changeType: ControllerChangeType
|
||||
) {
|
||||
if (!changeType.isEnter) {
|
||||
onChangeStartAssertedState?.let {
|
||||
assertState(it)
|
||||
hasAsserted = true
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
setup(controller)
|
||||
Shadows.shadowOf(Looper.getMainLooper()).idle()
|
||||
Assert.assertTrue(hasAsserted)
|
||||
}
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
package com.bluelinelabs.conductor.internal
|
||||
|
||||
import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
import android.util.SparseArray
|
||||
import io.kotest.matchers.ints.shouldBeExactly
|
||||
import io.kotest.matchers.shouldBe
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import org.robolectric.annotation.Config
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
@Config(manifest = Config.NONE)
|
||||
class StringSparseArrayParcelerTest {
|
||||
|
||||
@Test
|
||||
fun emptyArray() {
|
||||
SparseArray<String>().parcelAndUnParcel().size() shouldBeExactly 0
|
||||
}
|
||||
|
||||
@Test
|
||||
fun arrayWithContents() {
|
||||
val array = SparseArray<String>()
|
||||
array.put(1, "one")
|
||||
array.put(7, "seven")
|
||||
|
||||
val unParceled = array.parcelAndUnParcel()
|
||||
|
||||
unParceled.size() shouldBeExactly 2
|
||||
unParceled[1] shouldBe "one"
|
||||
unParceled[7] shouldBe "seven"
|
||||
}
|
||||
|
||||
private fun SparseArray<String>.parcelAndUnParcel(): SparseArray<String> {
|
||||
val parceler = StringSparseArrayParceler(this)
|
||||
|
||||
val parcel = Parcel.obtain()
|
||||
parceler.writeToParcel(parcel, 0)
|
||||
|
||||
parcel.setDataPosition(0)
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val creator = StringSparseArrayParceler::class.java.getField("CREATOR").get(null)
|
||||
as Parcelable.Creator<StringSparseArrayParceler>
|
||||
val unParceled = creator.createFromParcel(parcel)
|
||||
return unParceled.stringSparseArray.also {
|
||||
check(it !== this)
|
||||
}
|
||||
}
|
||||
}
|
||||
+15
-23
@@ -5,7 +5,7 @@ android {
|
||||
defaultConfig {
|
||||
applicationId "com.bluelinelabs.conductor.demo"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
targetSdkVersion libs.versions.targetsdk.get()
|
||||
versionCode 1
|
||||
versionName "1.0.0"
|
||||
vectorDrawables.useSupportLibrary true
|
||||
@@ -27,7 +27,7 @@ android {
|
||||
compose = true
|
||||
}
|
||||
|
||||
compileSdkVersion rootProject.ext.compileSdkVersion
|
||||
compileSdkVersion 31
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
@@ -39,40 +39,32 @@ android {
|
||||
}
|
||||
|
||||
composeOptions {
|
||||
kotlinCompilerExtensionVersion composeVersion
|
||||
kotlinCompilerExtensionVersion libs.versions.compose.get()
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation rootProject.ext.androidxAppCompat
|
||||
implementation rootProject.ext.androidxViewPager2
|
||||
implementation rootProject.ext.material
|
||||
implementation rootProject.ext.androidxCoreKtx
|
||||
implementation libs.androidx.appcompat
|
||||
implementation libs.androidx.viewpager2
|
||||
implementation libs.material
|
||||
implementation libs.androidx.core.ktx
|
||||
|
||||
implementation rootProject.ext.archComponentsLiveDataCore // Fix duplicate classes
|
||||
implementation libs.picasso
|
||||
|
||||
implementation rootProject.ext.picasso
|
||||
|
||||
implementation rootProject.ext.autodisposeKtx
|
||||
implementation libs.autodispose.ktx
|
||||
|
||||
implementation project(':conductor')
|
||||
implementation project(':conductor-modules:viewpager')
|
||||
implementation project(':conductor-modules:viewpager2')
|
||||
implementation project(':conductor-modules:rxlifecycle2')
|
||||
implementation project(':conductor-modules:autodispose')
|
||||
implementation project(':conductor-modules:arch-components-lifecycle')
|
||||
implementation project(':conductor-modules:androidx-transition')
|
||||
|
||||
implementation "androidx.compose.ui:ui:$composeVersion"
|
||||
implementation "androidx.compose.ui:ui-tooling:$composeVersion"
|
||||
implementation "androidx.compose.foundation:foundation:$composeVersion"
|
||||
implementation "androidx.compose.material:material:$composeVersion"
|
||||
implementation "androidx.compose.material:material-icons-core:$composeVersion"
|
||||
implementation "androidx.compose.material:material-icons-extended:$composeVersion"
|
||||
implementation "androidx.activity:activity-compose:1.3.0-beta02"
|
||||
implementation "androidx.lifecycle:lifecycle-viewmodel-compose:1.0.0-alpha07"
|
||||
implementation libs.compose.ui
|
||||
implementation libs.compose.ui.tooling
|
||||
implementation libs.compose.foundation
|
||||
implementation libs.compose.material
|
||||
implementation libs.activity.compose
|
||||
|
||||
debugImplementation rootProject.ext.leakCanary
|
||||
releaseImplementation rootProject.ext.leakCanaryNoOp
|
||||
testImplementation rootProject.ext.leakCanaryNoOp
|
||||
implementation libs.leakCanary
|
||||
}
|
||||
|
||||
@@ -1,29 +1,28 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="com.bluelinelabs.conductor.demo"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
package="com.bluelinelabs.conductor.demo">
|
||||
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
|
||||
<application
|
||||
android:name="com.bluelinelabs.conductor.demo.DemoApplication"
|
||||
android:allowBackup="true"
|
||||
android:fullBackupContent="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/AppTheme"
|
||||
android:fullBackupContent="true"
|
||||
tools:ignore="GoogleAppIndexingWarning">
|
||||
|
||||
<activity
|
||||
android:name="com.bluelinelabs.conductor.demo.MainActivity"
|
||||
android:exported="true"
|
||||
android:label="@string/app_name"
|
||||
android:launchMode="singleTask"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
package com.bluelinelabs.conductor.demo
|
||||
|
||||
import android.app.Application
|
||||
import com.squareup.leakcanary.LeakCanary
|
||||
import com.squareup.leakcanary.RefWatcher
|
||||
|
||||
class DemoApplication : Application() {
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
refWatcher = LeakCanary.install(this)
|
||||
}
|
||||
|
||||
companion object {
|
||||
lateinit var refWatcher: RefWatcher
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import com.bluelinelabs.conductor.Conductor.attachRouter
|
||||
import com.bluelinelabs.conductor.Router
|
||||
import com.bluelinelabs.conductor.Router.PopRootControllerMode
|
||||
import com.bluelinelabs.conductor.RouterTransaction.Companion.with
|
||||
import com.bluelinelabs.conductor.demo.controllers.HomeController
|
||||
import com.bluelinelabs.conductor.demo.databinding.ActivityMainBinding
|
||||
@@ -23,6 +24,8 @@ class MainActivity : AppCompatActivity(), ToolbarProvider {
|
||||
setContentView(binding.root)
|
||||
|
||||
router = attachRouter(this, binding.controllerContainer, savedInstanceState)
|
||||
.setPopRootControllerMode(PopRootControllerMode.NEVER)
|
||||
|
||||
if (!router.hasRootController()) {
|
||||
router.setRoot(with(HomeController()))
|
||||
}
|
||||
|
||||
+2
-19
@@ -11,22 +11,20 @@ import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleObserver
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.OnLifecycleEvent
|
||||
import com.bluelinelabs.conductor.ControllerChangeHandler
|
||||
import com.bluelinelabs.conductor.ControllerChangeType
|
||||
import com.bluelinelabs.conductor.RouterTransaction
|
||||
import com.bluelinelabs.conductor.archlifecycle.LifecycleController
|
||||
import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler
|
||||
import com.bluelinelabs.conductor.demo.DemoApplication
|
||||
import com.bluelinelabs.conductor.demo.R
|
||||
import com.bluelinelabs.conductor.demo.ToolbarProvider
|
||||
import com.bluelinelabs.conductor.demo.controllers.base.watchForLeaks
|
||||
import com.bluelinelabs.conductor.demo.databinding.ControllerLifecycleBinding
|
||||
|
||||
class ArchLifecycleController : LifecycleController() {
|
||||
|
||||
private var hasExited = false
|
||||
|
||||
init {
|
||||
Log.i(TAG, "Conductor: Constructor called")
|
||||
watchForLeaks()
|
||||
|
||||
lifecycle.addObserver(object : LifecycleObserver {
|
||||
@OnLifecycleEvent(Lifecycle.Event.ON_ANY)
|
||||
@@ -104,21 +102,6 @@ class ArchLifecycleController : LifecycleController() {
|
||||
|
||||
override fun onDestroy() {
|
||||
Log.i(TAG, "Conductor: onDestroy() called")
|
||||
super.onDestroy()
|
||||
if (hasExited) {
|
||||
DemoApplication.refWatcher.watch(this)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onChangeEnded(
|
||||
changeHandler: ControllerChangeHandler,
|
||||
changeType: ControllerChangeType
|
||||
) {
|
||||
super.onChangeEnded(changeHandler, changeType)
|
||||
hasExited = !changeType.isEnter
|
||||
if (isDestroyed) {
|
||||
DemoApplication.refWatcher.watch(this)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
+2
-18
@@ -6,14 +6,12 @@ import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import com.bluelinelabs.conductor.Controller
|
||||
import com.bluelinelabs.conductor.ControllerChangeHandler
|
||||
import com.bluelinelabs.conductor.ControllerChangeType
|
||||
import com.bluelinelabs.conductor.RouterTransaction.Companion.with
|
||||
import com.bluelinelabs.conductor.autodispose.ControllerScopeProvider
|
||||
import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler
|
||||
import com.bluelinelabs.conductor.demo.DemoApplication
|
||||
import com.bluelinelabs.conductor.demo.R
|
||||
import com.bluelinelabs.conductor.demo.ToolbarProvider
|
||||
import com.bluelinelabs.conductor.demo.controllers.base.watchForLeaks
|
||||
import com.bluelinelabs.conductor.demo.databinding.ControllerLifecycleBinding
|
||||
import com.uber.autodispose.autoDisposable
|
||||
import io.reactivex.Observable
|
||||
@@ -23,10 +21,10 @@ import java.util.concurrent.TimeUnit
|
||||
// instead of Activities or Fragments.
|
||||
class AutodisposeController : Controller() {
|
||||
|
||||
private var hasExited = false
|
||||
private val scopeProvider = ControllerScopeProvider.from(this)
|
||||
|
||||
init {
|
||||
watchForLeaks()
|
||||
Observable.interval(1, TimeUnit.SECONDS)
|
||||
.doOnDispose { Log.i(TAG, "Disposing from constructor") }
|
||||
.autoDisposable(scopeProvider)
|
||||
@@ -98,20 +96,6 @@ class AutodisposeController : Controller() {
|
||||
public override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
Log.i(TAG, "onDestroy() called")
|
||||
if (hasExited) {
|
||||
DemoApplication.refWatcher.watch(this)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onChangeEnded(
|
||||
changeHandler: ControllerChangeHandler,
|
||||
changeType: ControllerChangeType
|
||||
) {
|
||||
super.onChangeEnded(changeHandler, changeType)
|
||||
hasExited = !changeType.isEnter
|
||||
if (isDestroyed) {
|
||||
DemoApplication.refWatcher.watch(this)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
+1
-1
@@ -2,10 +2,10 @@ package com.bluelinelabs.conductor.demo.controllers
|
||||
|
||||
import android.graphics.PorterDuff
|
||||
import android.os.Bundle
|
||||
import android.support.annotation.DrawableRes
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
|
||||
+7
-8
@@ -17,7 +17,9 @@ import com.bluelinelabs.conductor.demo.databinding.RowHomeBinding
|
||||
import com.bluelinelabs.conductor.demo.util.viewBinding
|
||||
|
||||
class ExternalModulesController : BaseController(R.layout.controller_additional_modules) {
|
||||
private val binding: ControllerAdditionalModulesBinding by viewBinding(ControllerAdditionalModulesBinding::bind)
|
||||
private val binding: ControllerAdditionalModulesBinding by viewBinding(
|
||||
ControllerAdditionalModulesBinding::bind
|
||||
)
|
||||
|
||||
override val title = "External Module Demos"
|
||||
|
||||
@@ -40,11 +42,6 @@ class ExternalModulesController : BaseController(R.layout.controller_additional_
|
||||
.pushChangeHandler(FadeChangeHandler())
|
||||
.popChangeHandler(FadeChangeHandler())
|
||||
)
|
||||
ModuleModel.RX_LIFECYCLE_2 -> router.pushController(
|
||||
with(RxLifecycle2Controller())
|
||||
.pushChangeHandler(FadeChangeHandler())
|
||||
.popChangeHandler(FadeChangeHandler())
|
||||
)
|
||||
ModuleModel.ARCH_LIFECYCLE -> router.pushController(
|
||||
with(ArchLifecycleController())
|
||||
.pushChangeHandler(FadeChangeHandler())
|
||||
@@ -56,7 +53,6 @@ class ExternalModulesController : BaseController(R.layout.controller_additional_
|
||||
|
||||
private enum class ModuleModel(val title: String, @ColorRes val color: Int) {
|
||||
AUTODISPOSE("Autodispose", R.color.purple_300),
|
||||
RX_LIFECYCLE_2("Rx Lifecycle 2", R.color.blue_grey_300),
|
||||
ARCH_LIFECYCLE("Arch Components Lifecycle", R.color.orange_300);
|
||||
}
|
||||
|
||||
@@ -83,7 +79,10 @@ private class AdditionalModulesAdapter(
|
||||
) : RecyclerView.ViewHolder(binding.root) {
|
||||
fun bind(item: ModuleModel) {
|
||||
binding.title.text = item.title
|
||||
binding.dot.drawable.setColorFilter(ContextCompat.getColor(itemView.context, item.color), PorterDuff.Mode.SRC_ATOP)
|
||||
binding.dot.drawable.setColorFilter(
|
||||
ContextCompat.getColor(itemView.context, item.color),
|
||||
PorterDuff.Mode.SRC_ATOP
|
||||
)
|
||||
itemView.setOnClickListener { modelClickListener(item) }
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -1,6 +1,7 @@
|
||||
package com.bluelinelabs.conductor.demo.controllers
|
||||
|
||||
import android.view.View
|
||||
import com.bluelinelabs.conductor.Router.PopRootControllerMode
|
||||
import com.bluelinelabs.conductor.RouterTransaction
|
||||
import com.bluelinelabs.conductor.demo.R
|
||||
import com.bluelinelabs.conductor.demo.controllers.base.BaseController
|
||||
@@ -18,7 +19,7 @@ class MultipleChildRouterController : BaseController(R.layout.controller_multipl
|
||||
val childContainers = listOf(binding.container0, binding.container1, binding.container2)
|
||||
|
||||
childContainers.forEach { container ->
|
||||
val childRouter = getChildRouter(container).setPopsLastView(false)
|
||||
val childRouter = getChildRouter(container).setPopRootControllerMode(PopRootControllerMode.POP_ROOT_CONTROLLER_BUT_NOT_VIEW)
|
||||
if (!childRouter.hasRootController()) {
|
||||
childRouter.setRoot(RouterTransaction.with(NavigationDemoController(0, NavigationDemoController.DisplayUpMode.HIDE)))
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.bluelinelabs.conductor.demo.controllers
|
||||
import com.bluelinelabs.conductor.Controller
|
||||
import com.bluelinelabs.conductor.ControllerChangeHandler
|
||||
import com.bluelinelabs.conductor.ControllerChangeType
|
||||
import com.bluelinelabs.conductor.Router.PopRootControllerMode
|
||||
import com.bluelinelabs.conductor.RouterTransaction
|
||||
import com.bluelinelabs.conductor.changehandler.FadeChangeHandler
|
||||
import com.bluelinelabs.conductor.demo.R
|
||||
@@ -11,7 +12,6 @@ import com.bluelinelabs.conductor.demo.databinding.ControllerParentBinding
|
||||
import com.bluelinelabs.conductor.demo.util.getMaterialColor
|
||||
import com.bluelinelabs.conductor.demo.util.viewBinding
|
||||
|
||||
|
||||
class ParentController : BaseController(R.layout.controller_parent) {
|
||||
private val binding: ControllerParentBinding by viewBinding(ControllerParentBinding::bind)
|
||||
|
||||
@@ -50,7 +50,7 @@ class ParentController : BaseController(R.layout.controller_parent) {
|
||||
else -> throw IllegalStateException("Invalid child index $index")
|
||||
}
|
||||
|
||||
val childRouter = getChildRouter(container).setPopsLastView(true)
|
||||
val childRouter = getChildRouter(container).setPopRootControllerMode(PopRootControllerMode.POP_ROOT_CONTROLLER_AND_VIEW)
|
||||
if (!childRouter.hasRootController()) {
|
||||
val child = ChildController(
|
||||
title = "Child Controller #$index",
|
||||
|
||||
-119
@@ -1,119 +0,0 @@
|
||||
package com.bluelinelabs.conductor.demo.controllers
|
||||
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import com.bluelinelabs.conductor.ControllerChangeHandler
|
||||
import com.bluelinelabs.conductor.ControllerChangeType
|
||||
import com.bluelinelabs.conductor.RouterTransaction.Companion.with
|
||||
import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler
|
||||
import com.bluelinelabs.conductor.demo.DemoApplication
|
||||
import com.bluelinelabs.conductor.demo.R
|
||||
import com.bluelinelabs.conductor.demo.ToolbarProvider
|
||||
import com.bluelinelabs.conductor.demo.databinding.ControllerLifecycleBinding
|
||||
import com.bluelinelabs.conductor.rxlifecycle2.ControllerEvent
|
||||
import com.bluelinelabs.conductor.rxlifecycle2.RxController
|
||||
import io.reactivex.Observable
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
// Shamelessly borrowed from the official RxLifecycle demo by Trello and adapted for Conductor Controllers
|
||||
// instead of Activities or Fragments.
|
||||
class RxLifecycle2Controller : RxController() {
|
||||
|
||||
private var hasExited = false
|
||||
|
||||
init {
|
||||
Observable.interval(1, TimeUnit.SECONDS)
|
||||
.doOnDispose { Log.i(TAG, "Disposing from constructor") }
|
||||
.compose(bindUntilEvent(ControllerEvent.DESTROY))
|
||||
.subscribe { num: Long ->
|
||||
Log.i(TAG, "Started in constructor, running until onDestroy(): $num")
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup,
|
||||
savedViewState: Bundle?
|
||||
): View {
|
||||
Log.i(TAG, "onCreateView() called")
|
||||
val binding = ControllerLifecycleBinding.inflate(inflater, container, false)
|
||||
binding.title.text = binding.root.resources.getString(R.string.rxlifecycle_title, TAG)
|
||||
|
||||
binding.nextReleaseView.setOnClickListener {
|
||||
retainViewMode = RetainViewMode.RELEASE_DETACH
|
||||
router.pushController(
|
||||
with(TextController("Logcat should now report that the observables from onAttach() and onViewBound() have been disposed of, while the constructor observable is still running."))
|
||||
.pushChangeHandler(HorizontalChangeHandler())
|
||||
.popChangeHandler(HorizontalChangeHandler())
|
||||
)
|
||||
}
|
||||
|
||||
binding.nextRetainView.setOnClickListener {
|
||||
retainViewMode = RetainViewMode.RETAIN_DETACH
|
||||
router.pushController(
|
||||
with(TextController("Logcat should now report that the observables from onAttach() has been disposed of, while the constructor and onViewBound() observables are still running."))
|
||||
.pushChangeHandler(HorizontalChangeHandler())
|
||||
.popChangeHandler(HorizontalChangeHandler())
|
||||
)
|
||||
}
|
||||
|
||||
Observable.interval(1, TimeUnit.SECONDS)
|
||||
.doOnDispose { Log.i(TAG, "Disposing from onCreateView()") }
|
||||
.compose(bindUntilEvent(ControllerEvent.DESTROY_VIEW))
|
||||
.subscribe { num: Long ->
|
||||
Log.i(TAG, "Started in onCreateView(), running until onDestroyView(): $num")
|
||||
}
|
||||
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onAttach(view: View) {
|
||||
super.onAttach(view)
|
||||
Log.i(TAG, "onAttach() called")
|
||||
|
||||
(activity as ToolbarProvider).toolbar.title = "RxLifecycle2 Demo"
|
||||
Observable.interval(1, TimeUnit.SECONDS)
|
||||
.doOnDispose { Log.i(TAG, "Disposing from onAttach()") }
|
||||
.compose(bindUntilEvent(ControllerEvent.DETACH))
|
||||
.subscribe { num: Long ->
|
||||
Log.i(TAG, "Started in onAttach(), running until onDetach(): $num")
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroyView(view: View) {
|
||||
super.onDestroyView(view)
|
||||
Log.i(TAG, "onDestroyView() called")
|
||||
}
|
||||
|
||||
override fun onDetach(view: View) {
|
||||
super.onDetach(view)
|
||||
Log.i(TAG, "onDetach() called")
|
||||
}
|
||||
|
||||
public override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
Log.i(TAG, "onDestroy() called")
|
||||
if (hasExited) {
|
||||
DemoApplication.refWatcher.watch(this)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onChangeEnded(
|
||||
changeHandler: ControllerChangeHandler,
|
||||
changeType: ControllerChangeType
|
||||
) {
|
||||
super.onChangeEnded(changeHandler, changeType)
|
||||
hasExited = !changeType.isEnter
|
||||
if (isDestroyed) {
|
||||
DemoApplication.refWatcher.watch(this)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "RxLifecycleController"
|
||||
}
|
||||
|
||||
}
|
||||
+5
-1
@@ -14,7 +14,11 @@ import com.bluelinelabs.conductor.demo.ToolbarProvider
|
||||
abstract class BaseController(
|
||||
@LayoutRes private val layoutRes: Int,
|
||||
args: Bundle? = null
|
||||
) : RefWatchingController(args) {
|
||||
) : Controller(args) {
|
||||
|
||||
init {
|
||||
watchForLeaks()
|
||||
}
|
||||
|
||||
// Note: This is just a quick demo of how an ActionBar *can* be accessed, not necessarily how it *should*
|
||||
// be accessed. In a production app, this would use Dagger instead.
|
||||
|
||||
-32
@@ -1,32 +0,0 @@
|
||||
package com.bluelinelabs.conductor.demo.controllers.base
|
||||
|
||||
import android.os.Bundle
|
||||
import com.bluelinelabs.conductor.Controller
|
||||
import com.bluelinelabs.conductor.ControllerChangeHandler
|
||||
import com.bluelinelabs.conductor.ControllerChangeType
|
||||
import com.bluelinelabs.conductor.demo.DemoApplication
|
||||
|
||||
abstract class RefWatchingController(args: Bundle?) : Controller(args) {
|
||||
|
||||
private var hasExited = false
|
||||
|
||||
public override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
|
||||
if (hasExited) {
|
||||
DemoApplication.refWatcher.watch(this)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onChangeEnded(
|
||||
changeHandler: ControllerChangeHandler,
|
||||
changeType: ControllerChangeType
|
||||
) {
|
||||
super.onChangeEnded(changeHandler, changeType)
|
||||
|
||||
hasExited = !changeType.isEnter
|
||||
if (isDestroyed) {
|
||||
DemoApplication.refWatcher.watch(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
package com.bluelinelabs.conductor.demo.controllers.base
|
||||
|
||||
import android.view.View
|
||||
import com.bluelinelabs.conductor.Controller
|
||||
import com.bluelinelabs.conductor.ControllerChangeHandler
|
||||
import com.bluelinelabs.conductor.ControllerChangeType
|
||||
import leakcanary.AppWatcher
|
||||
|
||||
private class RefWatchingControllerLifecycleListener : Controller.LifecycleListener() {
|
||||
|
||||
private var hasExited = false
|
||||
|
||||
override fun postDestroy(controller: Controller) {
|
||||
if (hasExited) {
|
||||
controller.expectWeaklyReachable()
|
||||
}
|
||||
}
|
||||
|
||||
override fun preDestroyView(controller: Controller, view: View) {
|
||||
AppWatcher.objectWatcher.expectWeaklyReachable(
|
||||
view,
|
||||
"A destroyed controller view should have only weak references."
|
||||
)
|
||||
}
|
||||
|
||||
override fun onChangeEnd(
|
||||
controller: Controller,
|
||||
changeHandler: ControllerChangeHandler,
|
||||
changeType: ControllerChangeType
|
||||
) {
|
||||
hasExited = !changeType.isEnter
|
||||
if (controller.isDestroyed) {
|
||||
controller.expectWeaklyReachable()
|
||||
}
|
||||
}
|
||||
|
||||
private fun Controller.expectWeaklyReachable() {
|
||||
AppWatcher.objectWatcher.expectWeaklyReachable(
|
||||
this,
|
||||
"A destroyed controller should have only weak references."
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun Controller.watchForLeaks() {
|
||||
addLifecycleListener(RefWatchingControllerLifecycleListener())
|
||||
}
|
||||
@@ -30,7 +30,6 @@
|
||||
<!-- Drag Dismiss Demo -->
|
||||
<string name="drag_to_dismiss">Elastic scrolling view - scroll past bounds to dismiss.</string>
|
||||
|
||||
<!-- RxLifecycle Demo -->
|
||||
<string name="rxlifecycle_title">Watch tag %1$s on logcat for a demo.</string>
|
||||
<string name="next_controller_retain">Push Controller, retaining this View</string>
|
||||
<string name="next_controller_release">Push Controller, releasing this View</string>
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
ext {
|
||||
minSdkVersion = 16
|
||||
compileSdkVersion = 28
|
||||
targetSdkVersion = 28
|
||||
|
||||
picassoVersion = '2.5.2'
|
||||
leakCanaryVersion = '1.5.4'
|
||||
rxJavaVersion = '1.3.8'
|
||||
rxJava2Version = '2.1.14'
|
||||
rxLifecycleVersion = '1.0'
|
||||
rxLifecycle2Version = '2.2.1'
|
||||
autodisposeVersion = '1.0.0'
|
||||
archComponentsVersion = '2.3.1'
|
||||
junitVersion = '4.12'
|
||||
mvnPublishVersion = '0.13.0'
|
||||
dokkaVersion = '1.4.32'
|
||||
composeVersion = "1.0.0-beta09"
|
||||
|
||||
agpVersion = "7.0.0-beta04"
|
||||
lintVersion = agpVersion.replaceFirst(~/\d*/) { version ->
|
||||
// the major version of lint is always 23 version higher than the major version of agp
|
||||
version.toInteger() + 23
|
||||
}
|
||||
|
||||
kotlinVersion = '1.5.10'
|
||||
kotlinStd = "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlinVersion"
|
||||
|
||||
material = "com.google.android.material:material:1.1.0"
|
||||
androidxAnnotations = "androidx.annotation:annotation:1.1.0"
|
||||
androidxAppCompat = "androidx.appcompat:appcompat:1.3.0"
|
||||
androidxTransition = "androidx.transition:transition:1.3.1"
|
||||
androidxCollection = "androidx.collection:collection:1.1.0"
|
||||
androidxViewPager2 = "androidx.viewpager2:viewpager2:1.0.0"
|
||||
androidxCoreKtx = "androidx.core:core-ktx:1.3.2"
|
||||
|
||||
picasso = "com.squareup.picasso:picasso:$picassoVersion"
|
||||
|
||||
leakCanary = "com.squareup.leakcanary:leakcanary-android:$leakCanaryVersion"
|
||||
leakCanaryNoOp = "com.squareup.leakcanary:leakcanary-android-no-op:$leakCanaryVersion"
|
||||
|
||||
rxJava = "io.reactivex:rxjava:$rxJavaVersion"
|
||||
rxJava2 = "io.reactivex.rxjava2:rxjava:$rxJava2Version"
|
||||
|
||||
rxLifecycle2 = "com.trello.rxlifecycle2:rxlifecycle:$rxLifecycle2Version"
|
||||
rxLifecycleAndroid2 = "com.trello.rxlifecycle2:rxlifecycle-android:$rxLifecycle2Version"
|
||||
|
||||
autodispose = "com.uber.autodispose:autodispose:$autodisposeVersion"
|
||||
autodisposeLifecycle = "com.uber.autodispose:autodispose-lifecycle:$autodisposeVersion"
|
||||
autodisposeKtx = "com.uber.autodispose:autodispose-ktx:$autodisposeVersion"
|
||||
|
||||
archComponentsLifecycle = "androidx.lifecycle:lifecycle-runtime:$archComponentsVersion"
|
||||
archComponentsLiveDataCore = "androidx.lifecycle:lifecycle-livedata-core:$archComponentsVersion"
|
||||
|
||||
savedState = "androidx.savedstate:savedstate-ktx:1.1.0"
|
||||
|
||||
junit = "junit:junit:$junitVersion"
|
||||
robolectric = "org.robolectric:robolectric:4.5.1"
|
||||
|
||||
lintapi = "com.android.tools.lint:lint-api:$lintVersion"
|
||||
lintchecks = "com.android.tools.lint:lint-checks:$lintVersion"
|
||||
lint = "com.android.tools.lint:lint:$lintVersion"
|
||||
lintTests = "com.android.tools.lint:lint-tests:$lintVersion"
|
||||
}
|
||||
+5
-20
@@ -1,25 +1,9 @@
|
||||
# Project-wide Gradle settings.
|
||||
|
||||
# IDE (e.g. Android Studio) users:
|
||||
# Gradle settings configured through the IDE *will override*
|
||||
# any settings specified in this file.
|
||||
|
||||
# For more details on how to configure your build environment visit
|
||||
# http://www.gradle.org/docs/current/userguide/build_environment.html
|
||||
|
||||
# Specifies the JVM arguments used for the daemon process.
|
||||
# The setting is particularly useful for tweaking memory settings.
|
||||
VERSION_CODE=3
|
||||
VERSION_NAME=3.2.1-SNAPSHOT
|
||||
android.useAndroidX=true
|
||||
org.gradle.jvmargs=-Xmx1536m
|
||||
|
||||
# When configured, Gradle will run in incubating parallel mode.
|
||||
# This option should only be used with decoupled projects. More details, visit
|
||||
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||
# org.gradle.parallel=true
|
||||
|
||||
VERSION_NAME=3.1.1-SNAPSHOT
|
||||
VERSION_CODE=3
|
||||
GROUP=com.bluelinelabs
|
||||
|
||||
POM_DESCRIPTION=A small, yet full-featured framework that allows building View-based Android applications
|
||||
POM_URL=https://github.com/bluelinelabs/Conductor
|
||||
POM_SCM_URL=https://github.com/bluelinelabs/Conductor
|
||||
@@ -30,4 +14,5 @@ POM_LICENCE_URL=http://www.apache.org/licenses/LICENSE-2.0.txt
|
||||
POM_LICENCE_DIST=repo
|
||||
POM_DEVELOPER_ID=erickuck
|
||||
POM_DEVELOPER_NAME=Eric Kuck
|
||||
android.useAndroidX=true
|
||||
SONATYPE_HOST=DEFAULT
|
||||
RELEASE_SIGNING_ENABLED=true
|
||||
@@ -0,0 +1,66 @@
|
||||
[versions]
|
||||
minsdk = "16"
|
||||
compilesdk = "28"
|
||||
targetsdk = "28"
|
||||
|
||||
activity-compose = "1.3.0"
|
||||
agp = "7.2.1" # Note: update lint version whenever this is updated
|
||||
androidx-annotation = "1.1.0"
|
||||
androidx-appcompat = "1.3.0"
|
||||
androidx-collection = "1.1.0"
|
||||
androidx-core = "1.3.2"
|
||||
androidx-lifecycle = "2.3.1"
|
||||
androidx-savedstate = "1.1.0"
|
||||
androidx-transition = "1.3.1"
|
||||
androidx-viewpager2 = "1.0.0"
|
||||
autodispose = "1.0.0"
|
||||
compose = "1.1.0"
|
||||
dokka = "1.4.32"
|
||||
junit = "4.13"
|
||||
kotest = "4.6.0"
|
||||
kotlin = "1.6.10"
|
||||
leakCanary = "2.7"
|
||||
lint = "30.2.1" # Should always be agp + 23
|
||||
material = "1.2.1"
|
||||
mvnpublish = "0.23.2"
|
||||
picasso = "2.5.2"
|
||||
robolectric = "4.5.1"
|
||||
rxjava2 = "2.1.14"
|
||||
|
||||
[libraries]
|
||||
activity-compose = { module = "androidx.activity:activity-compose", version.ref = "activity-compose" }
|
||||
agp = { module = "com.android.tools.build:gradle", version.ref = "agp" }
|
||||
androidx-annotation = { module = "androidx.annotation:annotation", version.ref = "androidx-annotation" }
|
||||
androidx-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "androidx-appcompat" }
|
||||
androidx-collection = { module = "androidx.collection:collection", version.ref = "androidx-collection" }
|
||||
androidx-core-ktx = { module = "androidx.core:core-ktx", version.ref = "androidx-core" }
|
||||
androidx-lifecycle-runtime = { module = "androidx.lifecycle:lifecycle-runtime", version.ref = "androidx-lifecycle" }
|
||||
androidx-lifecycle-livedata-core = { module = "androidx.lifecycle:lifecycle-livedata-core", version.ref = "androidx-lifecycle" }
|
||||
androidx-savedstate-ktx = { module = "androidx.savedstate:savedstate-ktx", version.ref = "androidx-savedstate" }
|
||||
androidx-transition = { module = "androidx.transition:transition", version.ref = "androidx-transition" }
|
||||
androidx-viewpager2 = { module = "androidx.viewpager2:viewpager2", version.ref = "androidx-viewpager2" }
|
||||
autodispose = { module = "com.uber.autodispose:autodispose", version.ref = "autodispose" }
|
||||
autodispose-lifecycle = { module = "com.uber.autodispose:autodispose-lifecycle", version.ref = "autodispose" }
|
||||
autodispose-ktx = { module = "com.uber.autodispose:autodispose-ktx", version.ref = "autodispose" }
|
||||
compose-foundation = { module = "androidx.compose.foundation:foundation", version.ref = "compose" }
|
||||
compose-ui = { module = "androidx.compose.ui:ui", version.ref = "compose" }
|
||||
compose-ui-tooling = { module = "androidx.compose.ui:ui-tooling", version.ref = "compose" }
|
||||
compose-ui-tooling-preview = { module = "androidx.compose.ui:ui-tooling-preview", version.ref = "compose" }
|
||||
compose-material = { module = "androidx.compose.material:material", version.ref = "compose" }
|
||||
dokka = { module = "org.jetbrains.dokka:dokka-gradle-plugin", version.ref = "dokka" }
|
||||
junit = { module = "junit:junit", version.ref = "junit" }
|
||||
kotest = { module = "io.kotest:kotest-assertions-core", version.ref = "kotest" }
|
||||
kotlin-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
|
||||
kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib-jdk8", version.ref = "kotlin" }
|
||||
leakCanary = { module = "com.squareup.leakcanary:leakcanary-android", version.ref = "leakCanary" }
|
||||
lint = { module = "com.android.tools.lint:lint", version.ref = "lint" }
|
||||
lint-api = { module = "com.android.tools.lint:lint-api", version.ref = "lint" }
|
||||
lint-checks = { module = "com.android.tools.lint:lint-checks", version.ref = "lint" }
|
||||
lint-tests = { module = "com.android.tools.lint:lint-tests", version.ref = "lint" }
|
||||
material = { module = "com.google.android.material:material", version.ref = "material" }
|
||||
picasso = { module = "com.squareup.picasso:picasso", version.ref = "picasso" }
|
||||
robolectric = { module = "org.robolectric:robolectric", version.ref = "robolectric" }
|
||||
rxjava2 = { module = "io.reactivex.rxjava2:rxjava", version.ref = "rxjava2" }
|
||||
|
||||
[plugins]
|
||||
mvnpublish = { id = "com.vanniktech.maven.publish", version.ref = "mvnpublish" }
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.1-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
||||
@@ -2,7 +2,6 @@ include ':conductor'
|
||||
include ':conductor-lint'
|
||||
include ':conductor-modules:viewpager'
|
||||
include ':conductor-modules:viewpager2'
|
||||
include ':conductor-modules:rxlifecycle2'
|
||||
include ':conductor-modules:autodispose'
|
||||
include ':conductor-modules:arch-components-lifecycle'
|
||||
include ':conductor-modules:androidx-transition'
|
||||
|
||||
Reference in New Issue
Block a user