evant / redux

Redux ported to java/android (name tbd)
Apache License 2.0
191 stars 24 forks source link

Dispatcher should be send as parameter for the middlewares #8

Open guitcastro opened 6 years ago

guitcastro commented 6 years ago

ReduxJs define middleware apis as:

      var middlewareAPI = {
        getState: store.getState,
        dispatch: (action) => dispatch(action)
      };

Currently today we only receive the next Middleware and Action as parameter, so if we want to dispatch an action to go though all middleware again we need to explicit pass the dispatch as parameter to the Middleware, but this generate some downside as such:

We need to know the Dispatcher when creating the middleware, in the examples you can do this creating the middleware inside the store, but this generate a strong couple where all middleware must be initialized inside the store, make testing hard and etcs.

My propose is to change the middleware interface to receive the dispatcher as parameter:

 public interface Middleware<A, R> {
     R dispatch(Dispatcher, dispatcher, Next<A, R> next, A action);
 }
guitcastro commented 6 years ago

Just to lustrate the problem, today I have the follow store class:

class RootStore<S : Any>(initialValue: S,
                         reducer: Reducer<Action, S>,
                         vararg middlewares: AppMiddleware) : SimpleStore<S>(initialValue), Store<S> {
    private var dispatcher = Dispatcher.forStore(this, reducer).chain(middlewares.asIterable())
}

And the follow Middleware that need to access to the dispatcher:

class SessionMiddleware(private val dispatcher: Dispatcher<Action, Action>,
                        private val context: Context,
                        private val sessionManager: SessionManager) : AppMiddleware 

In order to create the middleware before the store, as workaround I create the follow dummy dispatcher:

class StoreDispatcher<S : Any> : Dispatcher<Action, Action>() {

    internal lateinit var dispatcher: Dispatcher<Action, Action>

    override fun dispatch(action: Action): Action {
        return this.dispatcher.dispatch(action)
    }
}

And changed the code of my store implementation to the following:

class RootStore<S : Any>(storeDispatcher: StoreDispatcher<S>,
                         initialValue: S,
                         reducer: Reducer<Action, S>,
                         vararg middlewares: AppMiddleware) : SimpleStore<S>(initialValue), Store<S> {

    constructor(initialValue: S, reducer: Reducer<Action, S>, vararg middlewares: AppMiddleware)
            : this(StoreDispatcher<S>(), initialValue, reducer, *middlewares)

    private var dispatcher: Dispatcher<Action, Action> = storeDispatcher.apply {
        dispatcher = Dispatcher.forStore(this@RootStore, reducer)
                .chain(middlewares.asIterable())
    }

This way I can pass the StoreDispatcher to the middleware and the same instance to the store which works. But it's a very hacky workaround.