Cross view communication using Grox
Most real-world Android applications consist of multiple fragments and views. The cross communication, syncing, and management of the application state between components have never been easy. Although callback and an event bus patterns help components communicate with each other, there is not an out of the box solution to manage the application state.
Grox is an open source library that makes it easier to manage the internal state of complex applications. We suggest reading our introductory article about Grox before proceeding. In this article, we will focus on using Grox to enhance cross view communication.
Lets take an example app…
Let’s consider a simple weather app as an example. It will display forecasts for either today, next five days or next ten days. The app contains a horizontal selector to toggle between forecast. We have a RecyclerView inside the main activity to show the weather details. Each view has a single presenter as shown in the diagram below.
and Grox it…
We use Grox inside our app to load weather information. Let’s first take a look at a high-level diagram of the state changes required by the application, and then we’ll jump into the code.
- The presenters attach to the views and subscribe for state change inside the Grox store during activity/fragment
2. Once subscribed, Grox emits the INITIATED state to the presenters.
WeatherMainPresenter creates the command to load weather data.
WeatherRequestCommand will execute the API request, map it to an action and return set of actions to the Grox Store.
5. Firstly, Grox calls the
WeatherRefreshAction to change the state to REFRESH and broadcasts it to the subscribers.
6. Secondly, the
WeatherSuccessAction is called to broadcast the SUCCESS state.
7. The presenters listen to the grox state change to update the views.
Show me the code
We represent our weather data with an immutable Weather Model. The store is responsible for holding the immutable state for our weather app. Typically, the data and view state goes inside the model. Initially, our model is in INITIATED state and contains the empty list of weather data. At any given point in time, the Grox model will be in one of the following states:
Now let’s have a look at some commands and actions.
WeatherRequestCommand fetches data from the network and maps it to appropriate actions. The
forecastMode represents the type of forecast selection. For successful request completion, the command maps the data to
WeatherSuccessAction. The success action updates the weather model to display the weather information. In Grox, all the outputs of a command, whether it’s a success or an error, have to be wrapped into an action. If the call to the API client triggers any error then the command will return
WeatherErrorAction. Below we can see the set of actions returned by the command:
In the snippet below, we provide the code of a typical Grox action: changing the state of the store. The
WeatherSuccessAction changes the state of the model to SUCCESS and updates the request mode and weather data. We can see that the actions update the model, have no side effects, no dependencies and thus they are fully reproducible. This makes the actions 100% unit-testable.
Now, let’s go through the presenters and the activity to see how the Grox command, actions and model are wired up.
detachView() to subscribe to the Grox store and clear subscriptions in the presenters respectively. The
states() to observe the store on the main thread. Further, it filters out the state, creates and dispatches a new request action. The
processRefreshing(), processSuccess() and
processError() observe the store for REFRESHING, SUCCESS and ERROR state to update the UI.
WeatherMainActivity creates the Grox store, wire up the views and presenters. The store is responsible for holding the immutable state for our weather app. We pass the store to the view and presenter in
onStart()and release it in
We see that the horizontal selector and the recycler view are isolated and never communicate with each other directly. They both subscribe to Grox to publish and listen to state changes. Also, there are no concurrent changes to the app state and is fully managed by Grox. Now let us go over some of the advantages of using Grox in our sample.
- Single Responsibility: Grox commands and actions adhere to the single responsibility principle. All the complex functionality is totally encapsulated within the components. For example, in our weather app, we have a specific command to make a network call and actions for each state.
- Separation of Concerns: Each component is distinct and isolated from other components. Component isolation leads to logical isolation, which makes it easier to write robust unit tests. Thus, making the components testable.
- No Concurrent Changes: Grox executes a single action at a time and all actions are atomic. Action executes in its entirety to create and emit a new state. Thus it is very easy to enforce concurrency with Grox.
- Simple Communication: Cross view communication is coordinated using Grox. Any new event should be placed on the Grox pipeline.The state change is communicated across components by observing the Grox store.
- Flexible: Grox is flexible and fits very well with the existing architectural patterns like MVP, MVVM and MVI. Our sample app uses MVP and Grox works very well with it.