-
Notifications
You must be signed in to change notification settings - Fork 3
SideEffect
abstract class SideEffect<S : State>(
private val stateReserve: StateReserve<S>,
protected val dispatchers: DispatcherProvider,
) {
protected val scope: CoroutineScope = stateReserve.config.scope
protected val actions: Flow<Action> = stateReserve.actions
protected val actionStates: Flow<ActionState.Always<Action, S>> = stateReserve.actionStates
protected val transitions: Flow<Any> = stateReserve.transitions
fun dispatch(action: Action) {
stateReserve.dispatch(action)
}
fun state(): S = stateReserve.state()
suspend fun awaitState(): S = stateReserve.awaitState()
}
SideEffect
is the place where all impure stuff goes. i.e SideEffect
are the place to make API calls, DB changes, navigation, logging, etc...
You can think of a side-effect as a use case in a clean architecture.
Example:
class GetMoviesSideEffect(
private val moviesRepository: MoviesRepository,
stateReserve: StateReserve<MovieScreenState>,
dispatchers: DispatcherProvider
) : SideEffect<MovieScreenState>(stateReserve, dispatchers) {
init {
stateReserve.actionStates.onEach(::handle).launchIn(scope)
}
private fun handle(actionState: ActionState<Action, MovieScreenState>) {
if (actionState.action is MovieAction.GetMoviesAction && actionState.state.movies.isEmpty()) {
scope.launch {
moviesRepository.getMovies().fold(
{ movies -> stateReserve.dispatch(MovieAction.MoviesLoadedAction(movies)) },
{ error -> stateReserve.dispatch(MovieAction.ErrorLoadingMoviesAction(error)) }
)
}
}
}
}
The SideEffect
provides three Flow
types: actions
, actionStates
, transitions
.
-
actions: Flow<Action>
are emitted as soon as anAction
reaches thedispatch
function and yet to pass through a reducer. This is useful when you are listening for navigation related actions where we don't want to wait till theAction
is passed through a reducer. -
actionStates: Flow<ActionState.Always<Action, State>>
emitAction
and currentState
after anAction
has passed through a reducer irrespective of theState
is changed or not. For things, that rely on a particularState
and are okay to wait till theAction
passed through a reducer, you can useactionStates
. Example: IO related tasks, where you might want to check if that data already exists in the current state before doing any API calls. -
transitions: Flow<Any>
: To listen forState
transitions, i.e when you want to react when a particularState
is entered, exited, etc... This is useful when usingStateReserve
asStateMachine
. For more details, check out the StateReserve wiki page.
The SideEffect
provides two functions for accessing state: state()
and suspend awaitState()
.
The difference between the two is that:
-
state()
: It is not guaranteed to provide the currentState
because when you are trying to accessState
usingstate()
function when there is a bunch ofAction
in the queue to be processed by the reducer, thestate()
function will provide aState
that may not be the current or most recent state. -
suspend awaitState()
: When you useawaitState()
to get currentState
. It will suspend until all pendingAction
is processed by the reducer and guaranteed to provide the currentState
. This is useful when your task relies on a particularState
.
Try to stick with awaitState()
for accessing State
. The state()
function is provided to access State
synchronously usually by view component or in places where we can't wait to access the State
. So use state()
with caution in SideEffect
.
If you need both previous State
, current State
along with Action
. You can collect transitions: Flow<Any>
flow using one of the provided extension functions. Please note: You need to enable enhancedStateMachine
config to use transitions: Flow<Any>
.
transitions.validTransitionWithAction<Action, PreviousState, CurrentState>()