Native Android, Redux, Firebase Auth & Database, and Material Design

Posted by

Introduction

Redux and React are things that are normally associated with web development. Redux is a beautiful design pattern that can help with any application, even native ones! This is the approach that I took to integrate the Redux pattern into native Android development, and leverage the stability and predictability of a finite state machine to native development.

Further, I used Firebase in order to do cross platform data synchronization across web and mobile clients. Firebase has the benefit of providing offline mode for web and native mobile client apps, which is incredibly powerful.

And I decided to use Firebase authentication for web, and mobile as well, which provides a consolidated way to do cross platform anonymous authentication and signed in authentication using a variety of social sign in providers (Google, Facebook, etc.)

Here’s a video of the app in action and here’s a link to the code that produced this app on GitHub. It’s a pretty sophisticated app, so please pay attention to the video and look thru the code on GitHub. You will find things like:

  • Fast boot time by persisting the redux state to local storage.
  • The ability to have anonymous auth (and save the state of the app), then switch to a signed in state and then retain the data that was created in the anonymous auth state of the app.
  • Proper handling of sign-in and anonymous authentication, and not burdening the user with Login, SignUp, and SignIn. Just replace all that with a single button press that takes the app from anonymous-auth mode to signed-in mode.
  • Using Redux middleware (and not just reducers) to play nice with Firebase updates, and solve a lot of murky synchronization issues that would otherwise present themselves.
  • The app also has a sophisticated debug mechanism that exposes the history of redux state transitions and even the ability to diff between 2 states in the Android app itself. You can see the app going thru it’s state changes from startup until present moment. These are incredibly valuable insights that you have on the lifecycle of your application that you leverage to build some really compelling apps.

There’s a web app that works hand in hand with this mobile app. Here are some links for the web app:

Screen Shot 2017-01-26 at 5.42.19 PM.png
This is a screenshot of the web app in action

This is also incredibly powerful because it showcases how the ideas that are used in the Android native app are very similar to those implemented in the web app, such as:

  • Reducers and middleware are very similar on both native Android and web.
  • Firebase auth is used in both cases, so that sign-in and anonymous auth work pretty much the same.
  • Firebase database is used in a similar way (in addition to the use of middleware) that makes user data synchronization between the web and mobile apps seamless, and offline!

Architecture

For more information on how Redux is used with the native Android app and how it syncs with the web app, check out the wiki page on GitHub.

Redux Middleware and Firebase

The Redux library I’m using for this project is the Bansa library on GitHub. You can add it to your build.gradle and get started very quickly.

// First, add JitPack to your repositories
repositories {
...
maven { url "https://jitpack.io" }
}

// Base package. Everything else is built upon it!
compile 'com.github.brianegan.bansa:bansa:1.0.0-beta'

Here are some links that contain some important details about this Redux Middleware and Async Actions pattern (which is used in the native Android and web app):

The way to get Redux and Firebase to work together is to use Middleware that handles some actions, and have the Reducer functions handle some other actions (instead of doing it all in the reducers). Now, regardless of whether these actions are handled by the Middleware or Reducers, they are dispatched to the Redux Store.

The Redux store contains the state of the application, which stores:

  1. the currently logged in user data, and
  2. the todolist data for this user.

Firebase database and auth

Both of these things are backed to Firebase. This is what the Firebase database looks like:

What happens in the reducer and what happens in the middleware and how that relates to Firebase

The following diagrams shows how the various actions are processed by Middleware, or Reducers, and how that interaction works with Firebase:

68747470733a2f2f6c68332e676f6f676c6575736572636f6e74656e742e636f6d2f366d47463662644d7a7273394d646b78636978734b6b72356142734e413666706e4864437a694f5a306c4e6642796573366d445259683938325971424e374d7576726735637738465974733d77323139362d68313536322d6e6f.jpg

Actions that are processed by Redux

The Middleware handles the following actions:

  1. AddTodoItemAction (this writes to Firebase)
  2. ToggleTodoItemAction (this writes to Firebase)
@Override
public void dispatch(Store<State> store, Action actionParam, NextDispatcher next) {

  try {

    State newState = store.getState().deepCopy();

    if (actionParam instanceof Actions.AddTodoItem) {

      if (newState.data == null) newState.data = new Data();
      Actions.AddTodoItem action = (Actions.AddTodoItem) actionParam;
      newState.data.todoArray.add(action.getParam());
      _ctx.getDatabase().saveUserDataToFirebase(newState.data);

    } else if (actionParam instanceof Actions.ToggleTodoItem) {

      Actions.ToggleTodoItem action = (Actions.ToggleTodoItem) actionParam;
      int index = action.getParam();
      newState.data.todoArray.get(index).done = !newState.data.todoArray.get(index).done;
      _ctx.getDatabase().saveUserDataToFirebase(newState.data);

    }

  } catch (Exception e) {
    App.logErr("StateMiddleware", "problem with dispatch()", e);
  }

  next.dispatch(actionParam);

}

The Reducer handles the following actions:

  1. ResetStateAction (no connection to Firebase)
  2. RestoreStateAction (no connection to Firebase)
  3. SetUserAction (these are read from Firebase)
  4. SetDataAction (these are read from Firebase)
@Override
public State reduce(State state, Action actionParam) {

  try {

    State newState = state.deepCopy();

    if (actionParam instanceof Actions.SetUser) {

      Actions.SetUser action = (Actions.SetUser) actionParam;
      newState.user = action.getParam();

    } else if (actionParam instanceof Actions.SetData) {

      Actions.SetData action = (Actions.SetData) actionParam;
      newState.data = action.getParam();

    } else if (actionParam instanceof Actions.RestoreState) {

      Actions.RestoreState action = (Actions.RestoreState) actionParam;
      newState = action.getParam();

    } else if (actionParam instanceof Actions.ResetState) {

      Actions.ResetState action = (Actions.ResetState) actionParam;
      newState = new State();

    }

    ctx.getReduxLog().push(ctx.getTime(), state, actionParam, newState);

    App.log("Reducer", "applying action: " + actionParam.getClass().getSimpleName());

    return newState;

  } catch (Exception e) {
    App.logErr("Reducer", "problem running reduce()", e);
  }
  return state;
}