Reactive applications with Model-View-Intent. Part 2: View and Intent
- Transfer
In the first part, we discussed what a model is, its relationship with the state, and how a properly designed model helps solve some problems in Android development. In this article, we will continue our way to the creation of reactive applications using pattern M odel- the V iew- I of ntent.
Before starting, we briefly discuss the basic idea of MVI.
This pattern has been described Shtalttsem Andre (André Staltz) for JavaScript-framework cycle.js . From a theoretical and mathematical point of view, MVI can be described as follows:

Let's get back to our task. We want to create a reactive application. But is MVI reactive? What does reactivity really mean in this context?
By reactivity we mean an application with a UI that responds to a state change. Since the state reflects the model, it is necessary that our business logic react to events (intentions), and at the output create a model that could be displayed in View by calling the render (model) method.
We need the data stream to be unidirectional. This is where RxJava comes into play. When creating reactive applications with a unidirectional data stream, it is not necessary to use this particular library. However, RxJava is well suited for event programming. And since the UI is event-based, it makes sense to use it.
In this article, I will describe the creation of a simple application for a fictional online store. In the application, you can search for products and add them to the basket.
The finished application will look like this:
Source code can be found on GitHub .
Let's start by implementing the search screen. First of all, I define a model that will be displayed using View, as described in the first part of this series of articles. I will write all model classes with the suffix ViewState , since the model reflects the state.
Java is a strongly typed language, so I chose a type-safe approach to creating a model, dividing each substate inside the class. Business logic will return an object of type SearchViewState , which can be an instance of SearchViewState.Error, etc. This is my personal preference, you can design the model in your own way.
Focus on business logic. Create a SearchInteractor that will be responsible for the search. The result of the execution will be a SearchViewState object.
Let's look at the signature of the SearchInteractor.search () method : there is an input parameter searchString and an output parameter Observable . This suggests that on an observed stream we expect an arbitrary number of SearchViewState instances. The startWith () method is needed to limit SearchViewState.Loading before starting a search query. Then View will be able to show progressBar while the search is in progress.
The onErrorReturn () method catches any exceptions that may occur during the search and throws SearchViewState.Error . We cannot just use the onError () callback when subscribing to Observable. This is a common misconception in RxJava: the onError () callback should be used when the entire observable stream encounters fatal errors and the entire stream terminates.
In our case, the error of the lack of an Internet connection does not fall under the definition of fatal errors - this is just one of the conditions of our model. In addition, we will be able to switch to another state - SearchViewState.Loading - after the Internet connection is again available.
In this way, we create an observable stream from the business logic in the View, which emits a new model each time the state changes. We do not need the monitored stream to end when there is an error connecting to the Internet, so such errors are treated as a state. Usually, in MVI, the observed thread never terminates (the onComplete or onError () methods are not called).
To summarize: SearchInteractor provides the observed flux Observable and emits a new SearchViewState every time a state changes.
Consider what the View layer should look like, which the model should display. Earlier, I suggested that View have a render (model) function . In addition, View should provide the ability for other layers to respond to UI events. In MVI, these events are called intents . In our case, there is only one intent: the user searches for a product by entering a search query in the search field. In MVP, it is good practice to create an interface for the View layer; we will do the same for MVI.
In our case, View provides only one intent, but depending on the task, there can be several.
In the first part, we discussed why using a single render () method is a good solution. Before creating a specific implementation of the View layer, let's look at what the final version will look like:
The render (SearchViewState) method should look concise. In searchIntent (), I use the RxBindings library . RxSearchView.queryText () creates an Observable that emits a string every time the user types something into the EditText widget. I use filter () so that the search query starts after entering three or more characters. We do not need the search request to be sent to the server every time the user enters a new character, so I added the debounce () operator.
We know that the input data stream for this screen is the searchIntent () method, and the output data stream is the render () method.
The following video demonstrates how the interaction between these two flows occurs.
The question remains, how to connect intent and business logic? If you carefully watch the video, you will see the flatMap () operator in the middle. This indicates the presence of an additional component, which I did not talk about - Presenter , - responsible for the connection of the layers.
What are MviBasePresenter , intent () and subscribeViewState () methods . This class is part of the Mosby library . It is worth saying a few words about Mosby and how MviBasePresenter works. Let's start with the life cycle: MviBasePresenter does not have it. The bindIntent () method associates the intent from the View with business logic. Typically, flatMap () or switchMap () is used to send intent to business logic. This method is called once when the View joins the Presenter, but is not called after the View joins the Presenter again, for example, after changing the screen orientation.
The question may be asked whether the MviBasePresenter can really survive the change in screen orientation, and if so, how does Mosby ensure that the observed stream does not “leak”? The intent () and subscribeViewState () methods are intended for this .
intent () creates a PublishSubject inside Presenter and uses it as a “gateway” for business logic. PublishSubject subscribes to the View View. An Intent call (O1) actually returns a PublishSubject that is subscribed to O1.
After changing the orientation of the screen, Mosby disconnects the View from the Presenter, but only temporarily unsubscribes the internal PublishSubject from the View and re-subscribes the PublishSubject to the View when the View reconnects to the Presenter.
subscribeViewState () does the same thing in the opposite direction. It creates inside the Presenter BehaviorSubject as a “gateway” from business logic to View. Since this is a BehaviorSubject, we can get an updated model from the business logic even when the View is disconnected from the Presenter. The BehaviorSubject always stores the last value it received and repeats it when the View reconnects to the Presenter.
A simple rule: use the intent () method to wrap any intent. Use subscribeViewState () instead of Observable.subscribe (...).

What about other life cycle events like onPause () or onResume () ? I still believe that the presenter does not need life cycle events . However, if you really think you need them, you can create them as an intent. PauseIntent () will appear in your View , the launch of which initiates the Android life cycle, and not the user's action.
In this part, we talked about the basics of MVI and implemented a simple screen. This example is probably too simple to understand all the benefits of MVI. There is nothing wrong with MVP or MVVM, and I am not saying that MVI is better than other architectural patterns. However, I believe that MVI helps us write more elegant code for complex problems, as we will see in the next part, in which we will talk about state reducer .
Before starting, we briefly discuss the basic idea of MVI.
Model-View-Intent (MVI)
This pattern has been described Shtalttsem Andre (André Staltz) for JavaScript-framework cycle.js . From a theoretical and mathematical point of view, MVI can be described as follows:

- intent () : A function that accepts input from the user (for example, user interface events, such as click events) and translates into what will be passed as an argument to the model () function. It can be a simple line for setting the model value or a more complex data structure, for example, an object.
- model () : A function that uses the output from the intent () function as input to the model. The result of this function is a new model (with a changed state). At the same time, the data must be immutable . In the first part, I gave an example with a counter application: we do not change an existing instance of the model, but create a new model according to the changes described in the intent. The model () function is just the part of the code responsible for creating a new model object. In fact, the model () function calls the business logic of the application (be it Interactor, UseCase, Repository) and as a result returns a new model object.
- view () : A function that receives a model from model () at the input and simply displays it. Usually the view () function looks like view.render (model) .
Let's get back to our task. We want to create a reactive application. But is MVI reactive? What does reactivity really mean in this context?
By reactivity we mean an application with a UI that responds to a state change. Since the state reflects the model, it is necessary that our business logic react to events (intentions), and at the output create a model that could be displayed in View by calling the render (model) method.
Connect the dots using RxJava
We need the data stream to be unidirectional. This is where RxJava comes into play. When creating reactive applications with a unidirectional data stream, it is not necessary to use this particular library. However, RxJava is well suited for event programming. And since the UI is event-based, it makes sense to use it.
In this article, I will describe the creation of a simple application for a fictional online store. In the application, you can search for products and add them to the basket.
The finished application will look like this:
Source code can be found on GitHub .
Let's start by implementing the search screen. First of all, I define a model that will be displayed using View, as described in the first part of this series of articles. I will write all model classes with the suffix ViewState , since the model reflects the state.
SearchViewState
public interface SearchViewState {
final class SearchNotStartedYet implements SearchViewState {}
final class Loading implements SearchViewState {}
final class EmptyResult implements SearchViewState {
private final String searchQueryText;
public EmptyResult(String searchQueryText) {
this.searchQueryText = searchQueryText;
}
public String getSearchQueryText() {
return searchQueryText;
}
}
final class SearchResult implements SearchViewState {
private final String searchQueryText;
private final List result;
public SearchResult(String searchQueryText, List result) {
this.searchQueryText = searchQueryText;
this.result = result;
}
public String getSearchQueryText() {
return searchQueryText;
}
public List getResult() {
return result;
}
}
final class Error implements SearchViewState {
private final String searchQueryText;
private final Throwable error;
public Error(String searchQueryText, Throwable error) {
this.searchQueryText = searchQueryText;
this.error = error;
}
public String getSearchQueryText() {
return searchQueryText;
}
public Throwable getError() {
return error;
}
}
Java is a strongly typed language, so I chose a type-safe approach to creating a model, dividing each substate inside the class. Business logic will return an object of type SearchViewState , which can be an instance of SearchViewState.Error, etc. This is my personal preference, you can design the model in your own way.
Focus on business logic. Create a SearchInteractor that will be responsible for the search. The result of the execution will be a SearchViewState object.
SearchInteractor
public class SearchInteractor {
final SearchEngine searchEngine;
public Observable search(String searchString) {
if (searchString.isEmpty()) {
return Observable.just(new SearchViewState.SearchNotStartedYet());
}
return searchEngine.searchFor(searchString)
.map(products -> {
if (products.isEmpty()) {
return new SearchViewState.EmptyResult(searchString);
} else {
return new SearchViewState.SearchResult(searchString, products);
}
})
.startWith(new SearchViewState.Loading())
.onErrorReturn(error -> new SearchViewState.Error(searchString, error));
}
}
Let's look at the signature of the SearchInteractor.search () method : there is an input parameter searchString and an output parameter Observable
The onErrorReturn () method catches any exceptions that may occur during the search and throws SearchViewState.Error . We cannot just use the onError () callback when subscribing to Observable. This is a common misconception in RxJava: the onError () callback should be used when the entire observable stream encounters fatal errors and the entire stream terminates.
In our case, the error of the lack of an Internet connection does not fall under the definition of fatal errors - this is just one of the conditions of our model. In addition, we will be able to switch to another state - SearchViewState.Loading - after the Internet connection is again available.
In this way, we create an observable stream from the business logic in the View, which emits a new model each time the state changes. We do not need the monitored stream to end when there is an error connecting to the Internet, so such errors are treated as a state. Usually, in MVI, the observed thread never terminates (the onComplete or onError () methods are not called).
To summarize: SearchInteractor provides the observed flux Observable
Consider what the View layer should look like, which the model should display. Earlier, I suggested that View have a render (model) function . In addition, View should provide the ability for other layers to respond to UI events. In MVI, these events are called intents . In our case, there is only one intent: the user searches for a product by entering a search query in the search field. In MVP, it is good practice to create an interface for the View layer; we will do the same for MVI.
public interface SearchView {
Observable searchIntent();
void render(SearchViewState viewState);
}
In our case, View provides only one intent, but depending on the task, there can be several.
In the first part, we discussed why using a single render () method is a good solution. Before creating a specific implementation of the View layer, let's look at what the final version will look like:
SearchFragment
public class SearchFragment extends Fragment implements SearchView {
@BindView(R.id.searchView) android.widget.SearchView searchView;
@BindView(R.id.container) ViewGroup container;
@BindView(R.id.loadingView) View loadingView;
@BindView(R.id.errorView) TextView errorView;
@BindView(R.id.recyclerView) RecyclerView recyclerView;
@BindView(R.id.emptyView) View emptyView;
private SearchAdapter adapter;
@Override
public Observable searchIntent() {
return RxSearchView.queryTextChanges(searchView)
.filter(queryString -> queryString.length() > 3 || queryString.length() == 0)
.debounce(500, TimeUnit.MILLISECONDS);
}
@Override
public void render(SearchViewState viewState) {
if (viewState instanceof SearchViewState.SearchNotStartedYet) {
renderSearchNotStarted();
} else if (viewState instanceof SearchViewState.Loading) {
renderLoading();
} else if (viewState instanceof SearchViewState.SearchResult) {
renderResult(((SearchViewState.SearchResult) viewState).getResult());
} else if (viewState instanceof SearchViewState.EmptyResult) {
renderEmptyResult();
} else if (viewState instanceof SearchViewState.Error) {
renderError();
} else {
throw new IllegalArgumentException("Don't know how to render viewState " + viewState);
}
}
private void renderResult(List result) {
TransitionManager.beginDelayedTransition(container);
recyclerView.setVisibility(View.VISIBLE);
loadingView.setVisibility(View.GONE);
emptyView.setVisibility(View.GONE);
errorView.setVisibility(View.GONE);
adapter.setProducts(result);
adapter.notifyDataSetChanged();
}
private void renderSearchNotStarted() {
recyclerView.setVisibility(View.GONE);
loadingView.setVisibility(View.GONE);
errorView.setVisibility(View.GONE);
emptyView.setVisibility(View.GONE);
}
private void renderLoading() {
recyclerView.setVisibility(View.GONE);
loadingView.setVisibility(View.VISIBLE);
errorView.setVisibility(View.GONE);
emptyView.setVisibility(View.GONE);
}
private void renderError() {
recyclerView.setVisibility(View.GONE);
loadingView.setVisibility(View.GONE);
errorView.setVisibility(View.VISIBLE);
emptyView.setVisibility(View.GONE);
}
private void renderEmptyResult() {
recyclerView.setVisibility(View.GONE);
loadingView.setVisibility(View.GONE);
errorView.setVisibility(View.GONE);
emptyView.setVisibility(View.VISIBLE);
}
}
The render (SearchViewState) method should look concise. In searchIntent (), I use the RxBindings library . RxSearchView.queryText () creates an Observable that emits a string every time the user types something into the EditText widget. I use filter () so that the search query starts after entering three or more characters. We do not need the search request to be sent to the server every time the user enters a new character, so I added the debounce () operator.
We know that the input data stream for this screen is the searchIntent () method, and the output data stream is the render () method.
The following video demonstrates how the interaction between these two flows occurs.
The question remains, how to connect intent and business logic? If you carefully watch the video, you will see the flatMap () operator in the middle. This indicates the presence of an additional component, which I did not talk about - Presenter , - responsible for the connection of the layers.
public class SearchPresenter extends MviBasePresenter {
private final SearchInteractor searchInteractor;
@Override protected void bindIntents() {
Observable search =
intent(SearchView::searchIntent)
.switchMap(searchInteractor::search) // на видео я использовал flatMap(), но здесь имеет смысл использовать switchMap()
.observeOn(AndroidSchedulers.mainThread());
subscribeViewState(search, SearchView::render);
}
}
What are MviBasePresenter , intent () and subscribeViewState () methods . This class is part of the Mosby library . It is worth saying a few words about Mosby and how MviBasePresenter works. Let's start with the life cycle: MviBasePresenter does not have it. The bindIntent () method associates the intent from the View with business logic. Typically, flatMap () or switchMap () is used to send intent to business logic. This method is called once when the View joins the Presenter, but is not called after the View joins the Presenter again, for example, after changing the screen orientation.
The question may be asked whether the MviBasePresenter can really survive the change in screen orientation, and if so, how does Mosby ensure that the observed stream does not “leak”? The intent () and subscribeViewState () methods are intended for this .
intent () creates a PublishSubject inside Presenter and uses it as a “gateway” for business logic. PublishSubject subscribes to the View View. An Intent call (O1) actually returns a PublishSubject that is subscribed to O1.
After changing the orientation of the screen, Mosby disconnects the View from the Presenter, but only temporarily unsubscribes the internal PublishSubject from the View and re-subscribes the PublishSubject to the View when the View reconnects to the Presenter.
subscribeViewState () does the same thing in the opposite direction. It creates inside the Presenter BehaviorSubject as a “gateway” from business logic to View. Since this is a BehaviorSubject, we can get an updated model from the business logic even when the View is disconnected from the Presenter. The BehaviorSubject always stores the last value it received and repeats it when the View reconnects to the Presenter.
A simple rule: use the intent () method to wrap any intent. Use subscribeViewState () instead of Observable.subscribe (...).

What about other life cycle events like onPause () or onResume () ? I still believe that the presenter does not need life cycle events . However, if you really think you need them, you can create them as an intent. PauseIntent () will appear in your View , the launch of which initiates the Android life cycle, and not the user's action.
Conclusion
In this part, we talked about the basics of MVI and implemented a simple screen. This example is probably too simple to understand all the benefits of MVI. There is nothing wrong with MVP or MVVM, and I am not saying that MVI is better than other architectural patterns. However, I believe that MVI helps us write more elegant code for complex problems, as we will see in the next part, in which we will talk about state reducer .