Once upon a time, you started a new app and everything was simple and nice: a few features, a simple UI and that’s it. But then it became bigger and bigger, and the logic became more complex, more entangled. Suddenly, you have a database, you have multiple network calls, several components talking to each others on different threads, callbacks everywhere and multiple user interactions.
The state of your app is modified from everywhere and at any time. At this point you can’t even clearly say in which state your app is after a user interacts with a few elements in the UI.
Let’s consider a typical Android App
Our apps do things like interacting with a server, with the local disk storage, and the UI. All these interactions can change the state of our app and can happen on different threads. How can we easily coordinate all this without losing our clear state?
Let’s see how the above diagram would change with Grox.
Wow… That’s a lot of new stuff!
Let’s walk through all the elements in the diagram.
Grox comes with 3 different modules.
The “Grox-Core” module defines the Action and Store classes.
The Store is the component that contains the state of your app. It is the unique source of truth about its state. The Store keeps state, dispatches actions and notifies its listeners of state changes. This part of the library is synchronized so only one action can change the state of the store at a time.
There is a second variant of the Grox-Core module, called “Grox-Core-Rx“ which uses Observable instead of a list of listeners.
Actions change the state inside the store, and in turn the store will emit the new state. Actions are pure functions, 100% reproducible and testable, their only purpose is to create a new state from the old one. The above diagram exemplifies how the store and actions work together. Actions are generated by commands.
Commands produce a stream of Actions. They are part of the Grox-Commands module and are based on RxJava. Commands return a stream of actions represented by an Rx
Observable<Action>. Every possible output of the command is mapped to an Action. Commands are used in Grox to interact with non-pure logic, not 100% reproducible parts of your app such as a network call, an interaction with the file system, etc. Events are generated by user interactions or by system evens like push notification or location change. They are mapped to commands that get triggered when an event occurs.
Luke… use the Grox…
How to Grox
Let’s use the concepts described above to create a login screen.
The sequence of events would be something like this:
Let’s see what happens when the user presses the login button:
- A click event is generated.
- A command will be started that will make a HTTP call to our server. This command knows the sequence of steps to be executed for the “Login use case”
- The first action the command will emit will create a new state that will represent the “login in progress state”.
- Once the HTTP call is completed, the command will emit a new Action that will change the state from “login in progress state” to “login finished successful” state.
The app will receive the new state from the store by subscribing to a stream of states. These states will be used to change the UI of your app. This way the UI becomes a passive representation of the state.
Enough talking, show me code!
Grox works wonderfully with RxJava so we will also use it in our example. Grox does not impose the use of RxJava, we can use the Store and Actions without it but we would need RxJava if we want to use commands.
First we define our login states.
Now let’s create a model class for the state. Grox does not enforce immutability but we we highly recommend it. A state change should be an atomic operation, like immutable references swaps. In this example we use Autovalue to achieve this, but it’s up to you how you do it.
Next, we build the initial state of the store. In the initial state we have no logged in user and the login request is not started.
Let’s see how the Login command looks like:
In the above example we assume that we have a
LoginApiClient class that performs the actual network request and throws an error if the request fails or returns an error code different than 200. We perform the request in a separate class and we pass it as a parameter so we are able to test this command by mocking the
loginApiClient object. The
LoginApiClient returns the logged in user if the request is successful
All the outputs of a command must be actions, even in the case of errors. At the network layer, errors do occur but in upper layers, they will represent a state of the app. In case of an error, a new “error state” is created and the UI can display it accordingly.
Using the RxChain, we map the successful result of the request to a
LoginSuccessfulAction and in case of an error to a
LoginFailedAction. We also start with a
LoginInProgressAction so, we change the state to “in progress” which will make the UI display a progress indicator while the actual network request will be performed.
LoginSuccessfulAction and the
LoginInProgressAction are very similar, their responsibility is to create a new state using the old one.
LoginSuccessfulAction saves the logged in user in the store so that the UI can display the user details.
LoginInProgressAction works exactly in the same way. It just sets the
LOGIN_IN_PROGRESS. Pretty straightforward, right?
LoginFailedAction should also look familiar by now.
As you can see in the example, actions are pure functions and are easy to test. They are 100% reproducible (so, no network calls, no disk access, etc. as they can fail). The non-reproducible part of the job of our app goes into commands.
Wiring everything together
Ok. We have built all our components. Every component is encapsulated, testable, and has a clear responsibility. The next thing we need to do is to wire everything together. This can be done inside the View or inside the Presenter, it depends on what kind of design you have inside the app. For simplicity in our example we wire everything inside the
Let’s walk through what is happening here. First we subscribe for store updates.
states is a static method in Grox library that receives the store as parameter and returns a stream of states. We observe these states on the main thread and we update the UI once a new state is emitted.
Next, for every click event, we create a
LoginCommand and we “launch” it by calling the
actions() method. This method will return a stream of Actions that will be dispatched to the store. If we use RxBindings the code becomes even more elegant:
Now you can clearly see that the click event is mapped to a
LoginCommand. That’s it! Pretty simple, right? We can now apply this design for every event and every operation in your app.
Once you start to Grox, you never go back
Grox your Android app!
Grox provides a clean solution for managing the state of an app. The main benefits of using the Grox design are:
- Clear Concepts: Every entity described above has a well defined role. Grox provides a clean architecture, with a clear separation of concerns between state, store, commands, events.
- Unified Process: Using Grox design you can actually represent everything that can change the state of your app or of a certain screen. All parts of an app, all screens, can use Grox to unify their handling of state. You can represent user interaction and you can represent operations. These operations can have different purposes like accessing the file system, making network request or even performing some computations.
- Scalable: You can add as many operations as you want and your app state will still be clear at any time. The same design easily scales to all events and for every operation that changes your app’s state.
- Testable: All the components that you create using Grox can be tested.
Cool introduction video: