Что такое презентер android
Перейти к содержимому

Что такое презентер android

  • автор:

Android Architecture Patterns Part 2: Model-View-Presenter

It’s about time we developers start thinking about how we can apply good architecture patterns in our Android apps. To help with this, Google offers Android Architecture Blueprints, where Erik Hellman and I worked together on the MVP & RxJava sample. Let’s have a look at how we applied it and the pros and cons of this approach.

The Model-View-Presenter Pattern

Here are the roles of every component:

  • Model — the data layer. Responsible for handling the business logic and communication with the network and database layers.
  • View — the UI layer. Displays the data and notifies the Presenter about user actions.
  • Presenter — retrieves the data from the Model, applies the UI logic and manages the state of the View, decides what to display and reacts to user input notifications from the View.

Since the View and the Presenter work closely together, they need to have a reference to one another. To make the Presenter unit testable with JUnit, the View is abstracted and an interface for it used. The relationship between the Presenter and its corresponding View is defined in a Contract interface class, making the code more readable and the connection between the two easier to understand.

Model-View-Presenter Architecture Model-View-Presenter class structure

The Model-View-Presenter Pattern & RxJava in Android Architecture Blueprints

The blueprint sample is a ”To Do” application. It lets a user create, read, update and delete “To Do” tasks, as well as apply filters to the displayed list of tasks. RxJava is used to move off the main thread and be able to handle asynchronous operations.

Model

The Model works with the remote and local data sources to get and save the data. This is where the business logic is handled. For example, when requesting the list of Task s, the Model would try to retrieve them from the local data source. If it is empty, it will query the network, save the response in the local data source and then return the list.

The retrieval of tasks is done with the help of RxJava:

The Model receives as parameters in the constructor interfaces of the local and remote data sources, making the Model completely independent from any Android classes and thus easy to unit test with JUnit. For example, to test that getTasks requests data from the local source, we implemented the following test:

The View works with the Presenter to display the data and it notifies the Presenter about the user’s actions. In MVP Activities, Fragments and custom Android views can be Views. Our choice was to use Fragments.

All Views implement the same BaseView interface that allows setting a Presenter.

The View notifies the Presenter that it is ready to be updated by calling the subscribe method of the Presenter in onResume . The View calls presenter.unsubscribe() in onPause to tell the Presenter that it is no longer interested in being updated. If the implementation of the View is an Android custom view, then the subscribe and unsubscribe methods have to be called on onAttachedToWindow and onDetachedFromWindow . User actions, like button clicks, will trigger corresponding methods in the Presenter, this being the one that decides what should happen next.

The Views are tested with Espresso. The statistics screen, for example, needs to display the number of active and completed tasks. The test that checks that this is done correctly first puts some tasks in the TaskRepository ; then launches the StatisticsActivity and checks content of the views:

Presenter

The Presenter and its corresponding View are created by the Activity. References to the View and to the TaskRepository — the Model — are given to the constructor of the Presenter. In the implementation of the constructor, the Presenter will call the setPresenter method of the View. This can be simplified when using a dependency injection framework that allows the injection of the Presenters in the corresponding views, reducing the coupling of the classes. The implementation of the ToDo-MVP with Dagger is covered in another sample.

All Presenters implement the same BasePresenter interface.

When the subscribe method is called, the Presenter starts requesting the data from the Model, then it applies the UI logic to the received data and sets it to the View. For example, in the StatisticsPresenter , all tasks are requested from the TaskRepository — then the retrieved tasks are used to compute the number of active and completed tasks. These numbers will be used as parameters for the showStatistics(int numberOfActiveTasks, int numberOfCompletedTasks) method of the View.

A unit test to check that indeed the showStatistics method is called with the correct values is easy to implement. We are mocking the TaskRepository and the StatisticsContract.View and give the mocked objects as parameters to the constructor of a StatisticsPresenter object. The test implementation is:

The role of the unsubscribe method is to clear all the subscriptions of the Presenter, thus avoiding memory leaks.

Apart from subscribe and unsubscribe , each Presenter exposes other methods, corresponding to the user actions in the View. For example, the AddEditTaskPresenter , adds methods like createTask , that would be called when the user presses the button that creates a new task. This ensures that all the user actions — and consequently all the UI logic — go through the Presenter and thereby can be unit tested.

Disadvantages of Model-View-Presenter Pattern

The Model-View-Presenter pattern brings with it a very good separation of concerns. While this is for sure a pro, when developing a small app or a prototype, this can seem like an overhead. To decrease the number of interfaces used, some developers remove the Contract interface class, and the interface for the Presenter.

One of the pitfalls of MVP appears when moving the UI logic to the Presenter: this becomes now an all-knowing class, with thousands of lines of code. To solve this, split the code even more and remember to create classes that have only one responsibility and are unit testable.

Conclusion

The Model-View-Controller pattern has two main disadvantages: firstly, the View has a reference to both the Controller and the Model; and secondly, it does not limit the handling of UI logic to a single class, this responsibility being shared between the Controller and the View or the Model. The Model-View-Presenter pattern solves both of these issues by breaking the connection that the View has with the Model and creating only one class that handles everything related to the presentation of the View — the Presenter: a single class that is easy to unit test.

What if we want an event-based architecture, where the View reacts on changes? Stay tuned for the next patterns sampled in the Android Architecture Blueprints to see how this can be implemented. Until then, read about our Model-View-ViewModel pattern implementation in the upday app.

Jobs at upday

Written by Florina Muntenescu
Florina passionately works at upday as a Senior Android Developer.

Model View Presenter(MVP) in Android with a simple demo project.

Model–view–presenter (MVP) is a derivation of the model–view–controller (MVC) architectural pattern which mostly used for building user interfaces. In MVP, the presenter assumes the functionality of the “middle-man”. In MVP, all presentation logic is pushed to the presenter. MVP advocates separating business and persistence logic out of the Activity and Fragment

Differences between MVC and MVP

  • Controllers are behavior based and can share multiple views.
  • View can communicate directly with Model
  • View more separated from Model. The Presenter is the mediator between Model and View.
  • Easier to create unit tests
  • Generally there is a one to one mapping between View and Presenter, with the possibility to use multiple Presenters for complex Views
  • Listen to user action and model updates
  • Updates model and view as well

Model

In an application with a good layered architecture, this model would only be the gateway to the domain layer or business logic. See it as the provider of the data we want to display in the view. Model’s responsibilities include using APIs, caching data, managing databases and so on.

The View, usually implemented by an Activity, will contain a reference to the presenter. The only thing that the view will do is to call a method from the Presenter every time there is an interface action.

Presenter

The Presenter is responsible to act as the middle man between View and Model. It retrieves data from the Model and returns it formatted to the View. But unlike the typical MVC, it also decides what happens when you interact with the View.

Project Simple Workflow

Here, I try to illustrate the very basic of MVP pattern with simple demo android project which is available in GITHUB. The project has only one screen that have the username and email EditText and one TextView. When the user tries to input username or email, the input data will be shown in the top of the screen in TextView.

Android. Пару слов об MVP + rxJava

Работая с Android часто можно видеть, как весь функциональный код помещается в методы жизненного цикла activity/fragment. В общем-то такой подход имеет некоторое обоснование — «методы жизненного цикла» всего лишь хэндлеры, обрабатывающие этапы создания компонента системой и специально предназначенные для наполнения их кодом. Добавив сюда то, что каркас UI описывается через xml файлы, мы уже получаем базовое разделение логики и интерфейса. Однако из-за не совсем «изящной» структуры жизненного цикла, его зависимости от множества флагов запуска, и различной (хоть и похожей) структуры для разных компонентов, эффективно воспользоваться подобным разделением не всегда бывает возможно, что в итоге выливается в написании всего кода в onCreate().

Model-View-Presenter+rxJava

MVP паттерн разработки для android, предлагающий разбивать приложение на следующие части:

  1. Model — представляет из себя точку входа к данным приложения (часто на каждый экран своя модель). При этом особой разницы откуда данные быть не должно — данные сетевых запросов или данные взаимодействия пользователя с UI (клики, свайпы и т.д). Хорошее место для внедрения «рукописных» кэшей. В связке с rxJava будет представлять из себя набор методов, отдающих Observable.
  2. View — представляет из себя класс, устанавливающий состояние UI элементов. Не путать термин с android.view.View
  3. Presenter — устанавливает связь между обработкой данных, получаемых из Model и вызовом методов у View, реализуя тем самым реакцию UI компонентов на на данные. Методы Presenter вызываются из методов жизненного цикла activity/fragment и часто «симметричны» им.
Пример

Рассмотрим пример приложения, состоящего из одного экрана, на котором находится EditText и TextView. При этом по мере редактирования текста в EditText отправляются сетевые запросы, результат которых должен отображаться в TextView (конкретика запроса не должна нас волновать, это может быть перевод, краткая справка по термину или что то подобное).

ExampleModel.java:

ExampleView.java:

ExamplePresenter.java:

Реализация
  • По поводу терминологии: в статье используется та же терминология, что и в достаточно по популярной статье про mvp для android-а от Antonio Leiva, здесь можно ознакомится с моим, совсем уж любительским переводом.
  • Некоторых смутило помещение на уровень Model-и метода, отдающего Observable, который отвечает за события, связанные с действиями пользователя (что приводит к необходимости в реализации Model-и содержать объект, связанный android view-шками), но я все же считаю это корректным и даже удобным, так как позволяет рассуждать о событиях редактирование текста, для которого требуется обработка, именно, как о потоке данных, которые лишь в конкретной реализации связаны с UI-ем. Если вас принципиально не устраивает это, то можете рассмотреть этот совсем уреазный пример т.е просто часть методов перейдет из Model в View. Так же можете рассмотреть пример из статье Antonio Leiva, связь компонентов там такая M<=P<=>V

Так как Model и View используют одни и тебе виджеты (в нашем случае EditText и TextView) для своей работы, разумно будет реализовать содержащий их класс.

ExampleViewHolder.java:

При реализации Model мы предполагаем использование rxAndroid, для «оборачивания» EditTetx, и retrofit для реализации сетевых запросов.

ExampleModelImpl.java:

ExampleViewImpl.java:

Так как количество сетевых запросов зависит от скорости набора текста (а она может быть достаточно высока), существует естественное желание ограничить частоту событий редактирование текста в EditText. В данном случае это реализуется оператором debounce (при этом, естественно, ввод текста не блокируется, а лишь пропускается часть событий редактирования, произошедших в временной промежуток в 150 миллисекунд).

ExamplePresenterImpl.java:

Реализация activity, передающая всю сущностную часть работы Presenter:

ExampleActivity.java

Заключение

Хотя наш пример невероятно упрощен, в нем уже есть нетривиальные моменты связанные с контролем частоты событий. Представить же эволюцию нашего приложения в разрезе mvp довольно легко:

Использование паттерна MVP в Android

При разработке сложных приложений можно столкнуться с проблемами, которые, вероятно, возникали раньше и уже имеют большое количество решений. Такие решения называются паттернами (шаблонами). Как правило, говорят о паттернах дизайна и паттернах архитектуры. Они упрощают разработку приложений, поэтому целесообразно их использовать, если такая возможность есть.

Дизайн проекта должен быть приоритетной задачей с самого начала разработки. Одна из задач, которые нужно решить в первую очередь — это архитектура проекта, определяющая, как разные элементы нашего приложения будут соотноситься друг с другом.

Хотя Android предлагает отличный SDK, его архитектурные паттерны довольно необычны и могут мешать во время разработки, особенно при создании сложных приложений, которые необходимо тестировать и поддерживать в течение долгого времени. К счастью, мы можем сами выбирать, как хотим проектировать приложение.

Инструменты, предлагаемые Android, предлагают нам использовать модель MVC (Model View Controller). MVC представляет собой паттерн, который направлен на разделение ролей в приложении.

Архитектура в нём делится на три слоя:

  • Model;
  • View;
  • Controller.

Каждый слой отвечает за отдельный аспект приложения. Model отвечает за бизнес-логику, View — за пользовательский интерфейс, а Controller обеспечивает доступ к Model.

Однако, если проанализировать архитектуру Android, особенно отношение между View (Activity, Fragment и т.д.) и Model (структуры данных), то можно сделать вывод, что это нельзя считать MVC. Если мы подумает о симбиотической связи между загрузчиками и активностями с фрагментами, то то вы сможете понять, почему нужно уделять пристальное внимание архитектуре Android. Активность или фрагмент отвечает за вызов загрузчика, который должен извлекать данные и возвращать их родителям. Его существование полностью привязано к его родителям и нет разделение между ролью View и бизнес-логикой, выполняемой загрузчиком.

Отсюда возникает проблемы:

  • Как можно использовать модульное тестирование в приложении, в котором данные тесно связаны с представлениями (активностями или фрагментами)?
  • Как найти проблему в чужом коде, если проект не придерживается какого-то паттерна и код может находиться буквально в любом месте?

Чтобы решить эти проблемы, нужно выбрать для разработки какую-то свою архитектуру. Примером хорошего паттерна может являться MVP (Model View Presenter). MVP является аналогом MVC, но с более современной парадигмой, которая создаёт лучшее разделение ролей и максимизирует тестируемость приложения.

MVP разделяет приложение на следующие уровни:

  • Model;
  • View;
  • Presenter.

У каждого из них есть свои обязанности и связь между ними происходит через Presenter.

Model содержит бизнес-логику приложения. Он контролирует как данные создаются, сохраняются и изменяются.

View это интерфейс, который отображает данные и направляет действия пользователя в Presenter.

Presenter выступает в роли посредника. Он извлекает данные из Model и показывает их в View. Он также обрабатывает действия пользователя, переданные ему из View.

Основным отличием от MVC здесь является то, что Presenter контролирует взаимодействие между View и Moder более строго, чем это делает Controller, в MVC слой View может сам извлекать данные из Model. Кроме того, MVP обладает и другими отличиями, делающими его более подходящим для построения архитектуры приложения:

  • View не имеет доступа к Model;
  • Presenter привязан к одному View;
  • View полностью пассивен;

Такое разделение слоёв позволяет лучше тестировать приложение, поскольку заранее известно, на каком уровне нужно искать код.

Реализуем паттерн MVP в одном из наших приложений (почитать о его реализации вы можете, перейдя по этой ссылке или по этой). Исходный код MainActivity в приложении выглядит следующим образом:

На уровне Model в нашем приложении будет использоваться класс AppInfo, отвечающий за создание, хранение и изменение данных о приложениях.

Для того, чтобы работать с данными из модели, создадим на этом же уровне специальный класс-интерактор, который будет сортировать списки приложений по категориям и возвращать итоговый результат в Presenter. Интерфейс класса будет состоять из одного метода filterApps(int currentList, List<AppInfo> mAppsList), который реализует сортировку.

Сама реализация выглядит следующим образом.

Преимущества использования интерактора в отдельном классе заключается в том, что он разбивает Presenter, делая его код более чистым и проверяемым, а также служит для абстракции логики данной модели. Класс-интерактор будет передаваться в Presenter при запуске приложения, поэтому у Presenter всегда будет к нему доступ.

Теперь нам нужно создать Presenter. Для этого необходимо перенести в отдельный класс обработку всех действий пользователя, а также работу с данными. Интерфейс получившегося презентера выглядит так:

При запуске приложения в методе onCreate() активности мы создаём экземпляр Presenter и передаём ему интерактор.

Таким образом, имеем следующий конструктор.

Затем необходимо передать в презентер родительскую View, для этого вызывается метод презентера attachView(MainView mainView).

При завершении работы с активностью крайне важно затем откреплять View от Presenter, поскольку нам не нужно хранить View всё время. Как правило, экземпляр View следует очищать в onDestroy() или onPause() вашей активности, в зависимости от требований приложения. В данном случае удаление будет происходит в методе onDestroy().

И собственно код выставление значения null экземпляру.

После этого уже можно приступить к реализации методов, отвечающих за обработку действий пользователя и работу с данными в приложении. В результате получился следующий класс Presenter, связывающий уровни View и Model в нашем приложении.

Для связи презентера с активностью реализуем интерфейс, содержащий в себе методы для возврата результатов работы на View или для запроса необходимых данных.

Запрос различных объектов, таких как PackageManager, происходит на уровне View потому, что Presenter не должен ничего знать о контексте приложения и тем более хранить его у себя. Некоторые разработчики для этих целей советуют хранить статический контекст в отдельном классе Application и запрашивать его оттуда, однако анализатор кода Android Studio настоятельно не рекомендует использовать такой способ, поскольку он приводит к утечкам памяти.

Перенеся логику работы приложения и оставив только пользовательский интерфейс, мы разгрузили код главной активности, сделав его более удобным и читаемым. Итоговый класс MainActivity после преобразований выглядит следующим образом:

После всех проделанных операций мы разделили код на три уровня, каждый из которых отвечает выполняет определённые задачи. Подводя итоги, можно сделать следующие выводы:

  • MainActivity не знает ничего об уровне Model;
  • MainActivity не заботится о результате запроса от Presenter;
  • Логика callback-ов остаётся в Presenter. Поэтому если вам захочется в будущем её изменить, вам не нужно будет затрагивать View.

Таким образом, используя в своих приложениях паттерн MVP, мы можем сделать разработку намного проще и понятнее.

Исходный код приложения вы можете посмотреть на GitHub, перейдя по следующей ссылке. Само приложение «Менеджер системных приложений» доступно в Google Play здесь.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *