Skip to content

Commit

Permalink
👮 Update events processing (#3)
Browse files Browse the repository at this point in the history
  • Loading branch information
ookami-kb authored Jul 8, 2020
1 parent 35aae18 commit 8033206
Show file tree
Hide file tree
Showing 7 changed files with 51 additions and 44 deletions.
10 changes: 3 additions & 7 deletions android/src/main/java/com/mews/app/bloc/android/BlocViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -13,21 +12,18 @@ import kotlinx.coroutines.flow.FlowCollector
abstract class BlocViewModel<EVENT : Any, STATE : Any> : ViewModel(), Bloc<EVENT, STATE> {
abstract val initialState: STATE

override fun addAsync(event: EVENT) = bloc.addAsync(event)

@InternalCoroutinesApi
override suspend fun collect(collector: FlowCollector<STATE>) = 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<EVENT, STATE>(viewModelScope) {
override val initialState: STATE by lazy { this@BlocViewModel.initialState }

override suspend fun Sink<STATE>.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<EVENT, STATE>) =
this@BlocViewModel.onTransition(transition)
Expand Down
25 changes: 9 additions & 16 deletions core/src/main/kotlin/com/mews/app/bloc/BaseBloc.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -22,7 +21,7 @@ abstract class BaseBloc<EVENT : Any, STATE : Any>(private val scope: CoroutineSc
channel.consumeAsFlow()
.let(::transformEvents)
.flatMapConcat { event ->
channelFlow<STATE> { StateSink(this).mapEventToState(event) }
channelFlow<STATE> { mapEventToState(event, ::send) }
.map { Transition(stateFlow.value, event, it) }
.catch { doOnError(it) }
.let(::transformTransitions)
Expand All @@ -40,19 +39,17 @@ abstract class BaseBloc<EVENT : Any, STATE : Any>(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)
Expand All @@ -68,7 +65,3 @@ abstract class BaseBloc<EVENT : Any, STATE : Any>(private val scope: CoroutineSc
onError(error)
}
}

private class StateSink<S>(private val producerScope: ProducerScope<S>) : Sink<S> {
override suspend fun add(value: S) = producerScope.send(value)
}
11 changes: 3 additions & 8 deletions core/src/main/kotlin/com/mews/app/bloc/Bloc.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,12 @@ interface Bloc<EVENT : Any, STATE : Any> : Flow<STATE>, Sink<EVENT> {
/**
* 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<STATE>.mapEventToState(event: EVENT)
suspend fun mapEventToState(event: EVENT, emitState: suspend (STATE) -> Unit)

/**
* Called whenever [transition] occurs before state is updated.
Expand Down
2 changes: 1 addition & 1 deletion core/src/main/kotlin/com/mews/app/bloc/Sink.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
package com.mews.app.bloc

interface Sink<T> {
suspend fun add(value: T)
fun add(value: T)
}
36 changes: 30 additions & 6 deletions core/src/test/kotlin/com/mews/app/bloc/BlocTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ class BlocTest {
val bloc = object : BaseBloc<Int, String>(scope) {
override val initialState: String = "0"

override suspend fun Sink<String>.mapEventToState(event: Int) {
add(event.toString())
override suspend fun mapEventToState(event: Int, emitState: suspend (String) -> Unit) {
emitState(event.toString())
}
}
bloc.add(1)
Expand All @@ -53,9 +53,9 @@ class BlocTest {
val bloc = object : BaseBloc<Int, String>(scope) {
override val initialState: String = "0"

override suspend fun Sink<String>.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)
Expand All @@ -80,7 +80,8 @@ class BlocTest {
val bloc = object : BaseBloc<Int, String>(scope) {
override val initialState: String = "0"

override suspend fun Sink<String>.mapEventToState(event: Int) = add(event.toString())
override suspend fun mapEventToState(event: Int, emitState: suspend (String) -> Unit) =
emitState(event.toString())

override fun transformEvents(flow: Flow<Int>): Flow<Int> =
flow.filter { it != 2 }
Expand All @@ -105,7 +106,8 @@ class BlocTest {
val bloc = object : BaseBloc<Int, String>(scope) {
override val initialState: String = "0"

override suspend fun Sink<String>.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<Int>): Flow<Int> = channelFlow {
Expand All @@ -130,4 +132,26 @@ class BlocTest {
)
Assert.assertEquals(expectedTransitions, delegate.transitions)
}

@Test
fun `can emit event during mapEventToState`() {
runBlocking {
val bloc = object : BaseBloc<Int, String>(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)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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() }
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
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

class MainViewModel : BlocViewModel<MainEvent, MainState>() {
override val initialState: MainState = MainState()

override suspend fun Sink<MainState>.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<MainEvent>): Flow<MainEvent> = events.transform {
Expand Down

0 comments on commit 8033206

Please sign in to comment.