RxRedux

A library that manages state for your Activities, Fragments & views easily. Find it here on Github.

Problem

Writing Android apps is a challenging task. Mobile apps being reactive by nature, in big projects, using MVP or MVVM is not enough. Having multiple sources of data can end us up in weird states that are hard to trace and reproduce.

Solution

State Management is a nice elegant solution. It requires a small change in approaching problems and solving them. Design Patterns that achieve this have been called MVI, Redux and Cycle but they are all basically the same principal. Welcome RxRedux, a library that applies this pattern with minimal boiler plate. Jake Wharton and Hannes Dorfmann have talked and written about the pattern and its benefits.

RxRedux

RxRedux builds upon the MVVM pattern. View Models in MVVM expose observables to your views, in which they subscribe or unsubscribe depending on the life cycle state. Every observable emits data from a different source and our view has to react to these changes accordingly. Also some of these observables are triggered by user inputs. To achieve state management, we group all of those observables into 1 observable that returns a UIState that the view just needs to display. As well as merging all our events into 1 observable. So ViewModels observe events (User input, LifeCycle, etc) and expose an observable that emits UI States.

UIState are data classes that hold data about your view. Like a list of items to be shown in a RecyclerView, ids of items selected, etc. Its good practice to make this objects Immutable and Parcelable.

@Parcel
public class UserListState {
List<User> users;

UserListState() {
users = new ArrayList<>();
}

private UserListState(Builder builder) {
users = builder.users;
lastId = builder.lastId;
}

static Builder builder() {
return new Builder();
}

List<User> getUsers() {
return users;
}

static class Builder {
List<User> users;

Builder() {
}

Builder users(List<User> value) {
users = value;
return this;
}

UserListState build() {
return new UserListState(this);
}
}
}

Getting Started

Using RxRedux is very simple. Just 5 steps :)

Step 1

Project root build.gradle

allprojects {
repositories {
maven { url 'https://jitpack.io' }
maven { url 'https://maven.google.com' }
}
}

Module build.gradle

dependencies {
compile 'com.github.Zeyad-37:RxRedux:1.4.1'
}

Step 2

All your ViewModels will need to extend BaseViewModel<S>. S is your UIState as shown earlier. There are two abstract methods that you will need to implement. First, an init method to initialize your ViewModel, by passing your initial state S and your SuccessStateAccumulator<S>, explanation coming shortly.

PS. BaseViewModel extends ViewModel from Android Architecture Components

@Override
public void init(SuccessStateAccumulator<UserListState> successStateAccumulator, UserListState initialState,
Object... otherDependencies) {
dataUseCase = (IDataService) otherDependencies[0];
setSuccessStateAccumulator(successStateAccumulator);
setInitialState(initialState);
}

Secondly, your EventsToExecutablesMapper.

@Override
public Function<BaseEvent, Flowable<?>> mapEventsToExecutables() {
return event -> {
Flowable executable = Flowable.empty();
if (event instanceof GetPaginatedUsersEvent) {
executable = getUsers(((GetPaginatedUsersEvent) event).getLastId());
} else if (event instanceof DeleteUsersEvent) {
executable = deleteCollection(((DeleteUsersEvent) event).getSelectedItemsIds());
}
return executable;
};
}

This is a simple mapping function that links everyEvent with its corresponding executable function. The rest of the class holds your executables which are methods that return observables.

Step 3

The SuccessStateAccumulator is an interface that you implement that handles how your view should transition from one success state to the other, given a new result, name of the event that triggered that result and the current UIState

SuccessStateAccumulator accumulator = 
new SuccessStateAccumulator<UserListState>() {
@Override
public UserListState accumulateSuccessStates(Object newResult, String event, UserListState currentStateBundle) {
List<User> resultList = (List) newResult;
List<User> users = currentStateBundle == null ? new ArrayList<>() : currentStateBundle.getUsers();
List<User> searchList = new ArrayList<>();
switch (event) {
case "GetPaginatedUsersEvent":
users.addAll(resultList);
break;
case "DeleteUsersEvent":
users = Observable.fromIterable(users)
.filter(user -> !resultList.contains((long) user.getId()))
.distinct()
.toList()
.blockingGet();
break;
}
return UserListState.builder().users(users).build();
}
};

Step 4

Your Activities or Fragments need to extend BaseActivity<UIState, ViewModel> or BaseFragment<UIState, ViewModel>. These base classes handle life cycle events. You will need to implement 6 methods and initialize your Events stream, more on that in a bit.

First method: initialize(). You should insatiate all your dependencies here, including your ViewModels and Events stream.

Second method: setupUI(). Here you setContentView() and all other ui related stuff.

Third method: errorMessageFactory(). Its a method that returns an interface that when given a Throwable it should return a String error message.

Fourth method: showError(String message). Given the error message, provide an implementation to display it on the screen. Could be SnackBars, Toast messages, error dialogs or whatever.

Fifth method: toggleViews(boolean isLoading). Given a boolean value indicating if the current state is a loading state, you should enable/disable buttons, hide/show progress bars and so on.

Sixth method: renderState(S successState). Given a state, provide an implementation to display that success state.

Finally initializing your Event stream.

Step 5: Events Stream

The events stream is an Observable<BaseEvent>. BaseEvent is an empty interface that all your events will need to implement, just for type safety. You initialize your event observable by merging all the events in your view. Like your GetUsersEvent event, DeleteUserEvent, SearchUserEvent, etc. RxBinding2 is a great lib that provides event observables from ui components.

@Override
public void initialize() {
//...
events = Single.<BaseEvent>just(new GetPaginatedUsersEvent(0))
.toObservable();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// ...
events = events.mergeWith(RxSearchView.queryTextChanges(searchView)
.filter(charSequence -> !charSequence.toString().isEmpty())
.map(query -> new SearchUsersEvent(query.toString()))
.throttleLast(100, TimeUnit.MILLISECONDS)
.debounce(200, TimeUnit.MILLISECONDS));
}

As demonstrated in the code above. Your events should collect the needed input and encapsulate it in an object that implements the BaseEvent interface.

And your done. So lets recap

Recap

The components for using the RxRedux lib are:

  1. BaseViewModel
  2. BaseActivity/Fragment
  3. SuccessStateAccumulator
  4. BaseEvent
  5. ErrorMessageFactory.

BaseViewModels have your functions that get data from different sources and maps events to these functions. Needs an initialState and a SuccessStateAccumulator as initializers.

public class UserListVM extends BaseViewModel<UserListState> {

private IDataService dataUseCase;

@Override
public void init(SuccessStateAccumulator<UserListState> successStateAccumulator, UserListState initialState,
Object... otherDependencies) {
dataUseCase = (IDataService) otherDependencies[0];
setSuccessStateAccumulator(successStateAccumulator);
setInitialState(initialState);
}

@Override
public Function<BaseEvent, Flowable<?>> mapEventsToExecutables() {
return event -> {
Flowable executable = Flowable.empty();
if (event instanceof GetPaginatedUsersEvent) {
executable = getUsers(((GetPaginatedUsersEvent) event).getLastId());
} else if (event instanceof DeleteUsersEvent) {
executable = deleteCollection(((DeleteUsersEvent) event).getSelectedItemsIds());
}
return executable;
};
}

public Flowable<List<User>> getUsers(long lastId) {
return dataUseCase.getUsers();
}

public Flowable<List<String>> deleteCollection(List<String> selectedItemsIds) {
return dataUseCase.deleteCollectionByIds();
}
}

BaseActivity/Fragment provide lifecycle handling of the exposed ViewModel observable, single responsibility methods to handle different UIStates (Loading, Error, Success) and an EventObservable to merge all your events.

@Override
public void renderState(UserListState successState) {
viewState = successState;
usersAdapter.setDataList(viewState.getUsers()));
}
@Override
public void toggleViews(boolean isLoading) {
loaderLayout.bringToFront();
loaderLayout.setVisibility(isLoading ? VISIBLE : GONE);
}

@Override
public void showError(String message) {
showErrorSnackBar(message, anyView, LENGTH_LONG);
}

BaseEvent is an interface that your events need to implement.

ErrorMessageFactory is an interface that maps Throwables to error messages

SuccessStateAccumulator accumulates UIStates by providing newResult, triggerEvent and currentUIState.

Conclusion

Applying this pattern, we ensure:

  1. That all our events(inputs) pass through 1 stream, which is a nice way to clarify and organize what are the possible actions allowed on the view.
  2. Single source of truth to the current UIState, which is automatically persisted in instanceState, needs to be annotated with @Parcel from Parceler.
  3. Error handling is an ease since we can map Throwables to messages and display them as we see fit.
  4. Loading States are also an ease, through the toggle(boolean isLoading) callback that signals whenever the load state starts or ends.
  5. Transition between success states is more clear through the SuccessStateAccumulator and the renderSuccessState() call back
  6. We crash the app if something outside the states and events we have declared causes an unexpected behavior.

Thanks for reading, if you liked it please give it a heart and share it with your friends :)

Also your feedback is welcome, either here in the comments or issues on the repo.