Skip to content

Commit

Permalink
👮 Refactor BLoC
Browse files Browse the repository at this point in the history
  • Loading branch information
ookami-kb committed Jul 3, 2020
1 parent e174af5 commit 448d3e4
Show file tree
Hide file tree
Showing 8 changed files with 46 additions and 38 deletions.
17 changes: 6 additions & 11 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,40 +4,35 @@ 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.Emitter
import com.mews.app.bloc.Transition
import kotlinx.coroutines.InternalCoroutinesApi
import kotlinx.coroutines.flow.FlowCollector

abstract class BlocViewModel<EVENT : Any, STATE : Any> : ViewModel(), Bloc<EVENT, STATE> {
abstract val initialState: STATE

protected abstract suspend fun FlowCollector<STATE>.mapEventToState(event: EVENT)
protected abstract suspend fun Emitter<STATE>.mapEventToState(event: EVENT)

protected open suspend fun onTransition(transition: Transition<EVENT, STATE>) {}

protected open suspend fun onError(error: Throwable) {}

protected open suspend fun onEvent(event: EVENT) {}

override fun addAsync(event: EVENT) {
bloc.addAsync(event)
}
override fun emitAsync(event: EVENT) = bloc.emitAsync(event)

@InternalCoroutinesApi
override suspend fun collect(collector: FlowCollector<STATE>) {
return bloc.collect(collector)
}
override suspend fun collect(collector: FlowCollector<STATE>) = bloc.collect(collector)

override suspend fun add(event: EVENT) {
bloc.add(event)
}
override suspend fun emit(value: EVENT) = bloc.emit(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 FlowCollector<STATE>.mapEventToState(event: EVENT) {
override suspend fun Emitter<STATE>.mapEventToState(event: EVENT) {
this@BlocViewModel.apply { mapEventToState(event) }
}

Expand Down
23 changes: 15 additions & 8 deletions core/src/main/kotlin/com/mews/app/bloc/BaseBloc.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package com.mews.app.bloc
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.InternalCoroutinesApi
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.channels.Channel.Factory.UNLIMITED
import kotlinx.coroutines.channels.ProducerScope
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch

Expand All @@ -13,24 +15,24 @@ abstract class BaseBloc<EVENT : Any, STATE : Any>(private val scope: CoroutineSc

override val state: STATE get() = stateFlow.value

protected abstract suspend fun FlowCollector<STATE>.mapEventToState(event: EVENT)
protected abstract suspend fun Emitter<STATE>.mapEventToState(event: EVENT)

@InternalCoroutinesApi
override suspend fun collect(collector: FlowCollector<STATE>) = stateFlow.collect(collector)

private val eventChannel = Channel<EVENT>()

override suspend fun add(event: EVENT) {
override suspend fun emit(value: EVENT) {
try {
doOnEvent(event)
eventChannel.send(event)
doOnEvent(value)
eventChannel.send(value)
} catch (e: Throwable) {
doOnError(e)
}
}

override fun addAsync(event: EVENT) {
scope.launch { add(event) }
override fun emitAsync(event: EVENT) {
scope.launch { emit(event) }
}

private suspend fun doOnEvent(event: EVENT) {
Expand All @@ -56,8 +58,9 @@ abstract class BaseBloc<EVENT : Any, STATE : Any>(private val scope: CoroutineSc

init {
eventChannel.consumeAsFlow()
.flatMapConcat { event ->
flow<STATE> { mapEventToState(event) }
.buffer(capacity = UNLIMITED)
.flatMapMerge { event ->
channelFlow<STATE> { StateEmitter(this).mapEventToState(event) }
.catch { doOnError(it) }
.map { Transition(stateFlow.value, event, it) }
}
Expand All @@ -73,3 +76,7 @@ abstract class BaseBloc<EVENT : Any, STATE : Any>(private val scope: CoroutineSc
.launchIn(scope)
}
}

private class StateEmitter<S>(private val producerScope: ProducerScope<S>) : Emitter<S> {
override suspend fun emit(value: S) = producerScope.send(value)
}
6 changes: 3 additions & 3 deletions core/src/main/kotlin/com/mews/app/bloc/Bloc.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ package com.mews.app.bloc

import kotlinx.coroutines.flow.Flow

interface Bloc<EVENT : Any, STATE : Any> : Flow<STATE>, Sink<EVENT> {
interface Bloc<EVENT : Any, STATE : Any> : Flow<STATE>, Emitter<EVENT> {
val state: STATE

override suspend fun add(event: EVENT)
override suspend fun emit(value: EVENT)

fun addAsync(event: EVENT)
fun emitAsync(event: EVENT)
}
5 changes: 5 additions & 0 deletions core/src/main/kotlin/com/mews/app/bloc/Emitter.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.mews.app.bloc

interface Emitter<T> {
suspend fun emit(value: T)
}
5 changes: 0 additions & 5 deletions core/src/main/kotlin/com/mews/app/bloc/Sink.kt

This file was deleted.

12 changes: 6 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 @@ -23,9 +23,9 @@ class BlocTest {
emit(event.toString())
}
}
bloc.add(1)
bloc.add(2)
bloc.add(3)
bloc.emit(1)
bloc.emit(2)
bloc.emit(3)
}

Assert.assertEquals(listOf(1, 2, 3), delegate.events)
Expand All @@ -52,9 +52,9 @@ class BlocTest {
emit(event.toString())
}
}
bloc.add(1)
bloc.add(2)
bloc.add(3)
bloc.emit(1)
bloc.emit(2)
bloc.emit(3)
}

Assert.assertEquals(listOf(1, 2, 3), delegate.events)
Expand Down
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.emitAsync(MainEvent.Incremented) }
minus.setOnClickListener { vm.emitAsync(MainEvent.Decremented) }

connect(vm) { text.text = it.value.toString() }
}
Expand Down
12 changes: 9 additions & 3 deletions example/src/main/java/com/mews/app/bloc/example/MainViewModel.kt
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
package com.mews.app.bloc.example

import com.mews.app.bloc.Emitter
import com.mews.app.bloc.android.BlocViewModel
import kotlinx.coroutines.flow.FlowCollector
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext

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

override suspend fun FlowCollector<MainState>.mapEventToState(event: MainEvent) = when (event) {
MainEvent.Incremented -> emit(state.copy(value = state.value + 1))
override suspend fun Emitter<MainState>.mapEventToState(event: MainEvent) = when (event) {
MainEvent.Incremented -> withContext(Dispatchers.IO) {
// Do some heavy computations here
Thread.sleep(1000)
emit(state.copy(value = state.value + 1))
}
MainEvent.Decremented -> emit(state.copy(value = state.value - 1))
}
}

0 comments on commit 448d3e4

Please sign in to comment.