Implement your own Redux in Java. Part 1: Basic Redux functionality

javaredux

To learn and understand new tech things I'm often implementing them on my own. I mean, How hard can it be? This time I'd like to show how to implement Redux with Java targeted towards JavaFX. The purpose here is not to actually use this implementation because there is already a really good one available. The purpose is only to see that Redux actually isn't that complex when it comes to a basic implementation of the library. As this topic includes a lot of sub-topics I will start a little series of blog posts that will cover the implementation of a Redux library in Java, the extension mechanisms of Redux and how to use them with JavaFX.

Topics in this blog series (maybe I will adjust this in the process of writing):

The first thing when implementing a library is to think about how a user would like to use it. How do you like your app code to look like?

First let's create some actions. I'm using the classic Todo app as an example here so we need an "AddItemAction":

public class AddItemAction implements Action {
  private final String value;

  public AddItemAction(String value) {
    this.value = value;
  }

  public String getValue() {
    return value;
  }

  // equals & hashcode
}

To give the class a little more context and to make the implementation of reducers easier I've also created a marker interface Action here that doesn't contain any methods.

The next step is to create a class for the State. This could look like this:

public class TodoState {

  private final List<String> items;

  public TodoState(List<String> items) {
    this.items = items;
  }

  public List<String> getItems() {
    return items;
  }

  public TodoState withItems(List<String> items) {
    if(items.equals(this.items)) {
      return this;
    } else {
      return new TodoState(items);
    }
  }
}

Here I'm implementing the state as an immutable class. This is important for Redux: You can't change the fields of the state object. Instead you have to create a copy that has your desired changes applied. In this example I'm implementing the immutability by hand. Of cource this won't scale for bigger applications. So for real-world applications I would suggest you to use a library like Immutables.org that creates immutable classes for you.

The next step for a Redux application is the Reducer. This can also be implemented with pure Java code like this:

public class TodoReducer {

  public TodoState reduce(TodoState oldState, Action action) {
    if(action instanceof AddItemAction) {
      AddItemAction addItemAction = (AddItemAction) action;

      List<String> oldItems = oldState.getItems();

      List<String> newItems = new ArrayList<>(oldItems);
      newItems.add(addItemAction.getValue());

      return oldState.withItems(newItems);
    }

    return oldState;
  }
}

The implementation is pretty straight forward. If the incoming action is of type AddItemAction we handle it by creating a new item list containing the old items together with the new one. Then we create an immutable copy with the new items by invoking withItems on the old state instance. If the action doesn't match the desired type we ignore it and simply return the existing state.

This is the basic redux code for our application. What is missing is the UI component to visualize the app but we will come to this later. Now we can start implementing our Redux library. The first step is to introduce an interface for Reducers. This should look like this:

public interface Reducer<S> {
  S reduce(S oldState, Action action);
}

The generic type variable S is used for the actual type of the State. Now our example reducer can implement the new interface:

public class TodoReducer implements Reducer<TodoState> {

  @Override
  public TodoState reduce(TodoState oldState, Action action) {
    // ...
  }
}

The Redux store

Now we can implement the actual Redux store. The task of the Store is to hold an instance of the current State and to exchange the state every time an action is dispatched. So the basic interface for the Store could look like this:

public class Store<S> {
  private S currentState;
  private Reducer<S> reducer;

  public Store(S initialState, Reducer<S> rootReducer) {
    this.currentState = initialState;
    this.reducer = rootReducer;
  }

  public S getState() {
    return this.currentState;
  }

  public void dispatch(Object action) {
    // todo
  }
}

To create a Store instance we need to provide an initial State and the Reducer. Then you can request the current State with getState and dispatch an Action with dispatch.

Now let's implement the dispatch method. Notice that I've used the type Object for the action param instead of our Action interface. At first this might look strange but it enables us to create powerful extension mechanisms later on. These extensions will make it possible to not only dispatch simple action objects but also other things like function definitions or CompleteableFutures. This way it is possible to introduce different strategies to handle asynchronous behavior that otherwise wouldn't be possible. Of course at this stage dispatching something other than a simple action object would crash the app with a ClassCastException. In one of the next blog posts I will introduce the concept of "middlewares" and then we will fix this issue.

public void dispatch(Object action) {
  this.currentState = reducer.reduce(this.currentState, (Action) action);
}

Subscribe to changes

We are now able to dispatch actions and have the reducer creating new states for us. However, this implementation isn't really useful yet. What is missing is a way to get notified about state-changes so that you can re-render the UI. For this I'm introducing two interfaces:

public interface Subscription {
  void unsubscribe();
}

public interface Subscriber<S> {
  void onChange(S state);
}

Subscriber is just a regular observer. It's onChange method will be invoked when the State in the store has been changed. When subscribing to the Store you pass a Subscriber as argument and you will get a Subscription as a result. You can ignore this Subscription or use it later to unsubscribe from the Store. This is a nice convenience feature but if you like you can also just implement a classic observer pattern. Subscribing to the Store looks like this:

Subscription sub = store.subscribe(state -> {
  // do some updates based on the new state
})


// later
sub.unsubscribe();

Now the implementation of the Store get's a little more interesting. First we add a private List<Subscriber<S>> subscribers = new ArrayList<>(); list to the store. Then we add the subscribe method:

public Subscription subscribe(Subscriber<S> subscriber) {
  subscribers.add(subscriber);

  subscriber.onChange(this.currentState);

  return () -> {
    subscribers.remove(subscriber);
  };
}

The subscriber is added to the subscriber list and it's onChange method is invoked with the current state. This way the subscribing code can properly initialize itself. Then we create a lambda for the Subscription which, when invoked, will remove the subscriber from the list.

The next step is to update the dispatch method. We need to notify all subscribers when an action is dispatched. Additionally I'm adding a check to see if the new state was really changed. A friendly reducer returns the existing state untouched if it doesn't know the incoming Action or it don't want to react to the action. Therefore it's likely that the resulting state instance is the same as the existing one. In this situation we don't want to notify the subscribers to minimize unnecessary re-renderings of the UI. The implementation now looks like this:

private void notifySubscribers() {
  subscribers.forEach(subscriber -> subscriber.onChange(this.currentState));
}

public void dispatch(Object action) {
  S oldState = this.currentState;

  S newState = reducer.reduce(this.currentState, (Action) action);

  if (oldState != newState && !oldState.equals(newState)) {
    this.currentState = newState;

    notifySubscribers();
  }
}

And that's it. This is a basic but fully functional Redux implementation. In comparison with the original redux.js library it misses some extension APIs like Middlewares and Store-Enhancers but we will work on this in a separate blog post. What is missing now to be useful is the UI component and the connection between the UI and the Redux Store. This will be the topic for the next part of this series.


You can find the code for redux example in this github repository. This commit is pointing directly to the state of this blog post.