Skip to content

Commit

Permalink
🔥 Remove side-effects
Browse files Browse the repository at this point in the history
  • Loading branch information
Peter Bryant committed Nov 23, 2021
1 parent 0e73392 commit 0957394
Show file tree
Hide file tree
Showing 30 changed files with 85 additions and 295 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ import kotlinx.coroutines.flow.Flow
*/

@Composable
fun <B : BlocBase<State, *>, State> BlocComposer(
fun <B : BlocBase<State>, State> BlocComposer(
bloc: B,
transformStates: Flow<State>.() -> Flow<State> = { this },
content: @Composable (State) -> Unit,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,51 +8,50 @@ import com.ptrbrynt.kotlin_bloc.core.BlocBase
import kotlinx.coroutines.flow.Flow

/**
* Takes a [bloc] and an [onSideEffect] callback and invokes [onSideEffect] in response to side
* effects in the [bloc].
* Takes a [bloc] and an [onState] callback and invokes [onState] in response to state changes in the [bloc].
*
* It should be used for side-effects resulting from new side-effects being emitted by the [bloc] e.g.
* It should be used for side-effects resulting from new states being emitted by the [bloc] e.g.
* navigation, showing a snackbar etc.
*
* If you want to build composables in response to new states, use [BlocComposer]
*
* ```kotlin
* BlocListener(bloc) { sideEffect ->
* // React to the new side effect here
* BlocListener(bloc) { state ->
* // React to the new state here
* }
* ```
*
* * An optional [transformSideEffects] can be implemented for more granular control over
* An optional [transformStates] can be implemented for more granular control over
* the frequency and specificity with which transitions occur.
*
* For example, to debounce the side effects:
* For example, to debounce the states:
*
* ```kotlin
* BlocListener(
* myBloc,
* transformSideEffects = { this.debounce(1000) },
* transformStates = { this.debounce(1000) },
* ) {
* // React to the new side-effect here
* // React to the new state here
* }
* ```
*
* @param bloc The bloc or cubit that the [BlocListener] will interact with.
* @param onSideEffect The callback function which will be invoked whenever a new `state` is emitted by the [bloc].
* @param transformSideEffects Provides more granular control over the [State] flow.
* @param onState The callback function which will be invoked whenever a new `state` is emitted by the [bloc].
* @param transformStates Provides more granular control over the [State] flow.
* @see BlocComposer
*/

@Composable
fun <B : BlocBase<*, SideEffect>, SideEffect> BlocListener(
fun <B : BlocBase<State>, State> BlocListener(
bloc: B,
transformSideEffects: Flow<SideEffect>.() -> Flow<SideEffect> = { this },
onSideEffect: suspend (SideEffect) -> Unit,
transformStates: Flow<State>.() -> Flow<State> = { this },
onState: suspend (State) -> Unit,
) {
val state by bloc.sideEffectFlow.transformSideEffects().collectAsState(initial = null)
val state by bloc.stateFlow.transformStates().collectAsState(initial = null)

state?.let {
LaunchedEffect(it) {
onSideEffect(it)
onState(it)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import kotlinx.coroutines.flow.map
*/

@Composable
fun <B : BlocBase<State, *>, State, T> BlocSelector(
fun <B : BlocBase<State>, State, T> BlocSelector(
bloc: B,
selector: (State) -> T,
content: @Composable (T) -> Unit,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,30 +12,22 @@ import com.ptrbrynt.kotlin_bloc.core.Transition
*/
class LoggingBlocObserver : BlocObserver() {

override fun <B : BlocBase<State, *>, State> onChange(bloc: B, change: Change<State>) {
override fun <B : BlocBase<State>, State> onChange(bloc: B, change: Change<State>) {
super.onChange(bloc, change)
Log.i(bloc::class.simpleName, change.toString())
}

override fun <B : BlocBase<*, *>> onCreate(bloc: B) {
override fun <B : BlocBase<*>> onCreate(bloc: B) {
super.onCreate(bloc)
Log.i(bloc::class.simpleName, "Created")
}

override fun <B : Bloc<Event, *, *>, Event> onEvent(bloc: B, event: Event) {
override fun <B : Bloc<Event, *>, Event> onEvent(bloc: B, event: Event) {
super.onEvent(bloc, event)
Log.i(bloc::class.simpleName, event.toString())
}

override fun <B : BlocBase<*, SideEffect>, SideEffect> onSideEffect(
bloc: B,
sideEffect: SideEffect,
) {
super.onSideEffect(bloc, sideEffect)
Log.i(bloc::class.simpleName, sideEffect.toString())
}

override fun <B : Bloc<Event, State, *>, Event, State> onTransition(
override fun <B : Bloc<Event, State>, Event, State> onTransition(
bloc: B,
transition: Transition<Event, State>,
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ class LoggingBlocObserverTest {
stream.println("I/CounterBloc: Increment")
stream.println("I/CounterBloc: Change(state=0, newState=1)")
stream.println("I/CounterBloc: Transition(state=0, event=Increment, newState=1)")
stream.println("I/CounterBloc: 1")
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,13 @@ import com.ptrbrynt.kotlin_bloc.core.Bloc

enum class CounterEvent { Increment, Decrement }

class CounterBloc : Bloc<CounterEvent, Int, Int>(0) {
class CounterBloc : Bloc<CounterEvent, Int>(0) {
init {
on<CounterEvent> { event ->
when (event) {
CounterEvent.Increment -> emit(state + 1)
CounterEvent.Decrement -> emit(state - 1)
}
emitSideEffect(state)
}
}
}
20 changes: 4 additions & 16 deletions core/src/main/java/com/ptrbrynt/kotlin_bloc/core/Bloc.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,14 @@ import kotlinx.coroutines.launch
* Takes [Event]s as input and transforms them into a [Flow]
* of [State]s as an output.
*
* Also emits a [Flow] of [SideEffect]s, which are non-state outputs emitted as a result of new
* [Event]s.
*
* @param initial The initial [State]
* @param Event The type of event this can receive
* @param State The type of state this emits
* @param SideEffect The type of side-effect this can emit
* @see Cubit
*/
@Suppress("LeakingThis")
abstract class Bloc<Event, State, SideEffect>(initial: State) :
BlocBase<State, SideEffect>(initial) {
abstract class Bloc<Event, State>(initial: State) :
BlocBase<State>(initial) {
protected val eventFlow = MutableSharedFlow<Event>()

init {
Expand All @@ -37,22 +33,14 @@ abstract class Bloc<Event, State, SideEffect>(initial: State) :
}

@PublishedApi
internal val emitter = object : Emitter<State, SideEffect> {
internal val emitter = object : Emitter<State> {
override suspend fun emit(state: State) {
mutableChangeFlow.emit(Change(this@Bloc.state, state))
}

override suspend fun emitEach(states: Flow<State>) {
states.onEach { emit(it) }.launchIn(blocScope)
}

override suspend fun emitSideEffect(sideEffect: SideEffect) {
mutableSideEffectFlow.emit(sideEffect)
}

override suspend fun emitSideEffects(sideEffects: Flow<SideEffect>) {
sideEffects.onEach { emitSideEffect(it) }.launchIn(blocScope)
}
}

/**
Expand All @@ -77,7 +65,7 @@ abstract class Bloc<Event, State, SideEffect>(initial: State) :
* @param E The type of [Event] that this handles
*/
protected inline fun <reified E : Event> on(
noinline mapEventToState: suspend Emitter<State, SideEffect>.(E) -> Unit,
noinline mapEventToState: suspend Emitter<State>.(E) -> Unit,
) {
eventFlow
.transformEvents()
Expand Down
32 changes: 1 addition & 31 deletions core/src/main/java/com/ptrbrynt/kotlin_bloc/core/BlocBase.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import kotlinx.coroutines.launch
* @param initial The initial [State]
*/
@Suppress("LeakingThis")
abstract class BlocBase<State, SideEffect>(initial: State) {
abstract class BlocBase<State>(initial: State) {

init {
Bloc.observer.onCreate(this)
Expand All @@ -29,18 +29,6 @@ abstract class BlocBase<State, SideEffect>(initial: State) {
}
}

protected val mutableSideEffectFlow = MutableSharedFlow<SideEffect>().apply {
blocScope.launch {
collect { onSideEffect(it) }
}
}

/**
* The [Flow] of [SideEffect]s
*/
val sideEffectFlow: Flow<SideEffect>
get() = mutableSideEffectFlow

/**
* The current [State] [Flow]
*/
Expand Down Expand Up @@ -73,22 +61,4 @@ abstract class BlocBase<State, SideEffect>(initial: State) {
Bloc.observer.onChange(this, change)
this.state = change.newState
}

/**
* Called whenever a [SideEffect] is emitted.
*
* **Note: `super.onSideEffect` should always be called first.**
*
* ```kotlin
* override fun onSideEffect(sideEffect: SideEffect) {
* // Always call super.onSideEffect first
* super.onSideEffect(sideEffect)
*
* // Custom logic goes here
* }
* ```
*/
open fun onSideEffect(sideEffect: SideEffect) {
Bloc.observer.onSideEffect(this, sideEffect)
}
}
17 changes: 4 additions & 13 deletions core/src/main/java/com/ptrbrynt/kotlin_bloc/core/BlocObserver.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@ abstract class BlocObserver {
*
* @param bloc The [Bloc] or [Cubit] which was created.
*/
open fun <B : BlocBase<*, *>> onCreate(bloc: B) {}
open fun <B : BlocBase<*>> onCreate(bloc: B) {}

/**
* Called whenever an [event] is `add`ed to any [bloc].
*
* @param bloc The [Bloc] to which the [Event] was `add`ed
* @param event The [Event] added to the [Bloc]
*/
open fun <B : Bloc<Event, *, *>, Event> onEvent(bloc: B, event: Event) {}
open fun <B : Bloc<Event, *>, Event> onEvent(bloc: B, event: Event) {}

/**
* Called whenever a [Change] occurs in any [Bloc] or [Cubit].
Expand All @@ -31,7 +31,7 @@ abstract class BlocObserver {
* @param bloc The [Bloc] or [Cubit] which emitted the [change]
* @param change The [Change] that occurred within the [bloc]
*/
open fun <B : BlocBase<State, *>, State> onChange(bloc: B, change: Change<State>) {}
open fun <B : BlocBase<State>, State> onChange(bloc: B, change: Change<State>) {}

/**
* Called whenever a [Transition] occurs in any [Bloc].
Expand All @@ -43,18 +43,9 @@ abstract class BlocObserver {
* @param bloc The [Bloc] in which the [transition] occurred
* @param transition The [Transition] which occurred within the [bloc]
*/
open fun <B : Bloc<Event, State, *>, Event, State> onTransition(
open fun <B : Bloc<Event, State>, Event, State> onTransition(
bloc: B,
transition: Transition<Event, State>,
) {
}

/**
* Called whenever a [SideEffect] is emitted by a [Bloc] or [Cubit].
*/
open fun <B : BlocBase<*, SideEffect>, SideEffect> onSideEffect(
bloc: B,
sideEffect: SideEffect,
) {
}
}
15 changes: 3 additions & 12 deletions core/src/main/java/com/ptrbrynt/kotlin_bloc/core/Cubit.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import kotlinx.coroutines.flow.onEach

/**
* A [Cubit] is similar to a [Bloc] but has no notion of events,
* instead relying on methods to [emit] [State]s and [SideEffect]s.
* instead relying on methods to [emit] [State]s..
*
* Every [Cubit] requires an initial state, which will be the state
* of the [Cubit] before [emit] has been called.
Expand All @@ -20,24 +20,15 @@ import kotlinx.coroutines.flow.onEach
*
* @param initial The initial [State]
* @param State The type of state this emits
* @param SideEffect The type of side-effect this can emit
* @see Bloc
*/
abstract class Cubit<State, SideEffect>(initial: State) :
BlocBase<State, SideEffect>(initial), Emitter<State, SideEffect> {
abstract class Cubit<State>(initial: State) :
BlocBase<State>(initial), Emitter<State> {
override suspend fun emit(state: State) {
mutableChangeFlow.emit(Change(this.state, state))
}

override suspend fun emitEach(states: Flow<State>) {
states.onEach { emit(it) }.launchIn(blocScope)
}

override suspend fun emitSideEffect(sideEffect: SideEffect) {
mutableSideEffectFlow.emit(sideEffect)
}

override suspend fun emitSideEffects(sideEffects: Flow<SideEffect>) {
sideEffects.onEach { emitSideEffect(it) }.launchIn(blocScope)
}
}
12 changes: 1 addition & 11 deletions core/src/main/java/com/ptrbrynt/kotlin_bloc/core/Emitter.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import kotlinx.coroutines.flow.Flow
/**
* Interface which can be implemented on any object which can [emit] a [State] or a [SideEffect]
*/
interface Emitter<State, SideEffect> {
interface Emitter<State> {
/**
* Emit a new [State]
*/
Expand All @@ -15,14 +15,4 @@ interface Emitter<State, SideEffect> {
* [emit] each [State] which is emitted by the [states] [Flow].
*/
suspend fun emitEach(states: Flow<State>)

/**
* Emit a new [SideEffect]
*/
suspend fun emitSideEffect(sideEffect: SideEffect)

/**
* Emit a [Flow] of [SideEffect]s.
*/
suspend fun emitSideEffects(sideEffects: Flow<SideEffect>)
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ object Decrement : CounterEvent()
open class CounterBloc(
private val onTransitionCallback: ((Transition<CounterEvent, Int>) -> Unit)? = null,
private val onEventCallback: ((CounterEvent) -> Unit)? = null,
) : Bloc<CounterEvent, Int, Unit>(0) {
) : Bloc<CounterEvent, Int>(0) {

init {
on<Increment> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ data class MultiFlowNumberAdded(val number: Int) : MultiFlowEvent()

@ExperimentalCoroutinesApi

class MultiFlowBloc : Bloc<MultiFlowEvent, List<Int>, Unit>(emptyList()) {
class MultiFlowBloc : Bloc<MultiFlowEvent, List<Int>>(emptyList()) {
private val numbers = MutableStateFlow(emptyList<Int>())

init {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package com.ptrbrynt.kotlin_bloc.core.blocs

import com.ptrbrynt.kotlin_bloc.core.Bloc

class SeededBloc(private val seed: List<Int>, initial: Int) : Bloc<String, Int, Unit>(initial) {
class SeededBloc(private val seed: List<Int>, initial: Int) : Bloc<String, Int>(initial) {
init {
on<String> {
for (value in seed) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import com.ptrbrynt.kotlin_bloc.core.Change
import com.ptrbrynt.kotlin_bloc.core.Cubit

class CounterCubit(private val onChangeCallback: ((Change<Int>) -> Unit)? = null) :
Cubit<Int, Unit>(0) {
Cubit<Int>(0) {
suspend fun increment() = emit(state + 1)
suspend fun decrement() = emit(state - 1)

Expand Down
Loading

0 comments on commit 0957394

Please sign in to comment.