Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rename SuspendExecutor to CoroutineExecutor, SuspendBootstrapper to CoroutineBootstrapper #265

Merged
merged 1 commit into from
Jul 27, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 9 additions & 9 deletions docs/store.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ Now it's time for the `Executor`. If you are interested you can find the interfa

There are two base `Executors` provided by `MVIKotlin`:
- [ReaktiveExecutor](https://github.com/arkivanov/MVIKotlin/blob/master/mvikotlin-extensions-reaktive/src/commonMain/kotlin/com/arkivanov/mvikotlin/extensions/reaktive/ReaktiveExecutor.kt) - this implementation is based on the [Reaktive](https://github.com/badoo/Reaktive) library and is provided by `mvikotlin-extensions-reaktive` module
- [SuspendExecutor](https://github.com/arkivanov/MVIKotlin/blob/master/mvikotlin-extensions-coroutines/src/commonMain/kotlin/com/arkivanov/mvikotlin/extensions/coroutines/SuspendExecutor.kt) - this implementation is based on the [Coroutines](https://github.com/Kotlin/kotlinx.coroutines) library and is provided by `mvikotlin-extensions-coroutines` module
- [CoroutineExecutor](https://github.com/arkivanov/MVIKotlin/blob/master/mvikotlin-extensions-coroutines/src/commonMain/kotlin/com/arkivanov/mvikotlin/extensions/coroutines/CoroutineExecutor.kt) - this implementation is based on the [Coroutines](https://github.com/Kotlin/kotlinx.coroutines) library and is provided by `mvikotlin-extensions-coroutines` module

Let's try both.

Expand Down Expand Up @@ -190,14 +190,14 @@ So we extended the `ReaktiveExecutor` class and implemented the `executeIntent`

> ⚠️ `ReaktiveExecutor` implements Reaktive's [DisposableScope](https://github.com/badoo/Reaktive#subscription-management-with-disposablescope) which provides a bunch of additional extension functions. We used one of those functions - `subscribeScoped`. This ensures that the subscription is disposed when the `Store` (and so the `Executor`) is disposed.

#### SuspendExecutor
#### CoroutineExecutor

```kotlin
internal class CalculatorStoreFactory(private val storeFactory: StoreFactory) {

// ...

private class ExecutorImpl : SuspendExecutor<Intent, Nothing, State, Result, Nothing>() {
private class ExecutorImpl : CoroutineExecutor<Intent, Nothing, State, Result, Nothing>() {
override suspend fun executeIntent(intent: Intent, getState: () -> State) =
when (intent) {
is Intent.Increment -> dispatch(Result.Value(getState().value + 1))
Expand All @@ -215,7 +215,7 @@ internal class CalculatorStoreFactory(private val storeFactory: StoreFactory) {
}
```

Here we extended the `SuspendExecutor` class. This gives us the `suspend fun executeIntent` method so we can use coroutines. The sum is calculated on the `Default` dispatcher and the `Result` is dispatched from on the `Main` thread.
Here we extended the `CoroutineExecutor` class. This gives us the `suspend fun executeIntent` method so we can use coroutines. The sum is calculated on the `Default` dispatcher and the `Result` is dispatched from on the `Main` thread.

#### Publishing Labels

Expand Down Expand Up @@ -284,14 +284,14 @@ internal class CalculatorStoreFactory(private val storeFactory: StoreFactory) {
}
```

And same for the `SuspendExecutor`:
And same for the `CoroutineExecutor`:

```kotlin
internal class CalculatorStoreFactory(private val storeFactory: StoreFactory) {

// ...

private class ExecutorImpl : SuspendExecutor<Intent, Action, State, Result, Nothing>() {
private class ExecutorImpl : CoroutineExecutor<Intent, Action, State, Result, Nothing>() {
override suspend fun executeAction(action: Action, getState: () -> State) =
when (action) {
is Action.Sum -> sum(action.n)
Expand Down Expand Up @@ -369,7 +369,7 @@ internal class CalculatorStoreFactory(private val storeFactory: StoreFactory) {
}
```

Using `SuspendBootstrapper` from the `mvikotlin-extensions-coroutines` module:
Using `CoroutineBootstrapper` from the `mvikotlin-extensions-coroutines` module:

```kotlin
internal class CalculatorStoreFactory(private val storeFactory: StoreFactory) {
Expand All @@ -390,14 +390,14 @@ internal class CalculatorStoreFactory(private val storeFactory: StoreFactory) {

// ...

private object BootstrapperImpl : SuspendBootstrapper<Action>() {
private object BootstrapperImpl : CoroutineBootstrapper<Action>() {
override suspend fun bootstrap() {
val sum = withContext(Dispatchers.Default) { (1L..1000000.toLong()).sum() }
dispatch(Action.SetValue(sum))
}
}

private class ExecutorImpl : SuspendExecutor<Intent, Action, State, Result, Nothing>() {
private class ExecutorImpl : CoroutineExecutor<Intent, Action, State, Result, Nothing>() {
override suspend fun executeAction(action: Action, getState: () -> State) =
when (action) {
is Action.SetValue -> dispatch(Result.Value(action.value))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package com.arkivanov.mvikotlin.extensions.coroutines

import com.arkivanov.mvikotlin.core.store.Bootstrapper
import com.arkivanov.mvikotlin.core.store.Store
import com.arkivanov.mvikotlin.utils.internal.atomic
import com.arkivanov.mvikotlin.utils.internal.initialize
import com.arkivanov.mvikotlin.utils.internal.requireValue
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
import kotlin.coroutines.CoroutineContext

/**
* An abstract implementation of the [Bootstrapper] that provides interoperability with coroutines.
* All coroutines are launched in a scope which closes when the [Bootstrapper] is disposed.
*/
abstract class CoroutineBootstrapper<Action : Any>(
mainContext: CoroutineContext = Dispatchers.Main
) : Bootstrapper<Action> {

private val actionConsumer = atomic<(Action) -> Unit>()
private val scope = CoroutineScope(mainContext)

final override fun init(actionConsumer: (Action) -> Unit) {
this.actionConsumer.initialize(actionConsumer)
}

/**
* Dispatches the `Action` to the [Store]
*
* @param action an `Action` to be dispatched
*/
protected fun dispatch(action: Action) {
actionConsumer.requireValue().invoke(action)
}

final override fun invoke() {
scope.launch {
bootstrap()
}
}

/**
* A suspending variant of the [Bootstrapper.invoke] method.
* The coroutine is launched in a scope which closes when the [Bootstrapper] is disposed.
*/
abstract suspend fun bootstrap()

override fun dispose() {
scope.cancel()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package com.arkivanov.mvikotlin.extensions.coroutines

import com.arkivanov.mvikotlin.core.annotations.MainThread
import com.arkivanov.mvikotlin.core.store.Bootstrapper
import com.arkivanov.mvikotlin.core.store.Executor
import com.arkivanov.mvikotlin.core.store.Executor.Callbacks
import com.arkivanov.mvikotlin.core.store.Reducer
import com.arkivanov.mvikotlin.core.store.Store
import com.arkivanov.mvikotlin.utils.internal.atomic
import com.arkivanov.mvikotlin.utils.internal.initialize
import com.arkivanov.mvikotlin.utils.internal.requireValue
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
import kotlin.coroutines.CoroutineContext

/**
* An abstract implementation of the [Executor] that provides interoperability with coroutines.
* All coroutines are launched in a scope which closes when the [Executor] is disposed.
*/
open class CoroutineExecutor<in Intent : Any, in Action : Any, in State : Any, Result : Any, Label : Any>(
mainContext: CoroutineContext = Dispatchers.Main
) : Executor<Intent, Action, State, Result, Label> {

private val callbacks = atomic<Callbacks<State, Result, Label>>()
private val getState: () -> State = { callbacks.requireValue().state }
private val scope = CoroutineScope(mainContext)

final override fun init(callbacks: Callbacks<State, Result, Label>) {
this.callbacks.initialize(callbacks)
}

final override fun handleIntent(intent: Intent) {
scope.launch {
executeIntent(intent, getState)
}
}

/**
* A suspending variant of the [Executor.handleIntent] method.
* The coroutine is launched in a scope which closes when the [Executor] is disposed.
*
* @param intent an `Intent` received by the [Store]
* @param getState a `State` supplier that returns the *current* `State` of the [Store]
*/
@MainThread
protected open suspend fun executeIntent(intent: Intent, getState: () -> State) {
}

final override fun handleAction(action: Action) {
scope.launch {
executeAction(action, getState)
}
}

/**
* Called for every `Action` produced by the [Executor]
* The coroutine is launched in a scope which closes when the [Executor] is disposed.
*
* @param action an `Action` produced by the [Bootstrapper]
* @param getState a `State` supplier that returns the *current* `State` of the [Store]
*/
@MainThread
protected open suspend fun executeAction(action: Action, getState: () -> State) {
}

override fun dispose() {
scope.cancel()
}

/**
* Dispatches the provided `Result` to the [Reducer].
* The updated `State` will be available immediately after this method returns.
*
* @param result a `Result` to be dispatched to the `Reducer`
*/
@MainThread
protected fun dispatch(result: Result) {
callbacks.requireValue().onResult(result)
}

/**
* Sends the provided `Label` to the [Store] for publication
*
* @param label a `Label` to be published
*/
@MainThread
protected fun publish(label: Label) {
callbacks.requireValue().onLabel(label)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ import kotlin.coroutines.CoroutineContext
* An abstract implementation of the [Bootstrapper] that provides interoperability with coroutines.
* All coroutines are launched in a scope which closes when the [Bootstrapper] is disposed.
*/
@Deprecated(
message = "Please use CoroutineBootstrapper",
replaceWith = ReplaceWith("CoroutineBootstrapper<Action>")
)
abstract class SuspendBootstrapper<Action : Any>(
mainContext: CoroutineContext = Dispatchers.Main
) : Bootstrapper<Action> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ import kotlin.coroutines.CoroutineContext
* An abstract implementation of the [Executor] that provides interoperability with coroutines.
* All coroutines are launched in a scope which closes when the [Executor] is disposed.
*/
@Deprecated(
message = "Please use CoroutineExecutor",
replaceWith = ReplaceWith("CoroutineExecutor<Intent, Action, State, Result, Label>")
)
open class SuspendExecutor<in Intent : Any, in Action : Any, in State : Any, Result : Any, Label : Any>(
mainContext: CoroutineContext = Dispatchers.Main
) : Executor<Intent, Action, State, Result, Label> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,11 +68,11 @@ import kotlin.js.JsName
* // Result entries
* }
*
* private class BootstrapperImpl: /* Extend either ReaktiveBootstrapper or SuspendBootstrapper */ {
* private class BootstrapperImpl: /* Extend either ReaktiveBootstrapper or CoroutineBootstrapper */ {
* // Implementation here
* }
*
* private class ExecutorImpl: /* Extend either ReaktiveExecutor or SuspendExecutor */ {
* private class ExecutorImpl: /* Extend either ReaktiveExecutor or CoroutineExecutor */ {
* // Implementation here
* }
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package com.arkivanov.mvikotlin.sample.todo.coroutines.store

import com.arkivanov.mvikotlin.core.store.Executor
import com.arkivanov.mvikotlin.core.store.StoreFactory
import com.arkivanov.mvikotlin.extensions.coroutines.SuspendExecutor
import com.arkivanov.mvikotlin.extensions.coroutines.CoroutineExecutor
import com.arkivanov.mvikotlin.sample.todo.common.database.TodoDatabase
import com.arkivanov.mvikotlin.sample.todo.common.database.TodoItem
import com.arkivanov.mvikotlin.sample.todo.common.internal.store.add.TodoAddStore.Intent
Expand All @@ -23,7 +23,7 @@ internal class TodoAddStoreFactory(

override fun createExecutor(): Executor<Intent, Nothing, State, Result, Label> = ExecutorImpl()

private inner class ExecutorImpl : SuspendExecutor<Intent, Nothing, State, Result, Label>(mainContext = mainContext) {
private inner class ExecutorImpl : CoroutineExecutor<Intent, Nothing, State, Result, Label>(mainContext = mainContext) {
override suspend fun executeIntent(intent: Intent, getState: () -> State) {
when (intent) {
is Intent.SetText -> dispatch(Result.TextChanged(intent.text))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package com.arkivanov.mvikotlin.sample.todo.coroutines.store

import com.arkivanov.mvikotlin.core.store.Executor
import com.arkivanov.mvikotlin.core.store.StoreFactory
import com.arkivanov.mvikotlin.extensions.coroutines.SuspendExecutor
import com.arkivanov.mvikotlin.extensions.coroutines.CoroutineExecutor
import com.arkivanov.mvikotlin.sample.todo.common.database.TodoDatabase
import com.arkivanov.mvikotlin.sample.todo.common.internal.store.details.TodoDetailsStore.Intent
import com.arkivanov.mvikotlin.sample.todo.common.internal.store.details.TodoDetailsStore.Label
Expand All @@ -23,7 +23,7 @@ internal class TodoDetailsStoreFactory(

override fun createExecutor(): Executor<Intent, Unit, State, Result, Label> = ExecutorImpl()

private inner class ExecutorImpl : SuspendExecutor<Intent, Unit, State, Result, Label>(mainContext = mainContext) {
private inner class ExecutorImpl : CoroutineExecutor<Intent, Unit, State, Result, Label>(mainContext = mainContext) {
override suspend fun executeAction(action: Unit, getState: () -> State) {
withContext(ioContext) {
database.get(itemId)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package com.arkivanov.mvikotlin.sample.todo.coroutines.store

import com.arkivanov.mvikotlin.core.store.Executor
import com.arkivanov.mvikotlin.core.store.StoreFactory
import com.arkivanov.mvikotlin.extensions.coroutines.SuspendExecutor
import com.arkivanov.mvikotlin.extensions.coroutines.CoroutineExecutor
import com.arkivanov.mvikotlin.sample.todo.common.database.TodoDatabase
import com.arkivanov.mvikotlin.sample.todo.common.internal.store.list.TodoListStore.Intent
import com.arkivanov.mvikotlin.sample.todo.common.internal.store.list.TodoListStore.State
Expand All @@ -21,7 +21,7 @@ internal class TodoListStoreFactory(

override fun createExecutor(): Executor<Intent, Unit, State, Result, Nothing> = ExecutorImpl()

private inner class ExecutorImpl : SuspendExecutor<Intent, Unit, State, Result, Nothing>(mainContext = mainContext) {
private inner class ExecutorImpl : CoroutineExecutor<Intent, Unit, State, Result, Nothing>(mainContext = mainContext) {
override suspend fun executeAction(action: Unit, getState: () -> State) {
withContext(ioContext) { database.getAll() }
.let(Result::Loaded)
Expand Down