RxRedux

Zeyad Gasser
5 min readJul 20, 2017

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.

public class UserListState implements Parcelable {
List<User> users;
... private UserListState(Builder builder) {
users = builder.users;
}

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

...
static class Builder {
...
}
}

Getting Started

Using RxRedux is very simple. Just 3 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:2.1.2'
}

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 stateReducer<S> and an eventsToActionsMapper, explanation coming shortly.

PS. BaseViewModel extends ViewModel from Android Architecture Components

@Override
public StateReducer<UserListState> stateReducer() {
return (newResult, event, currentStateBundle) -> {
List<User> users;
if (currentStateBundle == null || currentStateBundle.getUsers() == null)
users = new ArrayList<>();
else users = currentStateBundle.getUsers();
switch (event) {
case "GetPaginatedUsersEvent":
users.addAll((List) newResult);
break;
case "DeleteUsersEvent":
users = Observable.fromIterable(users)
.filter(user -> !((List) newResult).contains(user.getLogin()))
.distinct().toList().blockingGet();
break;
default:
break;
}
return UserListState.builder()
.users(users)
.build();
};
}

The StateReducer 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.

Secondly, your EventsToActionsMapper.

@Override
public Function<BaseEvent, Flowable<?>> mapEventsToActions() {
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

Your Activities or Fragments need to extend BaseActivity<UIState, ViewModel> or BaseFragment<UIState, ViewModel>. These base classes handle life cycle events of the events and uiStates streams and their consumption, handling savedInstanceState. You will need to implement 7 methods.

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

Second method: setupUI(boolean isNew). 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: renderSuccessState(S successState). Given a state, provide an implementation to display that success state.

Seventh method: Observable<BaseEvent> events(). Merge all events from ur view into one observable.

The events stream is an Observable<BaseEvent>. BaseEvent is an 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 renderState(UserListState 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);
}
@Override
public Observable<BaseEvent> events() {
return Observable.merge(otherEvents, initialEvent);
}
@Override
public ErrorMessageFactory errorMessageFactory() {
return Throwable::getLocalizedMessage;
}
@Override
public void initialize() {
viewModel = ViewModelProviders.of(this,
new ViewModelFactory(DataServiceFactory.getInstance()))
.get(UserListVM.class);
}

@Override
public void setupUI(boolean isNew) {
setContentView(R.layout.activity_user_list);
ButterKnife.bind(this);
...
}

And your done. So lets recap.

Recap

There are 5 components for using the RxRedux lib:

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

BaseViewModels have your functions that get data from different sources and maps events to these functions. Needs to implement stateReducer and mapEventsToActions.

public class UserListVM extends BaseViewModel<UserListState> {

...

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

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

BaseActivity/Fragment provide lifecycle handling of the exposed ViewModel observable, single responsibility methods to handle different UIStates (Loading, Error, Success).

@Override
public void renderState(UserListState 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);
}
@Override
public Observable<BaseEvent> events() {
return Observable.merge(otherEvents, initialEvent);
}
@Override
public ErrorMessageFactory errorMessageFactory() {
return Throwable::getLocalizedMessage;
}

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 implement Parcelable.
  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 StateReducer 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.

--

--