freeletics / RxRedux

Redux implementation based on RxJava
https://freeletics.engineering/2018/08/16/rxredux.html
Apache License 2.0
567 stars 34 forks source link

Log Actions that are produced by SideEffects? #34

Open NickvanDyke opened 5 years ago

NickvanDyke commented 5 years ago

Coming to RxRedux from Mobius, so far the only thing I miss is the ease of logging all of its equivalents of States and Actions. Implementing it myself for upstream Actions (e.g. from UI) and any State changes was pretty easy, but I don't know how I would log Action emissions from SideEffects without explicitly including a .doOnNext { Log.d(...) } in every SideEffect or a Log statement in my reducer.

Is this possible, and I'm missing how? If not, could some way for us to access that stream, or a logging method, be added?

Here's what I have right now

abstract class RxRedux<S, A> {
    val TAG: String = this::class.java.simpleName

    open fun init(state: S): Init<S, A> = Init(state, listOf())

    open fun reduce(state: S, action: A): S = state

    open fun sideEffects(): List<SideEffect<S, A>> = listOf()

    data class Init<S, A>(
            val state: S,
            val actions: List<A> = listOf()
    )
}
abstract class RxReduxFragment<S : Parcelable, A : Any> : BaseFragment() {

    private lateinit var sub: Disposable
    private lateinit var latestState: S

    @CallSuper
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        val redux = provideRxRedux(context!!)
        val init = redux.init(savedInstanceState?.getParcelable(STATE) ?: defaultState(context!!))

        sub = uiActions()
                .observeOn(Schedulers.io()) // by default, all redux things (effects, reducing, etc) are run on the io scheduler
                .doOnNext { Log.d(redux.TAG, "UI action: $it") }
                .startWith(init.actions.toObservable().doOnNext { Log.d(redux.TAG, "Init action: $it") })
                // TODO: log actions received from side effects
                .reduxStore(init.state, redux.sideEffects().plus(uiSideEffects()), redux::reduce)
                .distinctUntilChanged()
                .doOnNext { latestState = it }
                .doOnNext { Log.d(redux.TAG, "State: $it") }
                .doOnSubscribe { Log.d(redux.TAG, "Initializing with state: ${init.state}") }
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(this::render)
    }

    abstract fun provideRxRedux(context: Context): RxRedux<S, A>

    abstract fun defaultState(context: Context): S

    abstract fun render(state: S)

    abstract fun uiActions(): Observable<A>

    open fun uiSideEffects() = listOf<SideEffect<S, A>>()

    @CallSuper
    override fun onDestroyView() {
        super.onDestroyView()
        sub.dispose()
    }

    @CallSuper
    override fun onSaveInstanceState(outState: Bundle) {
        super.onSaveInstanceState(outState)
        outState.putParcelable(STATE, latestState)
    }

    companion object {
        private const val STATE = "RX_REDUX_STATE"
    }
}

Thanks

Tapchicoma commented 5 years ago

I think, in general, RxRedux needs some build-in logging solution that will allow to use different loggers implementations (like Android Log or Timber or something else).