📜 ⬆️ ⬇️

Extensible Android application code with MVP

From the translator: - I have long been interested in how to make the code of Android applications cleaner, and this is probably the first article, after which I didn’t have any thoughts: "Why is this all here?" and "Did he ever try to use it in his life?" Therefore, I decided to translate, maybe someone else would be useful .

Write Hello World is always easy. The code looks simple and straightforward, and it seems that the SDK is very adapted to your needs. But if you have experience writing more complex Android applications, you know that this is not the case with working code. You can spend hours trying to understand why your shopping cart is not updated after changing the orientation of your phone if WiFi is not available. You assume that the solution to the problem may be to add another if in the 457-line onCreate() method of your activation - somewhere between the code that fixes the crash on Samsung 4.1 on board, and the one that shows the coupon on $ 5 on the user's birthday. Well, there is a better way.

In Remind, we roll out new functions every two weeks, and in order to maintain this speed and high quality of the product, we need a way to keep the code simple, maintained, divided . "decoupled", in the sense of loose coupling, and tested. Using the MVP architectural pattern allows us to do this and focus on the most significant part of our code - our business logic.

MVP , or Model-View-Presenter, is one of several patterns that contributes to the division of responsibility when implementing a user interface. In each of these patterns, the roles of the layers are slightly different. The purpose of this article is not to describe the differences between the patterns, but to show how this can be applied on the android (by analogy with modern UI frameworks such as Rails and iOS ), and how your application will benefit from this.
')
An example of code that illustrates most of the approaches described below can be found here:
https://github.com/remind101/android-arch-sample

Old School Android


The division of responsibility, which is implied by the Android framework, looks like this: A model can be any POJO, a View is an XML markup, and a fragment (or initially activated) acts as a Controller / Presenter . In theory, this works quite well, but as soon as your application grows, a lot of code related to the Presentation appears in the Controller. This is because not so much can be done with XML, so that all data binding (data-binding), animation, input processing, etc., is performed in the fragment, along with business logic.

Everything becomes even worse when complex interface elements are placed in lists or grids (GridView / GridLayout means, in general, "grid elements"). Now the adapter is responsible not only to store the view and controller code for all these elements, but also to manage them as a collection. Since all these elements are tightly coupled, they become very difficult to maintain and even harder to test.

Enter the Model-View-Presenter


MVP gives us the opportunity to highlight all that boring low-level Android code that is needed to display our interface and interact with it, in the View , and the higher-level business logic of what our application should do, evict to Presenter .

To achieve this on an android, you need to consider the activation or a fragment as a presentation layer, and provide a lightweight presenter in order to control the presentation. The most important thing is to determine the responsibility of each layer, and standardize the interface between them. Here is a general description of the division, which works very well with us:

The presentation (activation or fragment) is responsible for:

  1. Creating a copy of the presenter and the mechanism of its connection / disconnection;
  2. Alert the presenter about important life cycle events for him;
  3. A message to the presenter about the input events;
  4. Placing the views and connecting them to the data;
  5. Animations;
  6. Event tracking;
  7. Switch to other screens.

The presenter is responsible for:

  1. Loading models;
  2. Saving the reference to the model and view state;
  3. Formatting what should be displayed on the screen, and instructing the view to display it;
  4. Interaction with repositories (database, network, etc.) ( approx. Lane. Repository is a pattern, just in case);
  5. Determining what to do when input events are received from the view.

Here is an example of what the interface between a presentation and a presenter could be:

 interface MessageView { //      ,         //     void setMessageBody(String body); void setAuthorName(String name); void showTranslationButton(boolean shouldShow); //   void goToUserProfile(User user); } interface MessagePresenter { //       ,        //     void onStart(); //    void onAuthorClicked(); void onThreeFingersSwipe(); } 

There are a couple of interesting points to consider about this interface:


As it turned out in practice, if the code of your presenter contains the code of the Android framework, and not just pure Java, you are probably doing something wrong. And accordingly, if your presentations need a reference to a model, apparently, you are also doing something wrong.

As soon as the question of tests arises, most of the code that you need to test will be in the presenter . What is cool is that this code does not need Android to run, since it only has links to the presentation interface, and not to its implementation in the context of Android. This means that you can simply get up the presentation interface and write clean JUnit tests for business logic that check the correctness of calling methods on the wet presentation. This is how our tests look now.

What about lists?


Until now, we assumed that our ideas are activations and fragments, but in reality they can be anything. We did quite well with the lists, having a ViewHolder that implements the view interface (both RecyclerView.ViewHolder , and the usual old ViewHolder for use in conjunction with a ListView). In the adapter, you just need the basic logic for handling the attachment / detachment of presenters (an example of all this is in the git repository).

If you look at an example of a screen containing a list of messages, download progress, and an empty view, the division of responsibility will be as follows:


All of these components are loosely coupled and can be tested separately from each other.

Moreover, if you have a message list screen and a detail screen, you can reuse the same message presenter and just have two different implementations of the presentation interface (in the ViewHolder and the snippet). This saves your DRY code ( approx. Lane - “Don't Repeat Yourself”, or “Do not repeat”, who does not know).

Similarly, a view interface can implement custom views. This allows you to use MVP in custom widgets to reuse it in different parts of the application, or simply break complex interfaces into simpler blocks.

MVP and configuration changes


If you’ve been writing for Android for some time, you know how much pain the support for changing orientation and configuration delivers:


Proper use of MVP can resolve this issue without having to think about it at all. Since presenters do not have a strong reference to the current UI, they are very lightweight and can be restored when the orientation changes! Since the presenter stores the link to the model and the state of the view, it can restore the desired state of the view after the orientation change. Here is a rough description of what happens when the screen is rotated, if this pattern is used:


How to save fragments between changes of orientation can be seen in the repository in the class PresenterManager .

Total


Yes, this is the end. I hope it turned out to demonstrate how sharing responsibility like MVP will help you write supported and tested code.

Summarizing:


The implementation of the above can be found in the ArchExample repository .
There are also many libraries that can help you use this approach, for example, Mosby , Flow and Mortar , or Nucleus . I advise you to consider them.

Source: https://habr.com/ru/post/278769/


All Articles