Skip to content

SideEffect

Abhi Muktheeswarar edited this page Aug 2, 2021 · 10 revisions
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 an Action reaches the dispatch 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 the Action is passed through a reducer.
  • actionStates: Flow<ActionState.Always<Action, State>> emit Action and current State after an Action has passed through a reducer irrespective of the State is changed or not. For things, that rely on a particular State and are okay to wait till the Action passed through a reducer, you can use actionStates. 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 for State transitions, i.e when you want to react when a particular State is entered, exited, etc... This is useful when using StateReserve as StateMachine. For more details, check out the StateReserve wiki page.

Accessing state

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 current State because when you are trying to access State using state() function when there is a bunch of Action in the queue to be processed by the reducer, the state() function will provide a State that may not be the current or most recent state.
  • suspend awaitState(): When you use awaitState() to get current State. It will suspend until all pending Action is processed by the reducer and guaranteed to provide the current State. This is useful when your task relies on a particular State.

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>()
Clone this wiki locally