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 app.dapk.state.createStore
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.launch
class StateViewModel<S, E>( class StateViewModel<S, E>(
reducerFactory: ReducerFactory<S>, reducerFactory: ReducerFactory<S>,
@ -32,7 +31,7 @@ class StateViewModel<S, E>(
} }
override fun dispatch(action: Action) { 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 kotlinx.coroutines.launch
import kotlin.reflect.KClass 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>() val subscribers = mutableListOf<(S) -> Unit>()
var state: S = reducerFactory.initialState() var state: S = reducerFactory.initialState()
return object : Store<S> { return object : Store<S> {
private val scope = createScope(coroutineScope, this) private val scope = createScope(coroutineScope, this)
private val reducer = reducerFactory.create(scope) private val reducer = reducerFactory.create(scope)
override suspend fun dispatch(action: Action) { override fun dispatch(action: Action) {
scope.coroutineScope.launch { coroutineScope.launch {
if (log) println("??? dispatch $action")
state = reducer.reduce(action).also { nextState -> state = reducer.reduce(action).also { nextState ->
if (nextState != state) { if (nextState != state) {
if (log) {
println("??? action: $action result...")
println("??? current: $state")
println("??? next: $nextState")
}
subscribers.forEach { it.invoke(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> { 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> { 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> { interface Store<S> {
suspend fun dispatch(action: Action) fun dispatch(action: Action)
fun getState(): S fun getState(): S
fun subscribe(subscriber: (S) -> Unit) fun subscribe(subscriber: (S) -> Unit)
} }
@ -82,14 +90,18 @@ fun <S> createReducer(
actionHandlers.fold(acc) { acc, handler -> actionHandlers.fold(acc) { acc, handler ->
when (handler) { when (handler) {
is ActionHandler.Async -> { is ActionHandler.Async -> {
scope.coroutineScope.launch {
handler.handler.invoke(scope, action) handler.handler.invoke(scope, action)
}
acc acc
} }
is ActionHandler.Sync -> handler.handler.invoke(action, acc) is ActionHandler.Sync -> handler.handler.invoke(action, acc)
is ActionHandler.Delegate -> when (val next = handler.handler.invoke(scope, action)) { is ActionHandler.Delegate -> when (val next = handler.handler.invoke(scope, action)) {
is ActionHandler.Async -> { is ActionHandler.Async -> {
scope.coroutineScope.launch {
next.handler.invoke(scope, action) next.handler.invoke(scope, action)
}
acc acc
} }

View File

@ -53,7 +53,7 @@ class ReducerTestScope<S, E>(
} }
private val reducer: Reducer<S> = reducerFactory.create(reducerScope) 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 capturedResult = it
} }