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

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

  • автор:

Android MVP пример для начинающих. Без библиотек и интерфейсов.

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

Что такое MVP

Сначала немного теории о MVP. Схематично это выглядит так:

MVP расшифровывается как Model-View-Presenter (модель-представление-презентер). Если рассматривать Activity, которое отображает какие-то данные с сервера, то View — это Activity, а Model — это ваши классы по работе с сервером. Напрямую View и Model не взаимодействуют. Для этого используется Presenter.

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

Если экран отображает данные из базы данных, то модель — это база данных. Презентер может подписаться на уведомления модели об обновлении. В случае, когда данные в БД изменятся, модель оповестит об этом презентер. Презентер получит эти изменения и передаст их в Activity.

Можно сказать, что презентер — это логика, вынесенная из Activity в отдельный класс. А Activity остается для отображения данных и взаимодействия с пользователем. Если вы решили сделать другое Activity для отображения данных, то вам уже не нужно будет переносить логику в новое Activity, можно будет использовать готовый Presenter. А если вы решили поменять логику, то вам не нужно будет лезть в Activity и там, среди кода, который отвечает за отображение данных и взаимодействие с пользователем, искать логику и менять ее. Вы меняете код в презентере.

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

Практика

Я создал небольшое приложение и залил на гитхаб.

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

Чтобы наглядно показать отличие MVP, я сделал этот экран в двух вариантах: Activity и MVP. Вы можете выбрать нужный вариант при запуске:

Оба этих режима внешне будут выглядеть и работать одинаково, но «под капотом» они разные.

Первый вариант реализован с помощью одного Activity — SingleActivity. В нем реализовано следующее:
— вывод информации на экран и обработка нажатий
— логика (что делать по нажатию на кнопки и что/когда показывать)
— работа с базой данных.

Такой вариант реализации считается тяжелым и неудобным. Слишком много всего возложено на один класс.

Второй вариант реализован с помощью MVP — mvp.

В этом варианте я просто разделил код из SingleActivity на три класса в соответствии с MVP:
— в UsersModel — работа с базой данных (Model)
— в UsersActivity — вывод информации на экран и обработка нажатий (View)
— в UsersPresenter — логика (Presenter)

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

UsersModel

Это Model (модель). В модели обычно реализована работа с данными, например: запрос данных с сервера, сохранение в БД, чтение файлов и т.п.

Здесь находятся все операции с базой данных. Этот класс имеет три public метода, которые вызываются презентером:
loadUsers — получение списка пользователей из БД
addUsers — добавление пользователя в БД
clearUsers — удаление всех пользователей из БД

Что происходит внутри этих методов — касается только модели. Презентер будет просто вызывать эти методы и его не должно интересовать, как именно они реализованы.

Методам на вход можно передавать колбэки, которые будут вызваны по окончанию операции. Асинхронность работы с БД реализована с помощью AsyncTask. В методы добавления и удаления добавлены секундные паузы для наглядности.

UserActivity

Это View (представление). Представление отвечает за отображение данных на экране и за обработку действий пользователя.

Здесь есть несколько public методов, вызываемых презентером:
getUserData — получение данных, введенных пользователем
showUsers — отображение списка пользователей
showToast — отображение Toast
showProgress/hideProgress — скрыть/показать прогресс-диалог

В представлении не должно быть никакой логики. Это только инструмент для отображения данных и взаимодействия с пользователем.

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

Повторюсь, т.к. очень важно понимать это правильно. По нажатию на кнопки, Activity не просит презентер добавить пользователя или удалить всех пользователей. Т.е. оно не указывает презентеру, что ему делать. Оно просто сообщает, что была нажата кнопка Add или Clear. А презентер принимает это к сведению и действует по своему усмотрению.

UsersPresenter

Это Presenter (презентер). Он является связующим звеном между представлением и моделью, которые не должны общаться напрямую.

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

Давайте по шагам рассмотрим взаимодействие представления, презентера и модели на нашем примере. Возьмем сценарий добавления новых данных в базу.

1) Пользователь вводит данные в поля ввода. Это никак не обрабатывается и ничего не происходит.
2) Пользователь жмет кнопку Add. Вот тут начинается движ.
3) Представление сообщает презентеру о том, что была нажата кнопка Add.
4) Презентер просит представление дать ему данные, которые были введены пользователем в поля ввода.
5) Презентер проверяет эти данные на корректность.
6) Если они некорректны, то презентер просит представление показать сообщение об этом.
7) Если данные корректны, то презентер просит представление показать прогресс-диалог и просит модель добавить данные в базу данных.
8) Модель асинхронно выполняет вставку данных и сообщает презентеру, что вставка завершена
9) Презентер просит представление убрать прогресс-диалог.
10) Презентер просит свежие данные у модели.
11) Модель возвращает данные презентеру.
12 Презентер просит представление показать новые данные.

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

Обратите внимание на методы презентера: attachView и detachView. Первый дает презентеру представление для работы, а второй говорит, что представление надо отпустить. Эти методы вызывает само представление. Первый метод — после своего создания, а второй — перед своим уничтожением. Иначе, если презентер будет держать ссылку на представление после его официального уничтожения, то может возникнуть утечка памяти.

Метод viewIsReady вызывается представлением, чтобы сообщить о том, что представление готово к работе. Презентер запрашивает у модели данные и просит представление отобразить их.

Плюсы MVP

Кратко напишу преимущества MVP по сравнению с Activity.
— легче писать тесты
— в небольших классах искать что-либо и вносить изменения легче, чем в одном большом
— бывает так, что одно представление используется разными презентерами, или наоборот — один презентер используется для разных представлений. Если у вас все в одном Activity — вы не сможете так сделать.

Все плюсы вытекают из того, что вместо одного класса, мы используем несколько.

Что дальше?

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

Интерфейсы.

Это то, что очень запутывает новичков в примерах MVP, но действительно является очень полезным инструментом.

Обратите внимание на взаимодействие презентера и представления в нашем примере. У представления есть несколько методов, которые вызывает презентер: getUserData, showUsers, showToast, showProgress, hideProgress. Вот эти методы — это все что должен знать презентер. Ему не нужны больше никакие знания о представлении. А в текущей реализации презентер знает, что его представление — это UsersActivity. Т.е. это целое Activity с кучей методов, которые презентеру знать незачем. Использование интерфейсов решает эту проблему.

Мы можем создать интерфейс UsersContractView

interface UsersContractView < UserData getUserData(); void showUsers(Listusers); void showToast(int resId); void showProgress(); void hideProgress(); >

Добавить этот интерфейс в UsersActivity

public class UsersActivity extends AppCompatActivity implements UsersContractView

Теперь в презентере можно убрать все упоминания о UsersActivity, и оставить только UsersContractView.

public class UsersPresenter < private UsersContractView view; public void attachView(UsersContractView view) < this.view = view; >. >

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

А если презентер завязан на конкретный класс, например, UsersActivity, то при замене представления, вам придется открыть презентер и поменять там UsersActivity на другой класс.

Асинхронные операции

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

Создание объектов

В UsersActivity мы создаем презентер следующим образом:

DbHelper dbHelper = new DbHelper(this); UsersModel usersModel = new UsersModel(dbHelper); presenter = new UsersPresenter(usersModel);

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

Поворот экрана

В этом примере нет обработки поворота экрана. Если ваш презентер выполняет какую-то долгую операцию, и вы повернете экран, у вас просто создастся новый презентер, а результаты работы старого презентера могут быть потеряны.

Есть различные способы, как этого избежать. Один из них — не пересоздавать презентер, если представление пересоздается. При этом, презенетер отпускает старое представление (метод detachView), и получает новое представление (метод attahcView). В итоге, результаты работы долгой операции будут отображены уже в новом представлении.

Если тема MVP стала вам интересна, то посмотрите этот пример. Он посложнее и более приближен к реальному коду.

Присоединяйтесь к нам в Telegram:

— в канале StartAndroid публикуются ссылки на новые статьи с сайта startandroid.ru и интересные материалы с хабра, medium.com и т.п.

— в чатах решаем возникающие вопросы и проблемы по различным темам: Android, Compose, Kotlin, RxJava, Dagger, Тестирование, Performance

— ну и если просто хочется поговорить с коллегами по разработке, то есть чат Флудильня

АРХИТЕКТУРНЫЙ ПАТТЕРН MODEL-VIEW-PRESENTER В РАЗРАБОТКЕ ПОД МОБИЛЬНУЮ ПЛАТФОРМУ ANDROID Текст научной статьи по специальности «Компьютерные и информационные науки»

ПРОГРАММНОЕ ОБЕСПЕЧЕНИЕ / ИНТЕРФЕЙС ПРИКЛАДНОГО ПРОГРАММИРОВАНИЯ / ПРИНЦИПЫ ПРОЕКТИРОВАНИЯ / АРХИТЕКТУРА МОБИЛЬНЫХ ПРИЛОЖЕНИЙ / МОДЕЛЬ-ПРЕДСТАВЛЕНИЕ-КОНТРОЛЛЕР / МОДЕЛЬ-ПРЕДСТАВЛЕНИЕ-ПРЕЗЕНТЕР / SOFTWARE / APPLICATION PROGRAMMING INTERFACE / DESIGN PRINCIPLES / MOBILE APPLICATION ARCHITECTURE / MODEL-VIEW-CONTROLLER / MODEL-VIEW-PRESENTER

Аннотация научной статьи по компьютерным и информационным наукам, автор научной работы — Шульгин Е.М.

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

i Надоели баннеры? Вы всегда можете отключить рекламу.

Похожие темы научных работ по компьютерным и информационным наукам , автор научной работы — Шульгин Е.М.

СРАВНИТЕЛЬНЫЙ АНАЛИЗ ШАБЛОНОВ ПРОЕКТИРОВАНИЯ ПРИЛОЖЕНИЙ С ПОЛЬЗОВАТЕЛЬСКИМ ИНТЕРФЕЙСОМ

СОЗДАНИЕ СИСТЕМЫ КОНТРОЛЯ ПРОСТРАНСТВЕННО-ВРЕМЕННОГО СОСТОЯНИЯ ТЕХНОГЕННЫХ ОБЪЕКТОВ С ИСПОЛЬЗОВАНИЕМ ШАБЛОНА ПРОЕКТИРОВАНИЯ MODEL-VIEW-PRESENTER

Принципы разработки приложений под операционную систему iOS

Разработка мобильного приложения, взаимодействующего с сервером, с использованием архитектурных компонентов операционной системы andorid и Custom View

ПРОЦЕСС ВЫБОРА АРХИТЕКТУРЫ ДЛЯ МОБИЛЬНОГО ПРИЛОЖЕНИЯ
i Не можете найти то, что вам нужно? Попробуйте сервис подбора литературы.
i Надоели баннеры? Вы всегда можете отключить рекламу.

ARCHITECTURE PATTERN MODEL-VIEW-PRESENTER IN DEVELOPING FOR THE ANDROID MOBILE PLATFORM

The article describes the results of the research of architectural solutions in the field of application development for the mobile operating system Android. The article discusses the basics of applications based on the Android mobile operating system, as well as the architectural patterns used in their development. The model-View-Presenter pattern is considered in detail, its positive qualities are revealed and the general approach to application design on its basis is described. The process of application design based on this pattern is also described.

Текст научной работы на тему «АРХИТЕКТУРНЫЙ ПАТТЕРН MODEL-VIEW-PRESENTER В РАЗРАБОТКЕ ПОД МОБИЛЬНУЮ ПЛАТФОРМУ ANDROID»

Шульгин Е.М. студент магистратуры 2 курса факультет «информационных технологий» Российский технологический университет

Россия, г. Москва

АРХИТЕКТУРНЫЙ ПАТТЕРН MODEL-VIEW-PRESENTER В

РАЗРАБОТКЕ ПОД МОБИЛЬНУЮ ПЛАТФОРМУ ANDROID

Статья посвящена описанию результатов исследования архитектурных решений в области разработки приложений под мобильную операционную систему Android.

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

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

Shulgin E.M. master’s degree student 2 course, faculty of information technology Russian University of technology Moscow, Russia

ARCHITECTURE PATTERN MODEL-VIEW-PRESENTER IN DEVELOPING FOR THE ANDROID MOBILE PLATFORM

The article describes the results of the research of architectural solutions in the field of application development for the mobile operating system Android.

The article discusses the basics of applications based on the Android mobile operating system, as well as the architectural patterns used in their development. The model-View-Presenter pattern is considered in detail, its positive qualities are revealed and the general approach to application design on its basis is described. The process of application design based on this pattern is also described.

Keywords: Software, application programming interface, design principles, mobile application architecture, model-view-controller, model-view-presenter.

Android — это портативная операционная система для коммуникаторов, планшетных компьютеров, электронных книжек, цифровых проигрывателей, наручных часов, нетбуков и смартбуков, основанная на ядре Linux, впервые выпущенная в сентябре 2008 года. На данный момент эта система установлена на большинстве продаваемых мобильных устройств в мире. Приложения под Android состоят из четырех основных компонентов:

Операция (Activity) представляет собой один экран с пользовательским интерфейсом. Например, в приложении для работы с электронной почтой одна операция может служить для отображения списка новых сообщений, другая — для составления сообщения и третья операция — для чтения сообщений. Несмотря на то, что операции совместно формируют связное взаимодействие пользователя с приложением по работе с электронной почтой, каждая из них не зависит от других операций. Любые из этих операций могут быть запущены другим приложением (если это позволяет приложение по работе с электронной почтой). Например, приложение для камеры может запустить операцию в приложении по работе с электронной почтой, которая составляет новое сообщение, чтобы пользователь мог отослать фотографию.

Служба (Service) представляет собой компонент, который работает в фоновом режиме и выполняет длительные операции, связанные с работой удаленных процессов. Служба не имеет пользовательского интерфейса. Например, она может воспроизводить музыку в фоновом режиме, пока пользователь работает в другом приложении, или же она может получать данные по сети, не блокируя взаимодействие пользователя с операцией. Служба может быть запущена другим компонентом, который затем будут взаимодействовать с ней, — например операцией.

Поставщик контента (Content provider) управляет общим набором данных приложения. Данные можно хранить в файловой системе, базе данных SQLite, в Интернете или любом другом постоянном месте хранения, к которому у вашего приложения имеется доступ. Посредством поставщика контента другие приложения могут запрашивать или даже изменять данные (если поставщик контента позволяет делать это). Например, в системе Android есть поставщик контента, который управляет информацией контактов пользователя. Любое приложение, получившее соответствующие разрешения, может запросить часть этого поставщика контента для чтения и записи сведений об определенном человеке.

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

Приемник широковещательных сообщений (Broadcast receiver) представляет собой компонент, который реагирует на объявления распространяемые по всей системе. Многие из этих объявлений рассылает

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

Разработка под операционную систему Android долгое время являла собой программирование без использования каких-либо архитектурных решений. Это связано с тем, что самой компанией Google, разработавшей данную операционную систему, в свое время не было предложено никаких готовых архитектурных решений, библиотек и даже рекомендаций по правильному проектированию приложений. Всвязи с этим, с течением времени в среде андроид-разработчиков появлялись различные архитектурные идеи и подходы, из которых укоренились так называемые Model-View-Controller, Model-View-Presenter и Clean Architecture.

В ходе статьи будут рассмотрены каждые из них, а также детально остановлюсь на паттерне Model-View-Presenter, самом широко-используемом и применимом в среде android-разработчиков.

ЧАСТЬ 1. АРХИТЕКТУРНЫЕ ПОДХОДЫ В ANDROID

Model — View — Controller

Существование данного паттерна в андроид-разработке обусловлено попыткой его использования разработчиками, пришедшими в мир Android из Web-разработки.

Сам паттерн предлагает разделение приложения на 3 сущности, взаимодействующие друг с другом — View, Controller и Model.

Здесь модель получает и отдает данные, изменяя представление.

Представление отображает актуальное состояние модели.

Контроллер, получая запросы от представления, оповещает модель о необходимости изменения.

Рисунок 1. Визуальное представление паттерна MVC.

Разделение на слои помогает разработчику следовать принципам SOLID — делает код менее связным, более поддерживаемым и тестируемым. SOLID включает в себя 5 основных принципов проектирования и ООП:

• S — SRP — Принцип единственной ответственности. Код базируется только на узкоспециализированных классах

• O — OCP — Принцип открытости/закрытости. Классы должны быть открыты для расширения, но закрыты для изменения

• L — LSP — Принцип подстановки Барбары Лисков. Объекты в программе должны быть заменяемыми на экземпляры их подтипов без изменения правильности выполнения программы

• I — ISP — Принцип разделения интерфейса. Следует избегать массивных интерфейсов, разделяя их на несколько частей.

• D — DIP — Принцип инверсии зависимостей. Следует программировать на уровне интерфейсов, а не конкретных реализаций.

В Android — разработке использования паттерна MVC весьма затруднительно. Это связано с различными попытками его внедрения под платформу и в нарушении принципа единственной ответственности слоя отображения — от всегда зависит от модели и должен сам реагировать на ее изменения.

Model — View — Presenter

Данный паттерн, как и MVC, предлагает разделение приложения на 3 сущности: Model, View и Presenter. Здесь представление все также отвечает за представление данных и получение данных от пользователя и модель реализует бизнес-логику и работу с данными. Отличие заключается в презентере, который является связующим звеном между представлением и моделью. Именно презентеру делегируется работа с данными пользователя, которую ему предоставляет представление, и именно он решает, что отображать представлению, в зависимости от модели данных.

Рисунок 2. Визуальное представление паттерна MVP.

Паттерн MVP получил широкое распространение при разработке Android — приложений. Он позволяет разрабатывать слой представления максимально пассивным, что значительно улучшает процесс тестирования всех слоев приложения.

В основе данного паттерна лежит идея о дополнительном разделении слоев и вынесении безнес-логики в отдельный для нее слой, называемый «интерактором» или «юз-кейсом». Изначально данный подход презентовался известным американским программистом Робертом Мартином (Robert Martin) для общего назначения в сфере разработки программного обеспечения, но нашел широкий отклик в более узком кругу андроид-разработчиков. На рис. 3 изображена оригинальная схема из статьи Роберта Мартина «The Clean Architecture».

Рисунок 3. Визуальное представление паттерна Clean Architecture. Рассмотрим подробнее схему, представленную на рисунке 3. Как видим, архитектура состоит из четырех слоев, а стрелка указывает направление зависимости, что формирует следующее важное правило: ни один внутренний слой не должен ничего знать о внешнем. Здесь внешним

слоем считается слой представления данных, затем следует слой работы и управления представлением, затем слой сценариев взаимодействия и слой бизнес-объектов. При разработке андроид-приложений данный подход немного изменился и упростился, сохранив в себе три слоя — данных, бизнес-логики и представления. При этом для обеспечения независимости всех слоев, каждый из них оперирует своей моделью данных. На рисунках 4,5 изображены оригинальные схемы из статьи «Architecting Android. The clean way?» Fernando Cejas.

Presentation T Domain

Mods! View ‘ 3 Regular Java

Рисунок 4. Визуальное представление паттерна Clean Architecture, адаптированного под мобильную разработку на платформе Android.

Как видно из рисунка 4, слой представления базируется на вышеупомянутом подходе Model-View-Presenter, что доказывает важность и универсальность данного подхода. Стоит также отметить, что, как правило, слой данных базируется на так-называемом паттерне «репозиторий», сущности, отвечающей за предоставление данных в одном и только одном виде (в англ. яз. называемом «stick of thruth»).

Рисунок 4. Визуальное представление паттерна Repository.

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

Architecture, по-сути, являясь универсальным инструментом в разработке Android-приложений.

ЧАСТЬ 2. АРХИТЕКТУРНЫЙ ПОДХОД MODEL-VIEW-

Основная идея MVP заключается в разделении логики и UI-части приложения так, чтобы их можно было тестировать по отдельности и этот паттерн используется в Android в первую очередь. Рассмотри части паттерна подробнее.

Model. Есть два подхода к пониманию этой сущности. Кто-то оперирует понятием Model в смысле всего слоя данных в приложении: это и бизнес-объекты, содержащие логику, и способ их получения (Repository), и какие-то менеджеры и другие элементы, относящиеся к данным. Такой подход уместен, если говорить, что ваша система использует исключительно паттерн MVP и больше никаких элементов. Но мы решили сохранить слой данных в том виде, в котором он был изложен в принципах «чистой» архитектуры, поэтому под Model мы будем понимать обычные классы объектов, которые используются при взаимодействии View с делегатом. Плюс такого подхода заключается в том, что мы разделяем сущности, что может упрощать понимание. На конечный результат использование разных терминологий никак не влияет, но это нужно учитывать при изучении других источников.

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

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

Рисунок 5. UML-диаграмма архитектурного подхода Model-View-

Обычно экземпляр View создаёт экземпляр Presenter, передавая ему ссылку на себя. При этом Presenter работает с View в абстрактном виде, через его интерфейс. Когда вызывается событие View, оно вызывает конкретный метод Presenter, не имеющего возвращаемого значения. Presenter получает необходимые для работы метода данные о состоянии пользовательского интерфейса через интерфейс View и через него же передаёт в View данные из Модели и другие результаты своей работы. При этом с моделью он также работает через интерфейс модели, запрашивая у нее данные и получая результат для View. Сущность модели может отличаться в зависимоти от слоев. Например, из внешнего хранилища слой данных получил объект, но для того, чтобы отобразить информацию пользователю этот объект следует модифицировать. Тогда изначальный объект изменяется в слое данных, в Presenter попадает объект, готовы для отображения.

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

Рисунок 6. UML-диаграмма архитектуры разрабатываемого

На рисунке 6 представлена UML-диаграмма архитектуры приложения. Видно, что классы разделены на слои, согласно паттерну Model-View-Presenter.

В слое View содержаться необходимые интерфейсы, обеспечивающие контракт по отображению данных в приложении. Класс UsersListFragment является входной точкой и наследуется от одного из компонентов андроид-приложения. Он также содержит необходимые интерфейсы, так как несет обязанность за отображение данных.

Слой Presenter в данном случае представляется одним классом, но несет самую большую роль: он диктует, какие данные получать из слоя модели и отображать слою представления. Со слоем представления он общается через интерфейс IUsersListView, а со слоем данных — через IUsersListRepository, следуя правилам SOLID.

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

Первый этап в разработке — подготовка программной среды. При проектировании android-приложений самой популярной и востребованной средой является IDE «Android Studio», разработанная на основе известной IntelliJ IDEA от команды из компании JetBrains.

Create Android Project

P цып: tlJuM MrtU geprsLeaAppifcsbur

Inchjfr m * (гы^^М^Пг,

Рисунок 7. Создание проекта. После создания проекта (рис. 7) была разделила его структура на две категории — presentation и data. В категории presentation будут расположены слои Presenter и View, в категории data — Model._

Рисунок 8. Категории проекта.

Далее следует продолжение формирования архитектурного каркаса приложения, следуя паттерну Model-View-Presenter — в категорию presentation добавляются подкатегории base и usersList. В категории base созданы два интерфейса ErrorView и LoadingView — при дальнейшем росте проекта они могут понадобиться и для иных задач._

i Не можете найти то, что вам нужно? Попробуйте сервис подбора литературы.

9 rr*lti«|l . .. ■.■.-‘ n.dlili □ 1 ri. Tyripjl L Ксй 1 ÏM.pitH iJr. . j: f

* ■ i ru» * an. V ЕИиш-i! * ft mm Аждолрщ * EfeLllll ® & ПТПШПШТИ1 ■ a luur r h-nrVbn щ iMrt-nVÉB» » bl’Un!!« |ШЫ0Мт ■~i Am * Ни . ■ Ml m 1 — 1 Щ t( Щ ErrII-TV1 1 E ‘..Il bMNE’ftf’l 1 -..M ‘,I,IFFJ-„JI| t

« ‘ ID 1 JLJLI’iU’L,’-, 1 ‘

‘..M L l-l^Lbûrl Llin : i till hldcLbadiiig 11

Рисунок 9. Общие интерфейсы слоя представления. В категории usersList созданы классы, отвечающие за реализацию

конкретной поставленной задачи — отображению списка людей. Сюда попадают специфичные для android-приложений классы (UsersListAdapter, Ц5ег5Ьл51Ргайтеп1), а также класс презентера и необходимый интерфейс.

в !U sers ListView

. U s ers List Près en ter

Рисунок 10. Классы слоя представления для реализации отображения

В категории data созданы интерфейс репозитория, через который presenter сможет получать данные, а также его реализацию, представленную на рис. 11. _ _ _ _

pacKaqi .ru.giraroig.ШЦузI^JUJJltPJTа import 10.reactivex*Single interface IUsersListRepository

¡Lin crtepr>;ita-|i Kt

£3 E Ш a. ru.ar>dro^. ища ЙЁ LI — iiltport —

Class UserslistRepository @Inject rcnstructor(

privatt val rcsratfiOataStiurce: IRemoteDataSource ) : ILtsersListRepository

P r- vat fun pa rseftsspo rise (response: Result); List < return response.users

Рисунок 11. Классы слоя данных. Как было указано выше, основной логикой обладает слой Presenter. Именно он решает, когда отобразить процесс загрузки данных, ошибки или списка пользователей (рис. 12).

; i 10.reactive*.disposables^CompositeDispcsable import rii. android, my application, data. IUsersListRepository Import ru.android.myeppUcation.presentation,common. schedulersIoToMain

private yal repository: IUsersListRepository ): MvpPresenter()

pr ivate yal disposable — CompositeDisposable!)

vi estate. showLoading!)

val observable = repository, getlisers!> .schedulersIoToMaini) »subscribe!

Рисунок 12. Реализация слоя Presenter. После успешного проектирования и создания приложения его исходный код компилируется и запускается на реальном устройстве.

Рисунок 13. Демонстрация процесса загрузки данных спроектированного приложения.

My Application Мочу

■ 11Ь* ! ы I гдаалйнм fiçAv ш

роят E t. ре пяШмшяр!* еож

игяпгр гщуечгчИ гтжп

• ч*Ат l i ùft/л^лхт tpit. с ùm

Рисунок 14. Демонстрация отображения случайных людей в спроектированном приложении.

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

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

В статье были рассмотрены основы приложений на базе мобильной операционной системы Android, а также архитектурные паттерны, применяемые при их разработке.

Детально был рассмотрен паттерн Model-View-Presenter, выявлены его положительные качества и описан общий подход к проектированию приложений на его основе.

Также было спроектировано приложение, отображающие список случайных людей, в основу которого лег данный паттерн.

1. Дон Гриффитс, Дэвид Гриффитс — «Head First: Программирование для Android»- «Питер», 2016г — 704c.

2. Б. Харди, Б. Филлипс — Android — Программирование для профессионалов — «Питер», 2016. — 637 с.

Расширяемый код Android-приложений с MVP

От переводчика: — я давненько интересуюсь тем, как сделать код Android-приложений чище, и это, наверное, первая статья, после которой у меня не возникло мыслей: «Зачем вот это вот все?» и «Он вообще пробовал когда-то это использовать в жизни?» Поэтому решил перевести, может, еще кому-то будет полезно.

Написать Hello World всегда легко. Код выглядит просто и прямолинейно, и кажется, что SDK очень адаптирована под ваши нужды. Но если у вас есть опыт написания более сложных Android-приложений, вы знаете, что с рабочим кодом все не так. Можно провести часы за попыткой понять, почему ваша корзина покупок не обновляется после изменения ориентации телефона, если недоступен WiFi. Вы предполагаете, что решением проблемы, возможно, будет добавить ещё один if в 457-строчном методе onCreate() вашей активити — где-то между тем кодом, который исправляет падение на самсунгах с Android 4.1 на борту, и тем, который показывает купон на 5$ в день рождения пользователя. Что ж, есть способ получше.

Мы в Remind (прим. пер. — название компании, где работает автор) выкатываем новые функции каждые две недели, и для того чтобы поддерживать эту скорость и высокое качество продукта, нужен способ сохранять код простым, поддерживаемым, разделённым (прим. пер. — «decoupled», в смысле слабой связанности) и тестируемым. Использование архитектурного паттерна MVP позволяет нам делать это и сосредоточиваться на самой значимой части нашего кода — нашей бизнес-логике.

MVP, или Model-View-Presenter, это один из нескольких паттернов, который способствует разделению ответственности при реализации пользовательского интерфейса. В каждом из этих паттернов роли слоев слегка отличаются. Целью этой статьи не является описание отличий между паттернами, а показать, как это можно применить на андроиде (по аналогии с современными UI-фреймворках, такими как Rails и iOS), и как от этого выиграет ваше приложение.

Пример кода, который иллюстрирует большинство подходов, описанных далее, вы можете найти здесь:
https://github.com/remind101/android-arch-sample

Олдскульный Android

Разделение ответственности, которое подразумевается Android-фреймворком, выглядит так: Модель может быть любым POJO, Представление — это XML-разметка, а фрагмент (или изначально активити) выступает в роли Контроллера/Презентера. В теории это работает довольно неплохо, но как только ваше приложение разрастается, в Контроллере появляется много кода, относящегося к Представлению. Все потому, что не так много можно сделать с XML, так что вся привязка данных (дата-биндинг), анимации, обработка ввода и т. д. производится во фрагменте, наряду с бизнес-логикой.

Все становится ещё хуже, когда сложные элементы интерфейса размещаются в списках или гридах (прим. пер. — имеет в виду GridView/GridLayout, да и вообще «сеточные элементы»). Теперь на адаптер ложится ответственность не только хранить код представления и контроллера для всех этих элементов, но и управлять ими как коллекцией. Так как все эти элементы сильно связаны, их становится очень трудно поддерживать и ещё сложнее тестировать.

Вводим Model-View-Presenter

MVP предоставляет нам возможность выделить весь тот скучный низкоуровневый Android-код, который нужен для отображения нашего интерфейса и взаимодействия с ним, в Представление, а более высокоуровневую бизнес-логику того, что наше приложение должно делать, выселить в Презентер.

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

Представление (активити или фрагмент) отвечает за:

  1. Создание экземпляра презентера и механизм его присоединения/отсоединения;
  2. Оповещение презентера о важных для него событиях жизненного цикла;
  3. Сообщение презентеру о входных событиях;
  4. Размещение вьюх и соединение их с данными;
  5. Анимации;
  6. Отслеживание событий;
  7. Переход на другие экраны.
  1. Загрузку моделей;
  2. Сохранение ссылки на модель и состояния представления;
  3. Форматирование того, что должно быть отображено на экране, и указание представлению отобразить это;
  4. Взаимодействие с репозиториями (база данных, сеть и т. д.) (прим. пер. Repository — это паттерн, на всякий случай);
  5. Определение необходимых действий, когда получены входные события от представления.
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(); >

Есть пара интересных моментов, которые стоит рассмотреть в связи с этим интерфейсом:

  • Методы обновления представления должны быть простыми и нацеленными на отдельный элемент. Это лучше, чем иметь один метод setMessage(Message message) , который будет обновлять все, так как форматирование того, что надо отобразить, должно быть ответственностью презентера. Например, в будущем вы захотите отображать «Вы» вместо имени пользователя, если текущий пользователь является автором сообщения, а это является частью бизнес-логики.
  • Методы событий жизненного цикла презентера просты и не должны отображать истинный (переусложненный) системный жизненный цикл. Вы не обязаны обрабатывать какой бы то ни было из них. Но если хотите, чтобы презентер совершал какие-то действия на разных этапах этого цикла, можете обрабатывать в нем столько, сколько считаете нужным.
  • Входные события у презентера должны оставаться высокоуровневыми. Например, если вы хотите определять сложный жест, например, трехпальцевый свайп, это и другие события должны определяться представлением.
  • Можно обратить внимание на методы MessagePresenter.onAuthorClicked() и MessageView.goToAuthorProfile() . Реализация представления, вероятно, будет иметь клик лисенер, который будет вызывать данный метод презентера, а тот в свою очередь будет вызывать goToAuthorProfile() . Не нужно ли убрать все это и переходить в профиль автора непосредственно из клик лисенера. Нет! Решение, переходить ли в профиль пользователя при нажатии на его имя, является частью вашей бизнес-логики, и за это отвечает презентер.

Как только возникнет вопрос тестов, большинство кода, который вам необходимо протестировать, будет в презентере. Что круто, так это то, что этому коду не нужен Android для запуска, так как у него есть только ссылки на интерефейс представления, а не на его реализацию в контексте Android. Это значит, что вы можете просто мокнуть интерфейс представления и написать чистые JUnit-тесты для бизнес-логики, проверяющие правильность вызова методов у мокнутого представления. Вот так теперь выглядят наши тесты.

Как насчёт списков?

До настоящего момента мы предполагали, что наши представления — это активити и фрагменты, но в реальности они могут быть чем угодно. У нас довольно неплохо получилось работать со списками, имея ViewHolder, реализующий интерфейс представления (как RecyclerView.ViewHolder , так и обычный старый ViewHolder для использования в связке с ListView). В адаптере вам всего лишь нужна базовая логика для обработки присоединения/отсоединения презентеров (пример всего этого есть в гит-репозитории).

Если вы посмотрите на пример экрана, содержащего список сообщений, прогресс загрузки и пустую вьюху, разделение ответственности будет следующим:

  • Презентер списка ответственен за загрузку сообщений и логику отображения вьюх списка/прогресса/пустой заглушки;
  • Фрагмент отвечает за реализацию логики отображения вьюх списка/прогресса/заглушки и перехода на другие экраны;
  • Адаптер сопоставляет презентеры их ViewHolder-ам;
  • Презентер сообщения отвечает за бизнес-логику отдельного сообщения;
  • ViewHolder ответственен за отображение сообщения.

Более того, если у вас есть экран списка сообщений и экран подробностей, вы можете переиспользовать тот же презентер сообщения и просто иметь две разные реализации интерфейса представления (во ViewHolder-е и фрагменте). Это сохраняет ваш код DRY (прим. пер. — «Don’t Repeat Yourself», или «Не повторяйтесь», кто не знает).

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

MVP и изменение конфигурации

Если вы уже какое-то время пишете под Android, вы знаете, сколько боли доставляет поддержка смены ориентации и конфигурации:

  • Фрагмент/активити должны уметь восстанавливать свое состояние. Каждый раз при работе с фрагментом вы должны спрашивать себя, как эта штука должна действовать при смене ориентации, что надо поместить в бандл saveInstanceState и т. д.
  • Долгие операции в фоновых потоках очень сложно сделать правильно. Одна из самых популярных ошибок — хранить ссылку на фрагмент/активити в фоновом потоке, так как ему надо обновить UI после завершения работы. Это приводит к утечке памяти (и, вероятно, падению приложения из-за увеличения потребления памяти), а также к тому, что новая активити никогда не получит колбек и, соответственно, не обновит UI.
  • Изначально активити создана (назовем её «первый экземпляр»);
    • Создаётся новый презентер;
    • Презентер привязывается к активити;
    • В презентере запускается долгая операция;
    • Презентер отвязывается от первого экземпляра активити
    • На первый экземпляр активити больше нет ссылок, и теперь она доступна сборщику мусора;
    • Презентер сохранен, фоновая операция продолжается;
    • Создаётся второй экземпляр активити;
    • Второй экземпляр активити привязывается к презентеру.
    • Презентер обновляет представление (второй экземпляр активити).

    Итог

    Да, это конец. Надеюсь, получилось продемонстрировать, как разделение ответственности наподобие MVP поможет вам писать поддерживаемый и тестируемый код.

    • Отделяйте вашу бизнес-логику, вынося её в голый java-объект презентера;
    • Потратьте время на чистый интерфейс между вашими презентерами и представлениями;
    • Пусть ваши активити, фрагменты и кастомные вьюхи реализуют интерфейс представления;
    • Для списков реализовывать интерфейс представления должен ViewHolder;
    • Тщательно тестируйте ваши презентеры;
      • Сохраняйте презентеры при смене ориентации.
      • mvp
      • android
      • android development
      • разработка под android
      • архитектура

      MVP в Android для самых маленьких

      Java-университет

      MVP в Android для самых маленьких - 1

      Когда я начинал свой путь Android-разработчика, слова «Архитектура мобильного приложения» вызывали у меня глубокое недоумение, гугл и статьи на Хабре вгоняли в ещё большую депрессию — смотрю в книгу, вижу фигу. Думаю, если ты читаешь эту статью, то уже не раз изучал эту картинку и пытался понять, что происходит: Проблема понимания архитектурного подхода в мобильной разработке, на мой взгляд, кроется в абстрактности самой архитектуры. У каждого разработчика своё видение того, как правильно реализовать тот или иной паттерн. Более-менее приличные примеры реализации MVP нашлись в англоязычном секторе интернета, что не удивительно. Кратенько разберём, что есть что, и перейдём к примеру. Model — уровень данных. Не люблю использовать термин «бизнес логика», поэтому в своих приложениях я называю его Repository и он общается с базой данных и сетью. View — уровень отображения. Это будет Activity, Fragment или Custom View, если вы не любите плясок с бубном и взаимодействия с жизненным циклом. Напомню, что изначально все Android приложения подчинены структуре MVC, где Controller это Activity или Fragment. Presenter — прослойка между View и Model. View передаёт ему происходящие события, презентер обрабатывает их, при необходимости обращается к Model и возращает View данные на отрисовку. Применительно к Android и конкретному примеру, выделю важную часть — Contract. Это интерфейс, который описывает все взаимодействия между вышеперечисленными компонентами. Резюмируя теоретическую часть:

      • View знает о Presenter;
      • Presenter знает о View и Model (Repository);
      • Model сама по себе;
      • Contract регулирует взаимодействия между ними.

      Собственно, сам пример, для простоты эксперимента будем по нажатию на кнопку загружать строку из БД и отображать в TextView. Например, БД содержит список лучших ресторанов города. Начнем с контракта: Создадим интерфейс MainContract :

       public interface MainContract < interface View < void showText(); >interface Presenter < void onButtonWasClicked(); void onDestroy(); >interface Repository < String loadMessage(); >> 

      Пока что мы просто выделяем 3 компонента нашего будущего приложения и что они будут делать. Далее опишем Repository:

       public class MainRepository implements MainContract.Repository < private static final String TAG = "MainRepository"; @Override public String loadMessage() < Log.d(TAG, "loadMessage()"); /** Здесь обращаемся к БД или сети. * Я специально ничего не пишу, чтобы не загромождать пример * DBHelper'ами и прочими не относяшимеся к теме объектами. * Поэтому я буду возвращать строку Сосисочная =) */ return "Сосисочная у Лёхи»; >> 

      С ним всё понятно, просто загрузка — выгрузка данных. Далее на очереди Presenter:

       public class MainPresenter implements MainContract.Presenter < private static final String TAG = "MainPresenter"; //Компоненты MVP приложения private MainContract.View mView; private MainContract.Repository mRepository; //Сообщение private String message; //Обрати внимание на аргументы конструктора - мы передаем экземпляр View, а Repository просто создаём конструктором. public MainPresenter(MainContract.View mView) < this.mView = mView; this.mRepository = new MainRepository(); Log.d(TAG, "Constructor"); >//View сообщает, что кнопка была нажата @Override public void onButtonWasClicked() < message = mRepository.loadMessage(); mView.showText(message); Log.d(TAG, "onButtonWasClicked()"); >@Override public void onDestroy() < /** * Если бы мы работали например с RxJava, в этом классе стоило бы отписываться от подписок * Кроме того, при работе с другими методами асинхронного андроида,здесь мы боремся с утечкой контекста */ Log.d(TAG, "onDestroy()"); >> 

      Помнишь, я писал про пляски с бубном и жизненный цикл? Presenter живёт до тех пор пока живёт его View, при разработки сложных пользовательских сценариев, советую дублировать все колбеки View в Presenter’e и вызывать их в соответствующие моменты, дублируя ЖЦ Activity/Fragment, чтобы вовремя понять что нужно сделать с теми данными, которые висят в данный момент в «прослойке». И наконец, View:

       public class MainActivity extends AppCompatActivity implements MainContract.View < private static final String TAG = "MainActivity"; private MainContract.Presenter mPresenter; private Button mButton; private TextView myTv; @Override protected void onCreate(Bundle savedInstanceState) < super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //Создаём Presenter и в аргументе передаём ему this - эта Activity расширяет интерфейс MainContract.View mPresenter = new MainPresenter(this); myTv = (TextView) findViewById(R.id.text_view); mButton = (Button) findViewById(R.id.button); mButton.setOnClickListener(new View.OnClickListener() < @Override public void onClick(View v) < mPresenter.onButtonWasClicked(); >>); Log.d(TAG, "onCreate()"); > @Override public void showText(String message) < myTv.setText(message); Log.d(TAG, "showMessage()"); >//Вызываем у Presenter метод onDestroy, чтобы избежать утечек контекста и прочих неприятностей. @Override public void onDestroy() < super.onDestroy(); mPresenter.onDestroy(); Log.d(TAG, "onDestroy()"); >> 
      • Activity, она же View, в методе onCreate() создаёт экзмпляр Presenter и передаёт ему в конструктор себя.
      • Presenter при создании явно получает View и создаёт экзмепляр Repository (его, кстати, можно сделать Singleton)
      • При нажатии на кнопку, View стучится презентеру и сообщает: «Кнопка была нажата».
      • Presenter обращается к Repository: «Загрузи мне вот эту шнягу».
      • Repository грузит и отдаёт «шнягу» Presenter’у.
      • Presenter обращается к View: «Вот тебе данные, отрисуй»

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

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