Skip to content

Commit

Permalink
Expanded retained store extensions and changed key to be string
Browse files Browse the repository at this point in the history
  • Loading branch information
LachlanMcKee committed Feb 20, 2023
1 parent 18fd6ae commit eca3a97
Show file tree
Hide file tree
Showing 9 changed files with 153 additions and 18 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
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
Expand All @@ -28,7 +26,7 @@ import kotlin.reflect.KClass
*/
interface RetainedInstanceStore {

fun <T : Any> get(nodeId: String, clazz: KClass<*>, disposer: (T) -> Unit, factory: () -> T): T
fun <T : Any> get(nodeId: String, key: String, disposer: (T) -> Unit = {}, factory: () -> T): T

fun removeAll(nodeId: String)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,49 @@ package com.bumble.appyx.core.store

import com.bumble.appyx.core.modality.BuildContext

/**
* Obtains or creates an instance of a class using the class name as the key.
* If you need multiple instances of an object with the same key, do not use this extension.
*/
inline fun <reified T : Any> RetainedInstanceStore.get(
nodeId: String,
noinline disposer: (T) -> Unit = {},
noinline factory: () -> T
): T =
get(nodeId, T::class, disposer, factory)
get(
nodeId = nodeId,
key = T::class.java.name,
disposer = disposer,
factory = factory
)

/**
* Obtains or creates an instance of a class using the class name as the key, and uses the node id
* from the BuildContext.
*
* If you need multiple instances of an object with the same key, do not use this extension.
*/
inline fun <reified T : Any> BuildContext.getRetainedInstance(
noinline disposer: (T) -> Unit = {},
noinline factory: () -> T
) =
RetainedInstanceStore.get(identifier, disposer, factory)
RetainedInstanceStore.get(
nodeId = identifier,
disposer = disposer,
factory = factory
)

/**
* Obtains or creates an instance of a class using the node id from the BuildContext.
*/
inline fun <reified T : Any> BuildContext.getRetainedInstance(
key: String,
noinline disposer: (T) -> Unit = {},
noinline factory: () -> T
) =
RetainedInstanceStore.get(
nodeId = identifier,
key = key,
disposer = disposer,
factory = factory
)
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
package com.bumble.appyx.core.store

import kotlin.reflect.KClass

internal class RetainedInstanceStoreImpl : RetainedInstanceStore {

private val map: MutableMap<String, MutableMap<KClass<*>, ValueHolder<*>>> = HashMap()
private val map: MutableMap<String, MutableMap<String, ValueHolder<*>>> = HashMap()

@Suppress("UNCHECKED_CAST")
override fun <T : Any> get(nodeId: String, clazz: KClass<*>, disposer: (T) -> Unit, factory: () -> T): T =
override fun <T : Any> get(nodeId: String, key: String, disposer: (T) -> Unit, factory: () -> T): T =
map
.getOrPut(nodeId) { HashMap() }
.getOrPut(clazz) { ValueHolder(factory(), disposer) }
.getOrPut(key) { ValueHolder(factory(), disposer) }
.value as T

override fun removeAll(nodeId: String) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ 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 {
Expand Down Expand Up @@ -96,7 +95,7 @@ class NodeTest {
private class RetainedInstanceStoreStub : RetainedInstanceStore {
var removeAllNodeId: String? = null

override fun <T : Any> get(nodeId: String, clazz: KClass<*>, disposer: (T) -> Unit, factory: () -> T): T {
override fun <T : Any> get(nodeId: String, key: String, disposer: (T) -> Unit, factory: () -> T): T {
error("Not needed for this test")
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,44 @@ class RetainedInstanceStoreTest {
assertEquals(2L, longValue)
}

@Test
fun `GIVEN object stored with int type WHEN second object stored with int type THEN second factory not called`() {
var secondFactoryInvoked = false
store.get(identifier) { 1 }

store.get(identifier) {
secondFactoryInvoked = true
2
}

assertFalse(secondFactoryInvoked)
}

@Test
fun `GIVEN object stored with int type AND key WHEN second object stored with int type AND key THEN second factory called`() {
var secondFactoryInvoked = false
store.get(nodeId = identifier, key = "1") { 1 }

store.get(identifier, key = "2") {
secondFactoryInvoked = true
2
}

assertTrue(secondFactoryInvoked)
}

@Test
fun `GIVEN two objects stored with same type AND different keys WHEN get with same identifier THEN both objects returned`() {
store.get(nodeId = identifier, key = "1") { 1 }
store.get(nodeId = identifier, key = "2") { 2 }

val integerValue1 = store.get(nodeId = identifier, key = "1") { 5 }
val integerValue2 = store.get(nodeId = identifier, key = "2") { 6L }

assertEquals(1, integerValue1)
assertEquals(2, integerValue2)
}

@Test
fun `GIVEN object stored WHEN removeAll with same identifier THEN object is removed`() {
val obj = Any()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,42 @@
package com.bumble.appyx.interop.rx2.store

import com.bumble.appyx.core.modality.BuildContext
import com.bumble.appyx.core.store.RetainedInstanceStore
import com.bumble.appyx.core.store.get
import com.bumble.appyx.core.store.getRetainedInstance
import io.reactivex.disposables.Disposable

inline fun <reified T : Disposable> BuildContext.getRetainedDisposableInstance(noinline factory: () -> T) =
/**
* Obtains or creates an instance of a class via the [get] extension.
* The disposable will be disposed when the disposer function is called.
*/
inline fun <reified T : Disposable> RetainedInstanceStore.getDisposable(
nodeId: String,
noinline factory: () -> T
): T =
get(
nodeId = nodeId,
disposer = { it.dispose() },
factory = factory
)

/**
* Obtains or creates an instance of a class via the [getRetainedInstance] extension.
* The disposable will be disposed when the disposer function is called.
*
* If you need multiple instances of an object with the same key, do not use this extension.
*/
inline fun <reified T : Disposable> BuildContext.getRetainedDisposable(
noinline factory: () -> T
) =
getRetainedInstance(disposer = { it.dispose() }, factory = factory)

/**
* Obtains or creates an instance of a class via the [getRetainedInstance] extension.
* The disposable will be disposed when the disposer function is called.
*/
inline fun <reified T : Disposable> BuildContext.getRetainedDisposable(
key: String,
noinline factory: () -> T
) =
getRetainedInstance(key = key, disposer = { it.dispose() }, factory = factory)
Original file line number Diff line number Diff line change
@@ -1,8 +1,42 @@
package com.bumble.appyx.interop.rx3.store

import com.bumble.appyx.core.modality.BuildContext
import com.bumble.appyx.core.store.RetainedInstanceStore
import com.bumble.appyx.core.store.get
import com.bumble.appyx.core.store.getRetainedInstance
import io.reactivex.rxjava3.disposables.Disposable

inline fun <reified T : Disposable> BuildContext.getRetainedDisposableInstance(noinline factory: () -> T) =
/**
* Obtains or creates an instance of a class via the [get] extension.
* The disposable will be disposed when the disposer function is called.
*/
inline fun <reified T : Disposable> RetainedInstanceStore.getDisposable(
nodeId: String,
noinline factory: () -> T
): T =
get(
nodeId = nodeId,
disposer = { it.dispose() },
factory = factory
)

/**
* Obtains or creates an instance of a class via the [getRetainedInstance] extension.
* The disposable will be disposed when the disposer function is called.
*
* If you need multiple instances of an object with the same key, do not use this extension.
*/
inline fun <reified T : Disposable> BuildContext.getRetainedDisposable(
noinline factory: () -> T
) =
getRetainedInstance(disposer = { it.dispose() }, factory = factory)

/**
* Obtains or creates an instance of a class via the [getRetainedInstance] extension.
* The disposable will be disposed when the disposer function is called.
*/
inline fun <reified T : Disposable> BuildContext.getRetainedDisposable(
key: String,
noinline factory: () -> T
) =
getRetainedInstance(key = key, disposer = { it.dispose() }, factory = factory)
Original file line number Diff line number Diff line change
Expand Up @@ -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.core.store.getRetainedInstance
import com.bumble.appyx.interop.rx2.store.getRetainedDisposable
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
Expand All @@ -12,7 +12,7 @@ import com.bumble.appyx.sandbox.client.mvicoreexample.feature.MviCoreExampleFeat
class MviCoreExampleBuilder : Builder<String>() {

override fun build(buildContext: BuildContext, payload: String): Node {
val feature = buildContext.getRetainedInstance {
val feature = buildContext.getRetainedDisposable {
MviCoreExampleFeature(payload)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ 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.core.store.getRetainedInstance
import com.bumble.appyx.interop.rx2.store.getRetainedDisposable
import com.bumble.appyx.sandbox.client.mvicoreexample.feature.MviCoreExampleFeature

class MviCoreLeafBuilder : Builder<String>() {

override fun build(buildContext: BuildContext, payload: String): Node {
val feature = buildContext.getRetainedInstance {
val feature = buildContext.getRetainedDisposable {
MviCoreExampleFeature(payload)
}
val view = MviCoreLeafViewImpl()
Expand Down

0 comments on commit eca3a97

Please sign in to comment.