fix state emissions becoming out of sync when using async handlers

This commit is contained in:
Adam Brown 2022-11-02 09:39:55 +00:00
parent 72fa795d38
commit e753cd30ad
3 changed files with 21 additions and 10 deletions

View File

@ -11,7 +11,6 @@ import app.dapk.state.Store
import app.dapk.state.createStore
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.launch
class StateViewModel<S, E>(
reducerFactory: ReducerFactory<S>,
@ -32,7 +31,7 @@ class StateViewModel<S, E>(
}
override fun dispatch(action: Action) {
viewModelScope.launch { store.dispatch(action) }
store.dispatch(action)
}
}

View File

@ -4,18 +4,26 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import kotlin.reflect.KClass
fun <S> createStore(reducerFactory: ReducerFactory<S>, coroutineScope: CoroutineScope): Store<S> {
fun <S> createStore(reducerFactory: ReducerFactory<S>, coroutineScope: CoroutineScope, log: Boolean = false): Store<S> {
val subscribers = mutableListOf<(S) -> Unit>()
var state: S = reducerFactory.initialState()
return object : Store<S> {
private val scope = createScope(coroutineScope, this)
private val reducer = reducerFactory.create(scope)
override suspend fun dispatch(action: Action) {
scope.coroutineScope.launch {
override fun dispatch(action: Action) {
coroutineScope.launch {
if (log) println("??? dispatch $action")
state = reducer.reduce(action).also { nextState ->
if (nextState != state) {
if (log) {
println("??? action: $action result...")
println("??? current: $state")
println("??? next: $nextState")
}
subscribers.forEach { it.invoke(nextState) }
} else {
if (log) println("??? action: $action skipped, no change")
}
}
}
@ -35,7 +43,7 @@ interface ReducerFactory<S> {
}
fun interface Reducer<S> {
suspend fun reduce(action: Action): S
fun reduce(action: Action): S
}
private fun <S> createScope(coroutineScope: CoroutineScope, store: Store<S>) = object : ReducerScope<S> {
@ -45,7 +53,7 @@ private fun <S> createScope(coroutineScope: CoroutineScope, store: Store<S>) = o
}
interface Store<S> {
suspend fun dispatch(action: Action)
fun dispatch(action: Action)
fun getState(): S
fun subscribe(subscriber: (S) -> Unit)
}
@ -82,14 +90,18 @@ fun <S> createReducer(
actionHandlers.fold(acc) { acc, handler ->
when (handler) {
is ActionHandler.Async -> {
handler.handler.invoke(scope, action)
scope.coroutineScope.launch {
handler.handler.invoke(scope, action)
}
acc
}
is ActionHandler.Sync -> handler.handler.invoke(action, acc)
is ActionHandler.Delegate -> when (val next = handler.handler.invoke(scope, action)) {
is ActionHandler.Async -> {
next.handler.invoke(scope, action)
scope.coroutineScope.launch {
next.handler.invoke(scope, action)
}
acc
}

View File

@ -53,7 +53,7 @@ class ReducerTestScope<S, E>(
}
private val reducer: Reducer<S> = reducerFactory.create(reducerScope)
override suspend fun reduce(action: Action) = reducer.reduce(action).also {
override fun reduce(action: Action) = reducer.reduce(action).also {
capturedResult = it
}