evant / redux

Redux ported to java/android (name tbd)

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

redux for java/android (name tbd)

Redux ported to java/android

I've seen a few of these floating around, but this one has some specific benefits over other implementations.

  • Any object can be used as an action or state.
  • Built-in functions to help compose reducers
  • Middleware that's actually implemented like you'd expect.
  • Thunk and rxjava dispatchers.
  • A fully-fleshed-out android sample.

Download

Maven Central Sonatype Snapshot

repositories {
  mavenCentral()
}

dependencies {
  compile "me.tatarka.redux:redux-core:0.11"
  compile "me.tatarka.redux:redux-android:0.11"
  compile "me.tatarka.redux:redux-android-lifecycle:0.11"
  compile "me.tatarka.redux:redux-thunk:0.11"
  compile "me.tatarka.redux:redux-rx:0.11"
  compile "me.tatarka.redux:redux-rx2:0.11"
}

Usage

Create a store.

SimpleStore<State> store = new SimpleStore(initialState);

Get the current state.

State state = store.state();

Listen to state changes.

store.addListener(new Listener<State>() {
  @Override
  public void onNewState(State state) {
    ...
  }
});

Or with rxjava (using redux-rx).

ObservableAdapter.observable(store).subscribe(state -> { ... });

Or with rxjava2 (using redux-rx2).

FlowableAdapter.flowable(store).subscribe(state -> { ... });

Create a dispatcher with optional middleware.

Dispatcher<Action, Action> dispatcher = Dispatcher.forStore(store, reducer)
    .chain(middleware...);

Dispatch actions.

dispatcher.dispatch(new MyAction());

Android

You can observe your store with LiveData which will properly tie into the android lifecycle.

LiveDataAdapter.liveData(store).observe(this, state -> { ... });

You can use StoreViewModel to keep your store around for the lifetime of an activity/fragment surviving configuration changes.

public class MyViewModel extends StoreViewModel<State, MyViewModel> {
  public MyViewModel() {
    super(new MyStore());
  }
}
public class MyActivity extends LifecycleActivity {
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    MyViewModel viewModel = ViewModelProviders.of(this).get(MyViewModel.class);
    MyStore store = viewModel.getStore();
    viewModel.getState().observe(this, state -> { ... });
  }
}

Since LiveData relays state changes to the main thread, you may lose important stack trace info. You can get it back by calling LiveDataAdapter.liveData(store, true) or LiveDataAdapter.setDebugAll(true). This creates an expensive stacktrace on every dispatch so you probably don't want it on release. A common pattern would be to put LiveDataAdapter.setDebugAll(BuildConfig.DEBUG) in your setup code.

Composing Reducers

It's common you'd want to switch on actions values or class type. Reducers.matchValue() and Reducers.matchClass() makes this easy.

Reducer<String, State> reducer = Reducers.matchValue()
  .when("action1", new Action1Reducer())
  .when("action2", new Action2Reducer());

Reducer<Object, State> reducer = Reducers.matchClass()
  .when(Action1.class, new Action1Reducer())
  .when(Action2.class, new Action2Reducer());

There is also Reducers.match() which takes a predicate for more complicated matching setups.

Reducer<Object, State> reducer = Reducers.match()
  .when(Predicates.is("action1"), new Action1Reducer())
  .when(Predicates.instanceOf(Action2.class), new Action2Reducer());

You can also run a sequence of reducers with Reducers.all(reducer1, reducer2, ...) or run reducers until one changes the state with Reducers.first(reducer1, reducer2, ...).

Thunk Dispatcher

Allows you to dispatch async functions as actions.

SimpleStore<State> store = new SimpleStore<>(initialState);
ThunkDispatcher<Action, Action> dispatcher = new ThunkDispatcher<>(Dispatcher.forStore(store, reducer));

dispatcher.dispatch(new Thunk<Action, Action>() {
  @Override
  public void run(Dispatcher<Action, Action> dispatcher) {
    dispatcher.dispatch(new StartLoading());
    someAsyncCall(new Runnable() {
      @Override
      public void run() {
        dispatcher.dispatch(new StopLoading());
      }
    }
  }
});

Observable Dispatcher

Alternatively, you can use the ObservableDispatcher to dispatch a stream of actions.

SimpleStore<State> store = new SimpleStore<>(initialState);
ObservableDispatcher<Action> dispatcher = new ObservableDispatcher<>(Dispatcher.forStore(store, reducer));

dispatcher.dispatch(callThatReturnsObservable()
    .map(result -> new StopLoading())
    .startWith(Observable.just(new StartLoading())));

Subclassing a Store

Don't want to have to worry about passing around the store and dispatchers? You can subclass SimpleStore and create your own dispatch methods. This also simplifies generics a bit when using throughout your app.

public class MyStore extends SimpleStore<State> {

  private final Dispatcher<Action, Action> dispatcher;
  private final ObservableDispatcher<Action> observableDispatcher;

  public MyStore() {
    super(new State());
    dispatcher = Dispatcher.forStore(this, new MyReducer())
      .chain(new LogMiddleware<>("ACTION"));
    observableDispatcher = new ObservableDispatcher<>(dispatcher);
  }

  public Action dispatch(Action action) {
    return dispatcher.dispatch(action);
  }

  public Subscription dispatch(Observable<Action> actions) {
    return observableDispatcher.dispatch(actions);
  }

  public Observable<Action> observable() {
    return ObservableAdapter.observable(this);
  }
}

Now you can just pass the single store around and call store.dispatch().

Debug Utilities

Android LogMiddleware

You can log all actions on android with the built-in LogMiddleware.

dispatcher = Dispatcher.forStore(store, reducer)
  .chain(new LogMiddleware<Action, Action>("ACTION"));

ReplayMiddleware

You can disable/enable actions and see how that effects your ui with the replay middleware. It will replay your modified actions back on the initial state.

compile "me.tatarka.redux:redux-replay:0.10"
replay = new ReplayMiddleware<State, Action, Action>(store, reducer);
dispatcher = Dispatcher.forStore(store, reducer)
  .chain(replay);

replay.actions() // lists all actions that have been dispatched
replay.disable(index) // disables action at given index
replay.enable(index) // enables action at the given index

The sample android app includes a debug drawer to let you interact with this middleware.

Redux Debugging tools integration.

You can connect to RemoteDev Server to interact with various redux debugging UI's. Currently only displaying actions/state is supported.

npm install -g remotedev-server
remotedev --hostname=localhost --port=8000
compile "me.tatarka.redux:redux-monitor:0.11"
dispatcher = Dispatcher.forStore(store, reducer)
  .chain(new MonitorMiddleware(store));

About

Redux ported to java/android (name tbd)

License:Apache License 2.0


Languages

Language:Java 100.0%