Skip to content

Commit

Permalink
implement retained store builders and immediate lifecycle
Browse files Browse the repository at this point in the history
  • Loading branch information
Nek-12 committed Mar 8, 2024
1 parent 33336a5 commit fef5f75
Show file tree
Hide file tree
Showing 9 changed files with 139 additions and 34 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package pro.respawn.flowmvi.util

import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.MainCoroutineDispatcher
import kotlin.concurrent.Volatile

@Volatile
private var isImmediateSupported: Boolean = true

@Suppress("UnusedReceiverParameter")
public val MainCoroutineDispatcher.immediateOrDefault: MainCoroutineDispatcher
get() {
if (isImmediateSupported) {
try {
return Dispatchers.Main.immediate
} catch (ignored: UnsupportedOperationException) {
} catch (ignored: NotImplementedError) {
}

isImmediateSupported = false
}
return Dispatchers.Main
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package pro.respawn.flowmvi.util

import pro.respawn.flowmvi.api.FlowMVIDSL
import pro.respawn.flowmvi.api.MVIState
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract

Expand All @@ -26,4 +27,4 @@ public inline fun <reified T> Any?.typed(): T? = this as? T
/**
* Get the name of the class, removing the "State" suffix, if present.
*/
public inline fun <reified T> nameByType(): String? = T::class.simpleName?.removeSuffix("State")
public inline fun <reified T : MVIState> nameByType(): String? = T::class.simpleName?.removeSuffix("State")
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import com.arkivanov.essenty.instancekeeper.InstanceKeeper
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.cancel

public interface RetainedScope : CoroutineScope, InstanceKeeper.Instance {
internal interface RetainedScope : CoroutineScope, InstanceKeeper.Instance {

override fun onDestroy(): Unit = cancel()
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import pro.respawn.flowmvi.api.MVIIntent
import pro.respawn.flowmvi.api.MVIState
import pro.respawn.flowmvi.api.Store

public interface RetainedStore<S : MVIState, I : MVIIntent, A : MVIAction> : Store<S, I, A>, InstanceKeeper.Instance {
internal interface RetainedStore<S : MVIState, I : MVIIntent, A : MVIAction> : Store<S, I, A>, InstanceKeeper.Instance {

override fun onDestroy(): Unit = close()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package pro.respawn.flowmvi.decompose.dsl

import kotlinx.coroutines.CoroutineScope
import pro.respawn.flowmvi.api.MVIAction
import pro.respawn.flowmvi.api.MVIIntent
import pro.respawn.flowmvi.api.MVIState
import pro.respawn.flowmvi.api.Store
import pro.respawn.flowmvi.decompose.api.RetainedStore

@PublishedApi
internal fun <S : MVIState, I : MVIIntent, A : MVIAction> retained(
store: Store<S, I, A>,
scope: CoroutineScope?,
): RetainedStore<S, I, A> = object : Store<S, I, A> by store, RetainedStore<S, I, A> {
init {
if (scope != null) start(scope)
}
}

@PublishedApi
internal fun <S : MVIState, I : MVIIntent, A : MVIAction> Store<S, I, A>.retain(
scope: CoroutineScope?
): RetainedStore<S, I, A> = retained(this, scope)
Original file line number Diff line number Diff line change
@@ -1,27 +1,28 @@
// TODO: https://github.com/arkivanov/Essenty/issues/158
@file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")

package pro.respawn.flowmvi.decompose.dsl

import com.arkivanov.decompose.ComponentContext
import com.arkivanov.essenty.instancekeeper.InstanceKeeper
import com.arkivanov.essenty.instancekeeper.InstanceKeeperOwner
import com.arkivanov.essenty.instancekeeper.getOrCreate
import com.arkivanov.essenty.lifecycle.coroutines.immediateOrFallback
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob
import pro.respawn.flowmvi.decompose.api.RetainedScope
import pro.respawn.flowmvi.util.immediateOrDefault
import kotlin.coroutines.CoroutineContext

public fun createRetainedScope(
context: CoroutineContext = Dispatchers.Main.immediateOrFallback
private const val DefaultScopeKey = "CoroutineScope"

internal fun createRetainedScope(
context: CoroutineContext = Dispatchers.Main.immediateOrDefault
): RetainedScope = object : RetainedScope, CoroutineScope by CoroutineScope(context + SupervisorJob(context[Job])) {}

public fun InstanceKeeper.retainedScope(
context: CoroutineContext = Dispatchers.Main.immediateOrFallback,
): CoroutineScope = getOrCreate("CoroutineScope") { createRetainedScope(context) }
context: CoroutineContext = Dispatchers.Main.immediateOrDefault,
key: String = DefaultScopeKey,
): CoroutineScope = getOrCreate(key) { createRetainedScope(context) }

public fun ComponentContext.retainedScope(
context: CoroutineContext = Dispatchers.Main.immediateOrFallback,
): CoroutineScope = instanceKeeper.retainedScope(context)
public fun InstanceKeeperOwner.retainedScope(
context: CoroutineContext = Dispatchers.Main.immediateOrDefault,
key: String = DefaultScopeKey,
): CoroutineScope = instanceKeeper.retainedScope(context, key)
Original file line number Diff line number Diff line change
@@ -1,35 +1,41 @@
@file:Suppress("Indentation") // conflict between detekt <> ide
package pro.respawn.flowmvi.decompose.dsl

import com.arkivanov.decompose.ComponentContext
import com.arkivanov.essenty.instancekeeper.InstanceKeeper
import com.arkivanov.essenty.instancekeeper.InstanceKeeperOwner
import com.arkivanov.essenty.instancekeeper.getOrCreate
import kotlinx.coroutines.CoroutineScope
import pro.respawn.flowmvi.api.MVIAction
import pro.respawn.flowmvi.api.MVIIntent
import pro.respawn.flowmvi.api.MVIState
import pro.respawn.flowmvi.api.Store
import pro.respawn.flowmvi.decompose.api.RetainedStore
import pro.respawn.flowmvi.util.nameByType

public fun <S : MVIState, I : MVIIntent, A : MVIAction> retained(
store: Store<S, I, A>,
): RetainedStore<S, I, A> = object : Store<S, I, A> by store, RetainedStore<S, I, A> {}
// keeper

public inline fun <reified S : MVIState, I : MVIIntent, A : MVIAction> InstanceKeeper.retainedStore(
store: Store<S, I, A>,
key: Any = store.name ?: nameByType<S>()?.let { "${it}Store" } ?: store::class,
): Store<S, I, A> = getOrCreate(key = key) { retained(store) }
scope: CoroutineScope? = retainedScope(),
key: Any = "${requireNotNull(nameByType<S>())}Store",
@BuilderInference factory: () -> Store<S, I, A>,
): Store<S, I, A> = getOrCreate(key) { retained(factory(), scope) }

public fun <S : MVIState, I : MVIIntent, A : MVIAction> InstanceKeeper.retainedStore(
public inline fun <S : MVIState, I : MVIIntent, A : MVIAction> InstanceKeeper.retainedStore(
key: Any,
store: Store<S, I, A>,
): Store<S, I, A> = getOrCreate(key = key) { retained(store) }
scope: CoroutineScope? = retainedScope(),
factory: () -> Store<S, I, A>,
): Store<S, I, A> = getOrCreate(key) { retained(factory(), scope) }

public fun <S : MVIState, I : MVIIntent, A : MVIAction> ComponentContext.retainedStore(
// keeper owner

public inline fun <S : MVIState, I : MVIIntent, A : MVIAction> InstanceKeeperOwner.retainedStore(
key: Any,
store: Store<S, I, A>,
): Store<S, I, A> = instanceKeeper.retainedStore(key, store)
scope: CoroutineScope? = retainedScope(),
factory: () -> Store<S, I, A>,
): Store<S, I, A> = instanceKeeper.retainedStore(key, scope, factory)

public inline fun <reified S : MVIState, I : MVIIntent, A : MVIAction> ComponentContext.retainedStore(
store: Store<S, I, A>,
key: Any = store.name ?: nameByType<S>()?.let { "${it}Store" } ?: store::class,
): Store<S, I, A> = instanceKeeper.retainedStore(store, key)
public inline fun <reified S : MVIState, I : MVIIntent, A : MVIAction> InstanceKeeperOwner.retainedStore(
scope: CoroutineScope? = retainedScope(),
key: Any = "${requireNotNull(nameByType<S>())}Store",
@BuilderInference factory: () -> Store<S, I, A>,
): Store<S, I, A> = instanceKeeper.retainedStore(key, scope, factory)
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package pro.respawn.flowmvi.decompose.dsl

import com.arkivanov.essenty.instancekeeper.InstanceKeeper
import com.arkivanov.essenty.instancekeeper.InstanceKeeperOwner
import com.arkivanov.essenty.instancekeeper.getOrCreate
import kotlinx.coroutines.CoroutineScope
import pro.respawn.flowmvi.api.MVIAction
import pro.respawn.flowmvi.api.MVIIntent
import pro.respawn.flowmvi.api.MVIState
import pro.respawn.flowmvi.api.Store
import pro.respawn.flowmvi.dsl.BuildStore
import pro.respawn.flowmvi.dsl.store
import pro.respawn.flowmvi.util.nameByType

// keeper

public inline fun <S : MVIState, I : MVIIntent, A : MVIAction> InstanceKeeper.retainedStore(
initial: S,
name: String,
scope: CoroutineScope? = retainedScope(),
@BuilderInference builder: BuildStore<S, I, A>,
): Store<S, I, A> = getOrCreate(name) {
store(initial) {
this.name = name
builder()
}.retain(scope)
}

public inline fun <reified S : MVIState, I : MVIIntent, A : MVIAction> InstanceKeeper.retainedStore(
initial: S,
scope: CoroutineScope? = retainedScope(),
name: String = "${requireNotNull(nameByType<S>())}Store",
@BuilderInference builder: BuildStore<S, I, A>,
): Store<S, I, A> = retainedStore(initial, name, scope, builder)

// owner

public inline fun <S : MVIState, I : MVIIntent, A : MVIAction> InstanceKeeperOwner.retainedStore(
initial: S,
name: String,
scope: CoroutineScope? = retainedScope(),
@BuilderInference builder: BuildStore<S, I, A>,
): Store<S, I, A> = instanceKeeper.retainedStore(initial, name, scope, builder)

public inline fun <reified S : MVIState, I : MVIIntent, A : MVIAction> InstanceKeeperOwner.retainedStore(
initial: S,
scope: CoroutineScope? = retainedScope(),
name: String = "${requireNotNull(nameByType<S>())}Store",
@BuilderInference builder: BuildStore<S, I, A>,
): Store<S, I, A> = instanceKeeper.retainedStore(initial, name, scope, builder)
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package pro.respawn.flowmvi.savedstate.dsl

import android.os.Parcelable
import androidx.lifecycle.SavedStateHandle
import pro.respawn.flowmvi.api.MVIState
import pro.respawn.flowmvi.savedstate.api.Saver
import pro.respawn.flowmvi.savedstate.api.ThrowRecover
import pro.respawn.flowmvi.util.nameByType
Expand All @@ -30,8 +31,8 @@ public fun <T> SavedStateHandleSaver(
*
* The [key] parameter is derived from the simple class name of the state by default.
*/
public inline fun <reified T : Parcelable> ParcelableSaver(
public inline fun <reified T> ParcelableSaver(
handle: SavedStateHandle,
key: String = nameByType<T>() ?: "State",
noinline recover: suspend (e: Exception) -> T? = ThrowRecover,
): Saver<T> = SavedStateHandleSaver(handle, key, recover)
): Saver<T> where T : Parcelable, T : MVIState = SavedStateHandleSaver(handle, key, recover)

0 comments on commit fef5f75

Please sign in to comment.