Skip to content
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
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 { [email protected] }

override suspend fun Sink<STATE>.mapEventToState(event: EVENT) {
[email protected] { mapEventToState(event) }
}
override suspend fun mapEventToState(event: EVENT, emitState: suspend (STATE) -> Unit) =
[email protected](event, emitState)

override suspend fun onTransition(transition: Transition<EVENT, STATE>) =
[email protected](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