diff --git a/android/src/main/java/com/mews/app/bloc/android/BlocViewModel.kt b/android/src/main/java/com/mews/app/bloc/android/BlocViewModel.kt index 4deccbe..3ba93dc 100644 --- a/android/src/main/java/com/mews/app/bloc/android/BlocViewModel.kt +++ b/android/src/main/java/com/mews/app/bloc/android/BlocViewModel.kt @@ -4,7 +4,6 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.mews.app.bloc.BaseBloc import com.mews.app.bloc.Bloc -import com.mews.app.bloc.Sink import com.mews.app.bloc.Transition import kotlinx.coroutines.InternalCoroutinesApi import kotlinx.coroutines.flow.Flow @@ -13,21 +12,18 @@ import kotlinx.coroutines.flow.FlowCollector abstract class BlocViewModel : ViewModel(), Bloc { abstract val initialState: STATE - override fun addAsync(event: EVENT) = bloc.addAsync(event) - @InternalCoroutinesApi override suspend fun collect(collector: FlowCollector) = bloc.collect(collector) - override suspend fun add(value: EVENT) = bloc.add(value) + override fun add(value: EVENT) = bloc.add(value) override val state: STATE get() = bloc.state private val bloc = object : BaseBloc(viewModelScope) { override val initialState: STATE by lazy { this@BlocViewModel.initialState } - override suspend fun Sink.mapEventToState(event: EVENT) { - this@BlocViewModel.apply { mapEventToState(event) } - } + override suspend fun mapEventToState(event: EVENT, emitState: suspend (STATE) -> Unit) = + this@BlocViewModel.mapEventToState(event, emitState) override suspend fun onTransition(transition: Transition) = this@BlocViewModel.onTransition(transition) diff --git a/core/src/main/kotlin/com/mews/app/bloc/BaseBloc.kt b/core/src/main/kotlin/com/mews/app/bloc/BaseBloc.kt index fa955ff..f4ee83a 100644 --- a/core/src/main/kotlin/com/mews/app/bloc/BaseBloc.kt +++ b/core/src/main/kotlin/com/mews/app/bloc/BaseBloc.kt @@ -3,7 +3,6 @@ package com.mews.app.bloc import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.InternalCoroutinesApi import kotlinx.coroutines.channels.Channel -import kotlinx.coroutines.channels.ProducerScope import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch @@ -22,7 +21,7 @@ abstract class BaseBloc(private val scope: CoroutineSc channel.consumeAsFlow() .let(::transformEvents) .flatMapConcat { event -> - channelFlow { StateSink(this).mapEventToState(event) } + channelFlow { mapEventToState(event, ::send) } .map { Transition(stateFlow.value, event, it) } .catch { doOnError(it) } .let(::transformTransitions) @@ -40,19 +39,17 @@ abstract class BaseBloc(private val scope: CoroutineSc } } - override suspend fun add(value: EVENT) { - try { - doOnEvent(value) - eventChannel.send(value) - } catch (e: Throwable) { - doOnError(e) + override fun add(value: EVENT) { + scope.launch { + try { + doOnEvent(value) + eventChannel.send(value) + } catch (e: Throwable) { + doOnError(e) + } } } - override fun addAsync(event: EVENT) { - scope.launch { add(event) } - } - private suspend fun doOnEvent(event: EVENT) { BlocSupervisor.delegate?.onEvent(event) onEvent(event) @@ -68,7 +65,3 @@ abstract class BaseBloc(private val scope: CoroutineSc onError(error) } } - -private class StateSink(private val producerScope: ProducerScope) : Sink { - override suspend fun add(value: S) = producerScope.send(value) -} diff --git a/core/src/main/kotlin/com/mews/app/bloc/Bloc.kt b/core/src/main/kotlin/com/mews/app/bloc/Bloc.kt index 7df5c6a..42552ca 100644 --- a/core/src/main/kotlin/com/mews/app/bloc/Bloc.kt +++ b/core/src/main/kotlin/com/mews/app/bloc/Bloc.kt @@ -11,17 +11,12 @@ interface Bloc : Flow, Sink { /** * Call this function to emit a new [EVENT] that should be processed by bloc. */ - override suspend fun add(value: EVENT) + override fun add(value: EVENT) /** - * Call this function to emit a new [EVENT] asynchronously within bloc scope. + * Processes an incoming [event] and optionally emits a new [STATE] with [emitState]. */ - fun addAsync(event: EVENT) - - /** - * Takes an incoming [event] and emits new [STATE]. - */ - suspend fun Sink.mapEventToState(event: EVENT) + suspend fun mapEventToState(event: EVENT, emitState: suspend (STATE) -> Unit) /** * Called whenever [transition] occurs before state is updated. diff --git a/core/src/main/kotlin/com/mews/app/bloc/Sink.kt b/core/src/main/kotlin/com/mews/app/bloc/Sink.kt index 9c74e87..2a86c34 100644 --- a/core/src/main/kotlin/com/mews/app/bloc/Sink.kt +++ b/core/src/main/kotlin/com/mews/app/bloc/Sink.kt @@ -1,5 +1,5 @@ package com.mews.app.bloc interface Sink { - suspend fun add(value: T) + fun add(value: T) } diff --git a/core/src/test/kotlin/com/mews/app/bloc/BlocTest.kt b/core/src/test/kotlin/com/mews/app/bloc/BlocTest.kt index be42078..c736e5e 100644 --- a/core/src/test/kotlin/com/mews/app/bloc/BlocTest.kt +++ b/core/src/test/kotlin/com/mews/app/bloc/BlocTest.kt @@ -26,8 +26,8 @@ class BlocTest { val bloc = object : BaseBloc(scope) { override val initialState: String = "0" - override suspend fun Sink.mapEventToState(event: Int) { - add(event.toString()) + override suspend fun mapEventToState(event: Int, emitState: suspend (String) -> Unit) { + emitState(event.toString()) } } bloc.add(1) @@ -53,9 +53,9 @@ class BlocTest { val bloc = object : BaseBloc(scope) { override val initialState: String = "0" - override suspend fun Sink.mapEventToState(event: Int) { + override suspend fun mapEventToState(event: Int, emitState: suspend (String) -> Unit) { if (event == 2) throw IllegalArgumentException("Test error") - add(event.toString()) + emitState(event.toString()) } } bloc.add(1) @@ -80,7 +80,8 @@ class BlocTest { val bloc = object : BaseBloc(scope) { override val initialState: String = "0" - override suspend fun Sink.mapEventToState(event: Int) = add(event.toString()) + override suspend fun mapEventToState(event: Int, emitState: suspend (String) -> Unit) = + emitState(event.toString()) override fun transformEvents(flow: Flow): Flow = flow.filter { it != 2 } @@ -105,7 +106,8 @@ class BlocTest { val bloc = object : BaseBloc(scope) { override val initialState: String = "0" - override suspend fun Sink.mapEventToState(event: Int) = add(event.toString()) + override suspend fun mapEventToState(event: Int, emitState: suspend (String) -> Unit) = + emitState(event.toString()) // Could be simplified after https://github.com/Kotlin/kotlinx.coroutines/issues/2034 override fun transformEvents(events: Flow): Flow = channelFlow { @@ -130,4 +132,26 @@ class BlocTest { ) Assert.assertEquals(expectedTransitions, delegate.transitions) } + + @Test + fun `can emit event during mapEventToState`() { + runBlocking { + val bloc = object : BaseBloc(scope) { + override val initialState: String = "0" + + override suspend fun mapEventToState(event: Int, emitState: suspend (String) -> Unit) = + if (event == 2) add(10) else emitState(event.toString()) + } + bloc.add(1) + bloc.add(2) + bloc.add(3) + } + + val expectedTransitions = listOf( + Transition("0", 1, "1"), + Transition("1", 10, "10"), + Transition("10", 3, "3") + ) + Assert.assertEquals(expectedTransitions, delegate.transitions) + } } diff --git a/example/src/main/java/com/mews/app/bloc/example/MainActivity.kt b/example/src/main/java/com/mews/app/bloc/example/MainActivity.kt index 9360cd3..8744d6a 100644 --- a/example/src/main/java/com/mews/app/bloc/example/MainActivity.kt +++ b/example/src/main/java/com/mews/app/bloc/example/MainActivity.kt @@ -14,8 +14,8 @@ class MainActivity : AppCompatActivity() { setContentView(R.layout.activity_main) setSupportActionBar(toolbar) - plus.setOnClickListener { vm.addAsync(MainEvent.Incremented) } - minus.setOnClickListener { vm.addAsync(MainEvent.Decremented) } + plus.setOnClickListener { vm.add(MainEvent.Incremented) } + minus.setOnClickListener { vm.add(MainEvent.Decremented) } connect(vm) { text.text = it.value.toString() } } diff --git a/example/src/main/java/com/mews/app/bloc/example/MainViewModel.kt b/example/src/main/java/com/mews/app/bloc/example/MainViewModel.kt index 5b855f6..63a46e4 100644 --- a/example/src/main/java/com/mews/app/bloc/example/MainViewModel.kt +++ b/example/src/main/java/com/mews/app/bloc/example/MainViewModel.kt @@ -1,6 +1,5 @@ package com.mews.app.bloc.example -import com.mews.app.bloc.Sink import com.mews.app.bloc.android.BlocViewModel import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.transform @@ -8,9 +7,9 @@ import kotlinx.coroutines.flow.transform class MainViewModel : BlocViewModel() { override val initialState: MainState = MainState() - override suspend fun Sink.mapEventToState(event: MainEvent) = when (event) { - MainEvent.Incremented -> add(state.copy(value = state.value + 1)) - MainEvent.Decremented -> add(state.copy(value = state.value - 1)) + override suspend fun mapEventToState(event: MainEvent, emitState: suspend (MainState) -> Unit) = when (event) { + MainEvent.Incremented -> emitState(state.copy(value = state.value + 1)) + MainEvent.Decremented -> emitState(state.copy(value = state.value - 1)) } override fun transformEvents(events: Flow): Flow = events.transform {