-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #26 from respawn-app/2.0.0-beta08
2.0.0-beta08
- Loading branch information
Showing
14 changed files
with
246 additions
and
84 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
99 changes: 99 additions & 0 deletions
99
core/src/commonMain/kotlin/pro/respawn/flowmvi/plugins/CachePlugin.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
package pro.respawn.flowmvi.plugins | ||
|
||
import kotlinx.atomicfu.atomic | ||
import kotlinx.atomicfu.update | ||
import pro.respawn.flowmvi.api.MVIAction | ||
import pro.respawn.flowmvi.api.MVIIntent | ||
import pro.respawn.flowmvi.api.MVIState | ||
import pro.respawn.flowmvi.api.PipelineContext | ||
import pro.respawn.flowmvi.dsl.StoreBuilder | ||
import kotlin.properties.ReadOnlyProperty | ||
import kotlin.reflect.KProperty | ||
|
||
private const val AccessBeforeCachingMessage = """ | ||
Cached value was accessed before the store was started. | ||
Please make sure you access the property only after onStart() and before onStop() have been called | ||
and the plugin that caches the value was installed before first access. | ||
""" | ||
|
||
/** | ||
* A plugin that allows to cache the value of a property, scoping it to the [pro.respawn.flowmvi.api.Store]'s lifecycle. | ||
* This plugin will clear the value when [PipelineContext] is canceled and call the [init] block each time a store is | ||
* started to initialize it again. | ||
* | ||
* This plugin is similar to [lazy] but where value is also bound to the [pro.respawn.flowmvi.api.Store]'s lifecycle. | ||
* This plugin can be used to get access to the [PipelineContext] and execute suspending operations in to | ||
* initialize the value. | ||
* | ||
* The [init] block can be called **multiple times** in rare cases of concurrent access. | ||
* In practice, this should not happen | ||
* | ||
* This plugin is useful with legacy APIs that rely on the [kotlinx.coroutines.CoroutineScope] | ||
* to be present during the lifetime of a value, such as paging, and can be used to obtain the value in plugins such | ||
* as [whileSubscribedPlugin] without recreating it. | ||
* | ||
* The cached value **must not be accessed** before [pro.respawn.flowmvi.api.StorePlugin.onStart] and after | ||
* [pro.respawn.flowmvi.api.StorePlugin.onStop] have been called, where it will be uninitialized. | ||
* The [init] block is evaluated in [pro.respawn.flowmvi.api.StorePlugin.onStart] in the order the | ||
* cache plugin was installed. | ||
* | ||
* This plugin should **not be used** to run operations in [PipelineContext]. Use [initPlugin] for that. | ||
* | ||
* Access the delegated property as follows: | ||
* | ||
* ```kotlin | ||
* // in store's scope | ||
* val pagedItems: Flow<PagingData<Item>> by cache { // this: PipelineContext -> | ||
* repo.pagedItems().cachedIn(this) | ||
* } | ||
* ``` | ||
* | ||
* @see cache | ||
* @see cachePlugin | ||
*/ | ||
public class CachePlugin<out T, S : MVIState, I : MVIIntent, A : MVIAction> internal constructor( | ||
name: String? = null, | ||
private val init: suspend PipelineContext<S, I, A>.() -> T, | ||
) : AbstractStorePlugin<S, I, A>(name), ReadOnlyProperty<Any?, T> { | ||
|
||
private data object UNINITIALIZED { | ||
|
||
override fun toString() = "Uncached value" | ||
} | ||
|
||
private var _value = atomic<Any?>(UNINITIALIZED) | ||
|
||
/** | ||
* Returns true if the cached value is present | ||
*/ | ||
public val isCached: Boolean get() = _value.value !== UNINITIALIZED | ||
|
||
override suspend fun PipelineContext<S, I, A>.onStart(): Unit = _value.update { init() } | ||
|
||
override fun onStop(e: Exception?): Unit = _value.update { UNINITIALIZED } | ||
|
||
override fun getValue(thisRef: Any?, property: KProperty<*>): T = | ||
@Suppress("UNCHECKED_CAST") | ||
requireNotNull(_value.value as? T) { AccessBeforeCachingMessage } | ||
} | ||
|
||
/** | ||
* Creates and returns a new [CachePlugin] without installing it. | ||
* @see CachePlugin | ||
*/ | ||
public fun <T, S : MVIState, I : MVIIntent, A : MVIAction> cachePlugin( | ||
name: String? = null, | ||
init: suspend PipelineContext<S, I, A>.() -> T, | ||
): CachePlugin<T, S, I, A> = CachePlugin(name, init) | ||
|
||
/** | ||
* Creates and installs a new [CachePlugin], returning a delegate that can be used to get access to the property that | ||
* was cached. Please consult the documentation of the parent class to understand how to use this plugin. | ||
* | ||
* @return A [ReadOnlyProperty] granting access to the value returned from [init] | ||
* @see cachePlugin | ||
*/ | ||
public fun <T, S : MVIState, I : MVIIntent, A : MVIAction> StoreBuilder<S, I, A>.cache( | ||
name: String? = null, | ||
init: suspend PipelineContext<S, I, A>.() -> T, | ||
): ReadOnlyProperty<Any?, T> = cachePlugin(name, init).also { install(it) } |
39 changes: 39 additions & 0 deletions
39
core/src/commonMain/kotlin/pro/respawn/flowmvi/plugins/DisallowRestartPlugin.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
package pro.respawn.flowmvi.plugins | ||
|
||
import kotlinx.atomicfu.atomic | ||
import kotlinx.atomicfu.getAndUpdate | ||
import pro.respawn.flowmvi.api.MVIAction | ||
import pro.respawn.flowmvi.api.MVIIntent | ||
import pro.respawn.flowmvi.api.MVIState | ||
import pro.respawn.flowmvi.api.StorePlugin | ||
import pro.respawn.flowmvi.dsl.StoreBuilder | ||
import pro.respawn.flowmvi.dsl.plugin | ||
|
||
private const val DisallowRestartMessage = """ | ||
Store was disallowed to restart but was restarted. Please remove disallowRestartPlugin() or do not reuse the store. | ||
""" | ||
|
||
private const val DisallowRestartPluginName = "DisallowRestartPlugin" | ||
|
||
/** | ||
* Disallow restart plugin will allow the store to be [pro.respawn.flowmvi.api.Store.start]ed only once. | ||
* It will throw on any subsequent invocations of [StorePlugin.onStart]. | ||
* You can use this when you are sure that you are not going to restart your store. | ||
* I.e. you are using the scope with which you launch the store only once, such as viewModelScope on Android. | ||
* | ||
* There is no need to install this plugin multiple times so the plugin has a unique [StorePlugin.name]. | ||
*/ | ||
public fun <S : MVIState, I : MVIIntent, A : MVIAction> disallowRestartPlugin(): StorePlugin<S, I, A> = plugin { | ||
name = DisallowRestartPluginName | ||
val started = atomic(false) | ||
onStart { | ||
check(!started.getAndUpdate { true }) { DisallowRestartMessage } | ||
} | ||
} | ||
|
||
/** | ||
* Installs a new [disallowRestartPlugin]. Please consult the docs of the parent function to learn more. | ||
* This plugin can only be installed only once. | ||
*/ | ||
public fun <S : MVIState, I : MVIIntent, A : MVIAction> StoreBuilder<S, I, A>.disallowRestart(): Unit = | ||
install(disallowRestartPlugin()) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.