diff --git a/libraries/core/src/androidTest/kotlin/com/bumble/appyx/core/node/RetainedInstanceStoreTest.kt b/libraries/core/src/androidTest/kotlin/com/bumble/appyx/core/node/RetainedInstanceStoreTest.kt new file mode 100644 index 0000000000..5e1f2fdeb3 --- /dev/null +++ b/libraries/core/src/androidTest/kotlin/com/bumble/appyx/core/node/RetainedInstanceStoreTest.kt @@ -0,0 +1,62 @@ +package com.bumble.appyx.core.node + +import androidx.lifecycle.Lifecycle +import com.bumble.appyx.core.AppyxTestScenario +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.store.RetainedInstanceStore +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Rule +import org.junit.Test +import kotlin.reflect.KClass + +class RetainedInstanceStoreTest { + private val stubRetainedInstanceStore = StubRetainedInstanceStore() + + @get:Rule + val rule = AppyxTestScenario { buildContext -> + TestParentNode(buildContext, stubRetainedInstanceStore) + } + + @Test + fun WHEN_activity_finished_THEN_retained_instance_store_content_is_removed() { + rule.start() + + rule.activityScenario.moveToState(Lifecycle.State.DESTROYED) + + assertTrue(stubRetainedInstanceStore.removeAllCalled) + } + + @Test + fun WHEN_activity_recreated_THEN_retained_instance_store_content_is_not_removed() { + rule.start() + + rule.activityScenario.recreate() + + assertFalse(stubRetainedInstanceStore.removeAllCalled) + } + + class TestParentNode( + buildContext: BuildContext, + retainedInstanceStore: RetainedInstanceStore, + ) : Node( + buildContext = buildContext, + retainedInstanceStore = retainedInstanceStore, + ) + + class StubRetainedInstanceStore : RetainedInstanceStore { + var removeAllCalled: Boolean = false + + override fun get( + nodeId: String, + clazz: KClass<*>, + disposer: (T) -> Unit, + factory: () -> T + ): T = RetainedInstanceStore.get(nodeId, clazz, disposer, factory) + + override fun removeAll(nodeId: String) { + removeAllCalled = true + RetainedInstanceStore.removeAll(nodeId) + } + } +} diff --git a/libraries/core/src/main/kotlin/com/bumble/appyx/core/integrationpoint/ActivityIntegrationPoint.kt b/libraries/core/src/main/kotlin/com/bumble/appyx/core/integrationpoint/ActivityIntegrationPoint.kt index b12e098062..55403d8899 100644 --- a/libraries/core/src/main/kotlin/com/bumble/appyx/core/integrationpoint/ActivityIntegrationPoint.kt +++ b/libraries/core/src/main/kotlin/com/bumble/appyx/core/integrationpoint/ActivityIntegrationPoint.kt @@ -23,6 +23,9 @@ open class ActivityIntegrationPoint( override val permissionRequester: PermissionRequester get() = permissionRequestBoundary + override val isChangingConfigurations: Boolean + get() = activity.isChangingConfigurations + fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { activityBoundary.onActivityResult(requestCode, resultCode, data) } diff --git a/libraries/core/src/main/kotlin/com/bumble/appyx/core/integrationpoint/IntegrationPoint.kt b/libraries/core/src/main/kotlin/com/bumble/appyx/core/integrationpoint/IntegrationPoint.kt index da8f513913..1e7f32559e 100644 --- a/libraries/core/src/main/kotlin/com/bumble/appyx/core/integrationpoint/IntegrationPoint.kt +++ b/libraries/core/src/main/kotlin/com/bumble/appyx/core/integrationpoint/IntegrationPoint.kt @@ -18,6 +18,8 @@ abstract class IntegrationPoint( abstract val permissionRequester: PermissionRequester + abstract val isChangingConfigurations: Boolean + fun onSaveInstanceState(outState: Bundle) { requestCodeRegistry.onSaveInstanceState(outState) } diff --git a/libraries/core/src/main/kotlin/com/bumble/appyx/core/integrationpoint/IntegrationPointStub.kt b/libraries/core/src/main/kotlin/com/bumble/appyx/core/integrationpoint/IntegrationPointStub.kt index a0e78a2abd..c06406991a 100644 --- a/libraries/core/src/main/kotlin/com/bumble/appyx/core/integrationpoint/IntegrationPointStub.kt +++ b/libraries/core/src/main/kotlin/com/bumble/appyx/core/integrationpoint/IntegrationPointStub.kt @@ -18,6 +18,9 @@ class IntegrationPointStub : IntegrationPoint(savedInstanceState = null) { override val permissionRequester: PermissionRequester get() = error(ERROR) + override val isChangingConfigurations: Boolean + get() = false + override fun handleUpNavigation() { error(ERROR) } diff --git a/libraries/core/src/main/kotlin/com/bumble/appyx/core/modality/BuildContext.kt b/libraries/core/src/main/kotlin/com/bumble/appyx/core/modality/BuildContext.kt index 5f386e319f..9ee81c9197 100644 --- a/libraries/core/src/main/kotlin/com/bumble/appyx/core/modality/BuildContext.kt +++ b/libraries/core/src/main/kotlin/com/bumble/appyx/core/modality/BuildContext.kt @@ -1,9 +1,11 @@ package com.bumble.appyx.core.modality +import com.bumble.appyx.core.state.MutableSavedStateMap +import com.bumble.appyx.core.state.SavedStateMap import com.bumble.appyx.utils.customisations.NodeCustomisation import com.bumble.appyx.utils.customisations.NodeCustomisationDirectory import com.bumble.appyx.utils.customisations.NodeCustomisationDirectoryImpl -import com.bumble.appyx.core.state.SavedStateMap +import java.util.UUID data class BuildContext( val ancestryInfo: AncestryInfo, @@ -11,6 +13,8 @@ data class BuildContext( val customisations: NodeCustomisationDirectory, ) { companion object { + private const val IDENTIFIER_KEY = "build.context.identifier" + fun root( savedStateMap: SavedStateMap?, customisations: NodeCustomisationDirectory = NodeCustomisationDirectoryImpl() @@ -22,6 +26,18 @@ data class BuildContext( ) } - fun getOrDefault(defaultCustomisation: T) : T = + val identifier: String by lazy { + if (savedStateMap == null) { + UUID.randomUUID().toString() + } else { + savedStateMap[IDENTIFIER_KEY] as String? ?: error("onSaveInstanceState() was not called") + } + } + + fun getOrDefault(defaultCustomisation: T): T = customisations.getRecursivelyOrDefault(defaultCustomisation) + + fun onSaveInstanceState(state: MutableSavedStateMap) { + state[IDENTIFIER_KEY] = identifier + } } diff --git a/libraries/core/src/main/kotlin/com/bumble/appyx/core/node/Node.kt b/libraries/core/src/main/kotlin/com/bumble/appyx/core/node/Node.kt index 1a05bfee22..ef5575c447 100644 --- a/libraries/core/src/main/kotlin/com/bumble/appyx/core/node/Node.kt +++ b/libraries/core/src/main/kotlin/com/bumble/appyx/core/node/Node.kt @@ -2,6 +2,7 @@ package com.bumble.appyx.core.node import androidx.activity.compose.LocalOnBackPressedDispatcherOwner import androidx.annotation.CallSuper +import androidx.annotation.VisibleForTesting import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.DisposableEffect @@ -35,17 +36,24 @@ import com.bumble.appyx.core.plugin.plugins import com.bumble.appyx.core.state.MutableSavedStateMap import com.bumble.appyx.core.state.MutableSavedStateMapImpl import com.bumble.appyx.core.state.SavedStateMap +import com.bumble.appyx.core.store.RetainedInstanceStore import kotlinx.coroutines.withContext -import java.util.UUID @Suppress("TooManyFunctions") @Stable -open class Node( - buildContext: BuildContext, +open class Node @VisibleForTesting internal constructor( + val buildContext: BuildContext, val view: NodeView = EmptyNodeView, + private val retainedInstanceStore: RetainedInstanceStore, plugins: List = emptyList() ) : NodeLifecycle, NodeView by view, RequestCodeClient { + constructor( + buildContext: BuildContext, + view: NodeView = EmptyNodeView, + plugins: List = emptyList() + ) : this(buildContext, view, RetainedInstanceStore, plugins) + @Suppress("LeakingThis") // Implemented in the same way as in androidx.Fragment private val nodeLifecycle = NodeLifecycleImpl(this) @@ -77,7 +85,8 @@ open class Node( private var wasBuilt = false - val id = getNodeId(buildContext) + val id: String + get() = buildContext.identifier override val requestCodeClientId: String = id @@ -92,14 +101,6 @@ open class Node( }) } - private fun getNodeId(buildContext: BuildContext): String { - val state = buildContext.savedStateMap ?: return UUID.randomUUID().toString() - - return state[NODE_ID_KEY] as String? ?: error( - "super.onSaveInstanceState() was not called for the node: ${this::class.qualifiedName}" - ) - } - @Deprecated( replaceWith = ReplaceWith("executeAction(action)"), message = "Will be removed in 1.1" @@ -182,6 +183,9 @@ open class Node( } nodeLifecycle.updateLifecycleState(state) if (state == Lifecycle.State.DESTROYED) { + if (!integrationPoint.isChangingConfigurations) { + retainedInstanceStore.removeAll(id) + } plugins().forEach { it.destroy() } } } @@ -197,7 +201,7 @@ open class Node( @CallSuper protected open fun onSaveInstanceState(state: MutableSavedStateMap) { - state[NODE_ID_KEY] = id + buildContext.onSaveInstanceState(state) } fun finish() { @@ -231,8 +235,6 @@ open class Node( plugins().any { it.handleUpNavigation() } companion object { - private const val NODE_ID_KEY = "node.id" - // BackPressHandler is correct when only one of its properties is implemented. private fun BackPressHandler.isCorrect(): Boolean { val listIsOverriddenOrPluginIgnored = onBackPressedCallback == null diff --git a/libraries/core/src/main/kotlin/com/bumble/appyx/core/store/RetainedInstanceStore.kt b/libraries/core/src/main/kotlin/com/bumble/appyx/core/store/RetainedInstanceStore.kt new file mode 100644 index 0000000000..bd7f5ea328 --- /dev/null +++ b/libraries/core/src/main/kotlin/com/bumble/appyx/core/store/RetainedInstanceStore.kt @@ -0,0 +1,37 @@ +package com.bumble.appyx.core.store + +import kotlin.reflect.KClass + +/** + * A simple storage to preserve any objects during configuration change events. + * `factory` function will be invoked immediately on the same thread + * only if an object of the same class within the same Node does not exist. + * + * The framework will manage the lifecycle of provided objects + * and invoke `disposer` function to destroy objects properly. + * + * Sample usage: + * ```kotlin + * val feature = RetainedInstanceStore.get( + * owner = buildContext.identifier, + * factory = { FeatureImpl() }, + * disposer = { feature.dispose() } + * } + * ``` + * or + * * ```kotlin + * * val feature = buildContext.getRetainedInstance( + * * factory = { FeatureImpl() }, + * * disposer = { feature.dispose() } + * * } + * * ``` + */ +interface RetainedInstanceStore { + + fun get(nodeId: String, clazz: KClass<*>, disposer: (T) -> Unit, factory: () -> T): T + + fun removeAll(nodeId: String) + + companion object : RetainedInstanceStore by RetainedInstanceStoreImpl() + +} diff --git a/libraries/core/src/main/kotlin/com/bumble/appyx/core/store/RetainedInstanceStoreExt.kt b/libraries/core/src/main/kotlin/com/bumble/appyx/core/store/RetainedInstanceStoreExt.kt new file mode 100644 index 0000000000..b4dbf56837 --- /dev/null +++ b/libraries/core/src/main/kotlin/com/bumble/appyx/core/store/RetainedInstanceStoreExt.kt @@ -0,0 +1,16 @@ +package com.bumble.appyx.core.store + +import com.bumble.appyx.core.modality.BuildContext + +inline fun RetainedInstanceStore.get( + nodeId: String, + noinline disposer: (T) -> Unit = {}, + noinline factory: () -> T +): T = + get(nodeId, T::class, disposer, factory) + +inline fun BuildContext.getRetainedInstance( + noinline disposer: (T) -> Unit = {}, + noinline factory: () -> T +) = + RetainedInstanceStore.get(identifier, disposer, factory) diff --git a/libraries/core/src/main/kotlin/com/bumble/appyx/core/store/RetainedInstanceStoreImpl.kt b/libraries/core/src/main/kotlin/com/bumble/appyx/core/store/RetainedInstanceStoreImpl.kt new file mode 100644 index 0000000000..367e483042 --- /dev/null +++ b/libraries/core/src/main/kotlin/com/bumble/appyx/core/store/RetainedInstanceStoreImpl.kt @@ -0,0 +1,28 @@ +package com.bumble.appyx.core.store + +import kotlin.reflect.KClass + +internal class RetainedInstanceStoreImpl : RetainedInstanceStore { + + private val map: MutableMap, ValueHolder<*>>> = HashMap() + + @Suppress("UNCHECKED_CAST") + override fun get(nodeId: String, clazz: KClass<*>, disposer: (T) -> Unit, factory: () -> T): T = + map + .getOrPut(nodeId) { HashMap() } + .getOrPut(clazz) { ValueHolder(factory(), disposer) } + .value as T + + override fun removeAll(nodeId: String) { + map.remove(nodeId)?.values?.forEach { it.dispose() } + } + + private class ValueHolder( + val value: T, + private val disposer: (T) -> Unit + ) { + fun dispose() { + disposer(value) + } + } +} diff --git a/libraries/core/src/test/kotlin/com/bumble/appyx/core/node/NodeTest.kt b/libraries/core/src/test/kotlin/com/bumble/appyx/core/node/NodeTest.kt index c701182e38..6282aa9c73 100644 --- a/libraries/core/src/test/kotlin/com/bumble/appyx/core/node/NodeTest.kt +++ b/libraries/core/src/test/kotlin/com/bumble/appyx/core/node/NodeTest.kt @@ -1,9 +1,13 @@ package com.bumble.appyx.core.node import androidx.arch.core.executor.testing.InstantTaskExecutorRule +import androidx.lifecycle.Lifecycle import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.NodeTest.TestNode.Companion.StatusExecuted +import com.bumble.appyx.core.store.RetainedInstanceStore import com.bumble.appyx.testing.junit4.util.MainDispatcherRule +import com.bumble.appyx.testing.unit.common.util.TestIntegrationPoint +import com.bumble.appyx.testing.unit.common.util.TestUpNavigationHandler import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.UnconfinedTestDispatcher @@ -12,6 +16,7 @@ import org.junit.Assert.assertEquals import org.junit.Assert.assertNull import org.junit.Rule import org.junit.Test +import kotlin.reflect.KClass @OptIn(ExperimentalCoroutinesApi::class) class NodeTest { @@ -23,11 +28,12 @@ class NodeTest { val mainDispatcherRule = MainDispatcherRule() private val testScope = TestScope(UnconfinedTestDispatcher()) + private val retainedInstanceStore: RetainedInstanceStoreStub = RetainedInstanceStoreStub() @Test fun `executeWorkflow WHEN called THEN executes action returns current node`() = testScope.runTest { - val node = TestNode() + val node = TestNode(retainedInstanceStore) assertNull(node.status) node.changeStatus() @@ -35,8 +41,42 @@ class NodeTest { assertEquals(node.status, StatusExecuted) } + @Test + fun `When node is destroyed AND not changing configurations THEN RetainedInstanceStore cleared`() = + testScope.runTest { + val node = TestNode(retainedInstanceStore) + node.integrationPoint = TestIntegrationPoint( + upNavigationHandler = TestUpNavigationHandler(), + isChangingConfigurations = false + ) + node.onBuilt() + node.updateLifecycleState(Lifecycle.State.CREATED) - private class TestNode : Node(BuildContext.root(null)) { + node.updateLifecycleState(Lifecycle.State.DESTROYED) + + assertEquals(node.id, retainedInstanceStore.removeAllNodeId) + } + + @Test + fun `When node is destroyed AND changing configurations THEN RetainedInstanceStore not cleared`() = + testScope.runTest { + val node = TestNode(retainedInstanceStore) + node.integrationPoint = TestIntegrationPoint( + upNavigationHandler = TestUpNavigationHandler(), + isChangingConfigurations = true + ) + node.onBuilt() + node.updateLifecycleState(Lifecycle.State.CREATED) + + node.updateLifecycleState(Lifecycle.State.DESTROYED) + + assertEquals(null, retainedInstanceStore.removeAllNodeId) + } + + private class TestNode(retainedInstanceStore: RetainedInstanceStore) : Node( + buildContext = BuildContext.root(null), + retainedInstanceStore = retainedInstanceStore, + ) { var status: String? = null private set @@ -52,4 +92,16 @@ class NodeTest { } } + + private class RetainedInstanceStoreStub : RetainedInstanceStore { + var removeAllNodeId: String? = null + + override fun get(nodeId: String, clazz: KClass<*>, disposer: (T) -> Unit, factory: () -> T): T { + error("Not needed for this test") + } + + override fun removeAll(nodeId: String) { + removeAllNodeId = nodeId + } + } } diff --git a/libraries/core/src/test/kotlin/com/bumble/appyx/core/store/RetainedInstanceStoreTest.kt b/libraries/core/src/test/kotlin/com/bumble/appyx/core/store/RetainedInstanceStoreTest.kt new file mode 100644 index 0000000000..e9983f636f --- /dev/null +++ b/libraries/core/src/test/kotlin/com/bumble/appyx/core/store/RetainedInstanceStoreTest.kt @@ -0,0 +1,72 @@ +package com.bumble.appyx.core.store + +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertSame +import org.junit.Assert.assertTrue +import org.junit.Test + +class RetainedInstanceStoreTest { + + private val identifier = "identifier" + private val store = RetainedInstanceStoreImpl() + + @Test + fun `GIVEN object stored WHEN get with same identifier THEN same instance is retrieved`() { + val obj = Any() + store.get(identifier) { obj } + + val retrieved = store.get(identifier) { Any() } + + assertSame(obj, retrieved) + } + + @Test + fun `GIVEN object stored WHEN get with same identifier THEN factory not called`() { + var factoryCalled = false + store.get(identifier) { Any() } + + store.get(identifier) { + factoryCalled = true + Any() + } + + assertFalse(factoryCalled) + } + + @Test + fun `GIVEN two objects with different types stored WHEN get with same identifier THEN both objects returned`() { + store.get(identifier) { 1 } + store.get(identifier) { 2L } + + val integerValue = store.get(identifier) { 5 } + val longValue = store.get(identifier) { 6L } + + assertEquals(1, integerValue) + assertEquals(2L, longValue) + } + + @Test + fun `GIVEN object stored WHEN removeAll with same identifier THEN object is removed`() { + val obj = Any() + var disposed = false + store.get(identifier, disposer = { disposed = true }) { obj } + + store.removeAll(identifier) + + assertTrue(disposed) + } + + @Test + fun `GIVEN object stored WHEN removeAll with different identifier THEN object is not removed`() { + val obj = Any() + val otherIdentifier = "other" + var disposed = false + store.get(identifier) { obj } + store.get(otherIdentifier, disposer = { disposed = true }) { obj } + + store.removeAll(identifier) + + assertFalse(disposed) + } +} diff --git a/libraries/testing-unit-common/src/main/kotlin/com/bumble/appyx/testing/unit/common/util/TestIntegrationPoint.kt b/libraries/testing-unit-common/src/main/kotlin/com/bumble/appyx/testing/unit/common/util/TestIntegrationPoint.kt index f25611f3cb..dd56364c75 100644 --- a/libraries/testing-unit-common/src/main/kotlin/com/bumble/appyx/testing/unit/common/util/TestIntegrationPoint.kt +++ b/libraries/testing-unit-common/src/main/kotlin/com/bumble/appyx/testing/unit/common/util/TestIntegrationPoint.kt @@ -6,7 +6,8 @@ import com.bumble.appyx.core.integrationpoint.permissionrequester.PermissionRequ import com.bumble.appyx.core.navigation.upnavigation.UpNavigationHandler class TestIntegrationPoint( - private val upNavigationHandler: UpNavigationHandler + private val upNavigationHandler: UpNavigationHandler, + override val isChangingConfigurations: Boolean = false ) : IntegrationPoint(savedInstanceState = null), UpNavigationHandler by upNavigationHandler { var rootFinished: Boolean = false diff --git a/samples/sandbox/src/main/kotlin/com/bumble/appyx/sandbox/client/container/ContainerNode.kt b/samples/sandbox/src/main/kotlin/com/bumble/appyx/sandbox/client/container/ContainerNode.kt index 32e81694d7..6ededd3a0b 100644 --- a/samples/sandbox/src/main/kotlin/com/bumble/appyx/sandbox/client/container/ContainerNode.kt +++ b/samples/sandbox/src/main/kotlin/com/bumble/appyx/sandbox/client/container/ContainerNode.kt @@ -35,6 +35,8 @@ import com.bumble.appyx.sandbox.client.container.ContainerNode.NavTarget.Blocker import com.bumble.appyx.sandbox.client.container.ContainerNode.NavTarget.Customisations import com.bumble.appyx.sandbox.client.container.ContainerNode.NavTarget.IntegrationPointExample import com.bumble.appyx.sandbox.client.container.ContainerNode.NavTarget.LazyExamples +import com.bumble.appyx.sandbox.client.container.ContainerNode.NavTarget.MviCoreExample +import com.bumble.appyx.sandbox.client.container.ContainerNode.NavTarget.MviCoreLeafExample import com.bumble.appyx.sandbox.client.container.ContainerNode.NavTarget.NavModelExamples import com.bumble.appyx.sandbox.client.container.ContainerNode.NavTarget.Picker import com.bumble.appyx.sandbox.client.customisations.CustomisationsNode @@ -42,6 +44,8 @@ import com.bumble.appyx.sandbox.client.explicitnavigation.ExplicitNavigationExam import com.bumble.appyx.sandbox.client.integrationpoint.IntegrationPointExampleNode import com.bumble.appyx.sandbox.client.interop.InteropExampleActivity import com.bumble.appyx.sandbox.client.list.LazyListContainerNode +import com.bumble.appyx.sandbox.client.mvicoreexample.MviCoreExampleBuilder +import com.bumble.appyx.sandbox.client.mvicoreexample.leaf.MviCoreLeafBuilder import com.bumble.appyx.sandbox.client.navmodels.NavModelExamplesNode import com.bumble.appyx.utils.customisations.NodeCustomisation import kotlinx.parcelize.Parcelize @@ -79,6 +83,12 @@ class ContainerNode internal constructor( @Parcelize object Customisations : NavTarget() + + @Parcelize + object MviCoreExample : NavTarget() + + @Parcelize + object MviCoreLeafExample : NavTarget() } @Suppress("ComplexMethod") @@ -90,6 +100,11 @@ class ContainerNode internal constructor( is IntegrationPointExample -> IntegrationPointExampleNode(buildContext) is BlockerExample -> BlockerExampleNode(buildContext) is Customisations -> CustomisationsNode(buildContext) + is MviCoreExample -> MviCoreExampleBuilder().build(buildContext, "MVICore initial state") + is MviCoreLeafExample -> MviCoreLeafBuilder().build( + buildContext, + "MVICore leaf initial state" + ) } @Composable @@ -139,6 +154,8 @@ class ContainerNode internal constructor( } TextButton("Lazy Examples") { backStack.push(LazyExamples) } TextButton("Blocker") { backStack.push(BlockerExample) } + TextButton("MVICore Example") { backStack.push(MviCoreExample) } + TextButton("MVICore Leaf Example") { backStack.push(MviCoreLeafExample) } } } } diff --git a/samples/sandbox/src/main/kotlin/com/bumble/appyx/sandbox/client/mvicoreexample/MviCoreExampleBuilder.kt b/samples/sandbox/src/main/kotlin/com/bumble/appyx/sandbox/client/mvicoreexample/MviCoreExampleBuilder.kt index fb7f51e27e..c8a85442a3 100644 --- a/samples/sandbox/src/main/kotlin/com/bumble/appyx/sandbox/client/mvicoreexample/MviCoreExampleBuilder.kt +++ b/samples/sandbox/src/main/kotlin/com/bumble/appyx/sandbox/client/mvicoreexample/MviCoreExampleBuilder.kt @@ -3,7 +3,7 @@ package com.bumble.appyx.sandbox.client.mvicoreexample import com.bumble.appyx.core.builder.Builder import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node -import com.bumble.appyx.interop.rx2.plugin.disposeOnDestroyPlugin +import com.bumble.appyx.core.store.getRetainedInstance import com.bumble.appyx.navmodel.backstack.BackStack import com.bumble.appyx.sandbox.client.mvicoreexample.MviCoreExampleNode.NavTarget import com.bumble.appyx.sandbox.client.mvicoreexample.MviCoreExampleNode.NavTarget.Child1 @@ -12,7 +12,9 @@ import com.bumble.appyx.sandbox.client.mvicoreexample.feature.MviCoreExampleFeat class MviCoreExampleBuilder : Builder() { override fun build(buildContext: BuildContext, payload: String): Node { - val feature = MviCoreExampleFeature(payload) + val feature = buildContext.getRetainedInstance { + MviCoreExampleFeature(payload) + } val backStack = BackStack( initialElement = Child1, @@ -29,7 +31,7 @@ class MviCoreExampleBuilder : Builder() { buildContext = buildContext, backStack = backStack, view = view, - plugins = listOf(interactor, disposeOnDestroyPlugin(feature)) + plugins = listOf(interactor) ) } } diff --git a/samples/sandbox/src/main/kotlin/com/bumble/appyx/sandbox/client/mvicoreexample/leaf/MviCoreLeafBuilder.kt b/samples/sandbox/src/main/kotlin/com/bumble/appyx/sandbox/client/mvicoreexample/leaf/MviCoreLeafBuilder.kt index b10c490422..916c473644 100644 --- a/samples/sandbox/src/main/kotlin/com/bumble/appyx/sandbox/client/mvicoreexample/leaf/MviCoreLeafBuilder.kt +++ b/samples/sandbox/src/main/kotlin/com/bumble/appyx/sandbox/client/mvicoreexample/leaf/MviCoreLeafBuilder.kt @@ -3,13 +3,15 @@ package com.bumble.appyx.sandbox.client.mvicoreexample.leaf import com.bumble.appyx.core.builder.Builder import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node -import com.bumble.appyx.interop.rx2.plugin.disposeOnDestroyPlugin +import com.bumble.appyx.core.store.getRetainedInstance import com.bumble.appyx.sandbox.client.mvicoreexample.feature.MviCoreExampleFeature class MviCoreLeafBuilder : Builder() { override fun build(buildContext: BuildContext, payload: String): Node { - val feature = MviCoreExampleFeature(payload) + val feature = buildContext.getRetainedInstance { + MviCoreExampleFeature(payload) + } val view = MviCoreLeafViewImpl() val interactor = MviCoreLeafInteractor( view = view, @@ -19,7 +21,7 @@ class MviCoreLeafBuilder : Builder() { return MviCoreLeafNode( buildContext = buildContext, view = view, - plugins = listOf(interactor, disposeOnDestroyPlugin(feature)) + plugins = listOf(interactor) ) } }