Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c8ac58ad6a | |||
| 5f04d9de89 | |||
| d32fc813d0 | |||
| c2bc72c5ce | |||
| 924e4bebfa | |||
| 4ea4aa5c56 |
@@ -20,27 +20,27 @@ Conductor is architecture-agnostic and does not try to force any design decision
|
||||
## Installation
|
||||
|
||||
```gradle
|
||||
implementation 'com.bluelinelabs:conductor:3.1.3'
|
||||
implementation 'com.bluelinelabs:conductor:3.1.5'
|
||||
|
||||
// AndroidX Transition change handlers:
|
||||
implementation 'com.bluelinelabs:conductor-androidx-transition:3.1.3'
|
||||
implementation 'com.bluelinelabs:conductor-androidx-transition:3.1.5'
|
||||
|
||||
// ViewPager PagerAdapter:
|
||||
implementation 'com.bluelinelabs:conductor-viewpager:3.1.3'
|
||||
implementation 'com.bluelinelabs:conductor-viewpager:3.1.5'
|
||||
|
||||
// ViewPager2 Adapter:
|
||||
implementation 'com.bluelinelabs:conductor-viewpager2:3.1.3'
|
||||
implementation 'com.bluelinelabs:conductor-viewpager2:3.1.5'
|
||||
|
||||
// RxJava2 Autodispose support:
|
||||
implementation 'com.bluelinelabs:conductor-autodispose:3.1.3'
|
||||
implementation 'com.bluelinelabs:conductor-autodispose:3.1.5'
|
||||
|
||||
// Lifecycle-aware Controllers (architecture components):
|
||||
implementation 'com.bluelinelabs:conductor-archlifecycle:3.1.3'
|
||||
implementation 'com.bluelinelabs:conductor-archlifecycle:3.1.5'
|
||||
```
|
||||
|
||||
**SNAPSHOT**
|
||||
|
||||
Just use `3.1.4-SNAPSHOT` as your version number in any of the dependencies above and add the url to the snapshot repository:
|
||||
Just use `3.1.6-SNAPSHOT` as your version number in any of the dependencies above and add the url to the snapshot repository:
|
||||
|
||||
```gradle
|
||||
allprojects {
|
||||
|
||||
@@ -1005,27 +1005,30 @@ 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();
|
||||
}
|
||||
|
||||
@@ -742,6 +742,7 @@ public abstract class Router {
|
||||
@Override
|
||||
public void run() {
|
||||
containerFullyAttached = true;
|
||||
performPendingControllerChanges();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -861,12 +862,14 @@ public abstract class Router {
|
||||
to.setNeedsAttach(true);
|
||||
}
|
||||
pendingControllerChanges.add(transaction);
|
||||
container.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
performPendingControllerChanges();
|
||||
}
|
||||
});
|
||||
if (container != null) {
|
||||
container.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
performPendingControllerChanges();
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
ControllerChangeHandler.executeChange(transaction);
|
||||
}
|
||||
|
||||
+77
-21
@@ -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
|
||||
@@ -28,6 +29,7 @@ internal class OwnViewTreeLifecycleAndRegistry private constructor(
|
||||
|
||||
private var hasSavedState = false
|
||||
private var savedRegistryState = Bundle.EMPTY
|
||||
private val parentChangeListeners = mutableMapOf<String, Controller.LifecycleListener>()
|
||||
|
||||
init {
|
||||
controller.addLifecycleListener(object : Controller.LifecycleListener() {
|
||||
@@ -84,29 +86,17 @@ 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() &&
|
||||
changeController.view != null &&
|
||||
lifecycleRegistry.currentState == Lifecycle.State.RESUMED
|
||||
) {
|
||||
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_PAUSE)
|
||||
|
||||
savedRegistryState = Bundle()
|
||||
savedStateRegistryController.performSave(savedRegistryState)
|
||||
|
||||
hasSavedState = true
|
||||
}
|
||||
pauseOnChangeStart(
|
||||
targetController = controller,
|
||||
changeController = changeController,
|
||||
changeHandler = changeHandler,
|
||||
changeType = changeType,
|
||||
)
|
||||
}
|
||||
|
||||
override fun preDetach(controller: Controller, view: View) {
|
||||
@@ -147,6 +137,14 @@ internal class OwnViewTreeLifecycleAndRegistry private constructor(
|
||||
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY)
|
||||
}
|
||||
}
|
||||
|
||||
override fun postContextAvailable(controller: Controller, context: Context) {
|
||||
listenForParentChangeStart(controller)
|
||||
}
|
||||
|
||||
override fun preContextUnavailable(controller: Controller, context: Context) {
|
||||
stopListeningForParentChangeStart(controller)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -154,11 +152,69 @@ internal class OwnViewTreeLifecycleAndRegistry private constructor(
|
||||
|
||||
override fun getSavedStateRegistry() = savedStateRegistryController.savedStateRegistry
|
||||
|
||||
private fun listenForParentChangeStart(controller: Controller) {
|
||||
controller.parentController?.let { parent ->
|
||||
val changeListener = object : Controller.LifecycleListener() {
|
||||
override fun onChangeStart(
|
||||
controller: Controller,
|
||||
changeHandler: ControllerChangeHandler,
|
||||
changeType: ControllerChangeType
|
||||
) {
|
||||
pauseOnChangeStart(
|
||||
targetController = parent,
|
||||
changeController = controller,
|
||||
changeHandler = changeHandler,
|
||||
changeType = changeType,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
parent.addLifecycleListener(changeListener)
|
||||
parentChangeListeners[controller.instanceId] = changeListener
|
||||
|
||||
listenForParentChangeStart(parent)
|
||||
}
|
||||
}
|
||||
|
||||
private fun stopListeningForParentChangeStart(controller: Controller) {
|
||||
controller.parentController?.let { parent ->
|
||||
parentChangeListeners.remove(parent.instanceId)?.let { listener ->
|
||||
parent.removeLifecycleListener(listener)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val KEY_SAVED_STATE = "Registry.savedState"
|
||||
|
||||
fun own(target: Controller) {
|
||||
OwnViewTreeLifecycleAndRegistry(target)
|
||||
fun own(target: Controller): OwnViewTreeLifecycleAndRegistry {
|
||||
return OwnViewTreeLifecycleAndRegistry(target)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+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)
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
VERSION_CODE=3
|
||||
VERSION_NAME=3.1.4-SNAPSHOT
|
||||
VERSION_NAME=3.1.6-SNAPSHOT
|
||||
android.useAndroidX=true
|
||||
org.gradle.jvmargs=-Xmx1536m
|
||||
|
||||
|
||||
Reference in New Issue
Block a user