Skip to content

Instantly share code, notes, and snippets.

@zsoltk
Last active June 1, 2018 08:49
Show Gist options
  • Save zsoltk/add93eb49bf987c3724e0e9b07c17e47 to your computer and use it in GitHub Desktop.
Save zsoltk/add93eb49bf987c3724e0e9b07c17e47 to your computer and use it in GitHub Desktop.
News examples
class ReducerFeatureWithoutNews : ReducerFeature<Wish, State, Nothing>(
initialState = State(),
reducer = ReducerImpl()
) {
data class State(
val counter: Int = 0
)
sealed class Wish {
object IncreaseCounter : Wish()
}
class ReducerImpl : Reducer<State, Wish> {
override fun invoke(state: State, effect: Wish): State = when (effect) {
IncreaseCounter -> state.copy(
counter = state.counter + 1
)
}
}
}
class ReducerFeatureWithNews : ReducerFeature<Wish, State, News>(
initialState = State(),
reducer = ReducerImpl(),
newsPublisher = NewsPublisherImpl()
) {
data class State(
val counter: Int = 0
)
sealed class Wish {
object IncreaseCounter : Wish()
}
sealed class News {
object LoremIpsum : News()
object Unused : News()
}
class ReducerImpl : Reducer<State, Wish> {
override fun invoke(state: State, effect: Wish): State = when (effect) {
IncreaseCounter -> state.copy(
counter = state.counter + 1
)
}
}
class NewsPublisherImpl : SimpleNewsPublisher<Wish, State, News>() {
override fun invoke(wish: Wish, state: State): News? =
when {
// this is dumb, but you get the idea
(wish === IncreaseCounter && state.counter == 101) -> LoremIpsum
else -> null
}
}
}
// ---------------------------------------------
object ClientCode {
class NewsListener(private val context: Context) : Consumer<News> {
override fun accept(news: News) {
// don't have to react on News.Unused if you don't want
when (news) {
is LoremIpsum -> {
Toast.makeText(context, "Lorem ipsum", Toast.LENGTH_SHORT).show()
}
}
}
}
}
class ActorReducerFeatureWithNews : DefaultFeature<Wish, Action, Effect, State, News>(
initialState = State(),
wishToAction = { Execute(it) },
actor = ActorImpl(),
reducer = ReducerImpl(),
newsPublisher = NewsPublisherImpl()
) {
data class State(
val isLoading: Boolean = false,
val payload: Any? = null
)
sealed class Wish {
object LoadNewData : Wish()
}
sealed class News {
data class ErrorExecutingRequest(val throwable : Throwable) : News()
}
sealed class Action {
data class Execute(val wish: Wish) : Action()
}
sealed class Effect {
object StartedLoading : Effect()
data class ReceivedPayload(val payload: Any) : Effect()
data class FailedLoading(val throwable : Throwable) : Effect()
}
class ActorImpl : Actor<State, Action, Effect> {
private val service: Observable<Any> = TODO()
override fun invoke(state: State, action: Action): Observable<Effect> = when (action) {
is Execute -> when (action.wish) {
LoadNewData -> {
service
.observeOn(AndroidSchedulers.mainThread())
.map { ReceivedPayload(it) as Effect }
.startWith(just(StartedLoading))
.onErrorReturn { FailedLoading(it) }
}
}
}
}
class ReducerImpl : Reducer<State, Effect> {
override fun invoke(state: State, effect: Effect): State = when (effect) {
StartedLoading -> state.copy(
isLoading = true
)
is ReceivedPayload -> state.copy(
isLoading = false,
payload = effect.payload
)
is FailedLoading -> state.copy(
isLoading = false
)
}
}
class NewsPublisherImpl : NewsPublisher<Action, Effect, State, News> {
override fun invoke(action: Action, effect: Effect, state: State): News? = when (effect) {
is FailedLoading -> News.ErrorExecutingRequest(effect.throwable)
else -> null
}
}
}
// ---------------------------------------------
object ClientCode {
class NewsListener(private val context: Context) : Consumer<News> {
override fun accept(news: News) {
when (news) {
is News.ErrorExecutingRequest -> {
Toast.makeText(context, "Lorem ipsum", Toast.LENGTH_SHORT).show()
}
}
}
}
}
class DefaultFeatureWithNews : DefaultFeature<Wish, Action, Effect, State, News>(
initialState = State(),
wishToAction = { Execute(it) },
actor = ActorImpl(),
reducer = ReducerImpl(),
postProcessor = PostProcessorImpl(),
newsPublisher = NewsPublisherImpl()
) {
data class State(
val isLoading: Boolean = false,
val payload: Any? = null,
internal val requestCounter: Int = 0
)
sealed class Wish {
object LoadNewData : Wish()
}
sealed class News {
data class ErrorExecutingRequest(val throwable : Throwable) : News()
object CacheInvalidated : News()
}
sealed class Action {
data class Execute(val wish: Wish) : Action()
object InvalidateCache : Action()
}
sealed class Effect {
object StartedLoading : Effect()
data class ReceivedPayload(val payload: Any) : Effect()
data class FailedLoading(val throwable : Throwable) : Effect()
object CacheInvalidated : Effect()
}
class ActorImpl : Actor<State, Action, Effect> {
private val service: Observable<Any> = TODO()
override fun invoke(state: State, action: Action): Observable<Effect> = when (action) {
is Execute -> when (action.wish) {
LoadNewData -> {
service
.observeOn(AndroidSchedulers.mainThread())
.map { ReceivedPayload(it) as Effect }
.startWith(just(StartedLoading))
.onErrorReturn { FailedLoading(it) }
}
}
InvalidateCache -> {
Observable
.fromCallable { /* do the thing */ }
.map { CacheInvalidated }
}
}
}
class ReducerImpl : Reducer<State, Effect> {
override fun invoke(state: State, effect: Effect): State = when (effect) {
StartedLoading -> state.copy(
isLoading = true
)
is ReceivedPayload -> state.copy(
isLoading = false,
payload = effect.payload
)
is FailedLoading -> state.copy(
isLoading = false
)
is CacheInvalidated -> state.copy(
requestCounter = 0
)
}
}
class PostProcessorImpl : PostProcessor<Action, Effect, State> {
override fun invoke(action: Action, effect: Effect, state: State): Action? =
when {
(state.requestCounter >= 3) -> InvalidateCache
else -> null
}
}
class NewsPublisherImpl : NewsPublisher<Action, Effect, State, News> {
override fun invoke(action: Action, effect: Effect, state: State): News? = when (effect) {
is FailedLoading -> News.ErrorExecutingRequest(effect.throwable)
is CacheInvalidated -> News.CacheInvalidated
else -> null
}
}
}
// Feature dependencies are injected, but if you really want, you can do the wiring somewhere else
class ReactingToNewsInFeature1(
feature1: ReducerFeatureWithNews,
feature2: ActorReducerFeatureWithNews
) : ReducerFeature<Wish, State, Nothing>(
initialState = State(),
reducer = ReducerImpl(),
bootstrapper = BootStrapperImpl(feature1, feature2)
) {
data class State(
val counter: Int = 0
)
sealed class Wish {
object IncreaseCounter : Wish()
}
class BootStrapperImpl(
private val feature1: ReducerFeatureWithNews,
private val feature2: ActorReducerFeatureWithNews
) : Bootstrapper<Wish> {
// Again, no need to exhaust all possible News, just the ones we're interested in
override fun invoke(): Observable<Wish> = Observable.merge(
Observable.wrap(feature1.news).mapNotNull {
when (it) {
LoremIpsum -> IncreaseCounter
else -> null
}
},
Observable.wrap(feature2.news).mapNotNull {
when (it) {
is ErrorExecutingRequest -> IncreaseCounter
else -> null
}
}
)
// Another simpler approach, if it's mapped to the same one wish:
fun invoke2(): Observable<Wish> = Observable.merge(
Observable.wrap(feature1.news).filter { it is LoremIpsum },
Observable.wrap(feature2.news).filter { it is ErrorExecutingRequest }
).map { IncreaseCounter }
}
class ReducerImpl : Reducer<State, Wish> {
override fun invoke(state: State, effect: Wish): State = when (effect) {
IncreaseCounter -> state.copy(
counter = state.counter + 1
)
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment