Going full-featured¶
BaseFeature¶
If the reduced functionality of ReducerFeature and ActorReducerFeature is not enough for your case, this base class is your go-to.
BaseFeature takes four generic parameters:
BaseFeature<Wish, Action, Effect, State>
The new one here compared to the simpler Features is the Action.
Actions¶
Use-case:
- you need some kind of an "internal
Wish" to execute, but you don't want to leak it through yourWishsealed class, as it would make it publicly callable. - you want to be able to trigger these "internal
Wishes"
Action is a superset of Wish in the form of:
sealed class Wish {
object PublicWish1 : Wish()
object PublicWish2 : Wish()
object PublicWish3 : Wish()
}
sealed class Action {
data class Execute(val wish: Wish) : Action()
object InvalidateCache : Action()
object ReloadSomething : Action()
}
This has two implications:
-
For
BaseFeatureto know how your publicWishmaps to anAction, you need to supply a mapping function in the constructortypealias WishToAction<Wish, Action> = (Wish) -> Action -
Now your
Actorwill be acting uponActioninstead ofWish
So any incoming Wish is mapped to an Action, and executed in the Actor along with all other Actions:
class MyComplexFeature : BaseFeature<Wish, Action, Effect, State>(
// ...remainder omitted...
wishToAction = { Execute(it) },
actor = ActorImpl(),
) {
// ...remainder omitted...
sealed class Wish {
object PublicWish1 : Wish()
object PublicWish2 : Wish()
object PublicWish3 : Wish()
}
sealed class Action {
data class Execute(val wish: Wish) : Action()
object InvalidateCache : Action()
object ReloadSomething : Action()
}
class ActorImpl : Actor<State, Action, Effect> {
override fun invoke(state: State, action: Action): Observable<Effect> = when (action) {
is Execute -> when (action.wish) {
PublicWish1 -> TODO()
PublicWish2 -> TODO()
PublicWish3 -> TODO()
}
InvalidateCache -> TODO()
ReloadSomething -> TODO()
}
}
// ...remainder omitted...
}
So now you can have internal Actions, but how will you trigger them? Meet the PostProcessor.
PostProcessor¶
The PostProcessor (as the name implies) will have a chance to react after a certain Action was mapped to a certain Effect which was used to create a new State. At this point, it can signal the need for additional Actions:
typealias PostProcessor<Action, Effect, State> = (Action, Effect, State) -> Action?
Using the example above this could be:
class MyComplexFeature : BaseFeature<Wish, Action, Effect, State>(
// ...remainder omitted...
postProcessor = PostProcessorImpl()
) {
// ...remainder omitted...
class PostProcessorImpl : PostProcessor<Action, Effect, State> {
// do anything based on action (contains wish), effect, state
override fun invoke(action: Action, effect: Effect, state: State): Action? {
if (state.i == 101) {
return InvalidateCache
}
return null
}
}
}
The implementation of BaseFeature wires everything up for you from mapping your Wish to Action, calling your Actor, Reducer, and PostProcessor and emitting the next State.