- Android MVP пример для начинающих. Без библиотек и интерфейсов.
- Что такое MVP
- Практика
- UsersModel
- UserActivity
- UsersPresenter
- Плюсы MVP
- Что дальше?
- Интерфейсы.
- Асинхронные операции
- Создание объектов
- Поворот экрана
- MVP и Dagger 2 – скелет Android-приложения – часть 1
- 1.1 Abstracts
- 1.2. Компоненты
- 1.3. Заключение первой части
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
Добавить этот интерфейс в UsersActivity
Теперь в презентере можно убрать все упоминания о UsersActivity, и оставить только UsersContractView.
Плюс такого похода в том, что теперь в этом презентере вы можете использовать любое представление, которое реализует интерфейс UsersContractView. И вам не придется ничего менять в презентере.
А если презентер завязан на конкретный класс, например, UsersActivity, то при замене представления, вам придется открыть презентер и поменять там UsersActivity на другой класс.
Асинхронные операции
Для реализации асинхронности я здесь использовал AsyncTask. Но не помню, когда последний раз использовал его в рабочем коде. Обычно используются различные библиотеки, которые удобнее в использовании, гибче и дают больше возможностей. Например — RxJava.
Создание объектов
В UsersActivity мы создаем презентер следующим образом:
Это не очень хорошая практика. Рекомендуется не создавать объекты внутри вашего класса, а получать их уже готовыми снаружи. Для реализации этого принципа существуют различные библиотеки. Самый распространенный пример — это библиотека Dagger 2.
Поворот экрана
В этом примере нет обработки поворота экрана. Если ваш презентер выполняет какую-то долгую операцию, и вы повернете экран, у вас просто создастся новый презентер, а результаты работы старого презентера могут быть потеряны.
Есть различные способы, как этого избежать. Один из них — не пересоздавать презентер, если представление пересоздается. При этом, презенетер отпускает старое представление (метод detachView), и получает новое представление (метод attahcView). В итоге, результаты работы долгой операции будут отображены уже в новом представлении.
Если тема MVP стала вам интересна, то посмотрите этот пример. Он посложнее и более приближен к реальному коду.
Присоединяйтесь к нам в Telegram:
— в канале StartAndroid публикуются ссылки на новые статьи с сайта startandroid.ru и интересные материалы с хабра, medium.com и т.п.
— в чатах решаем возникающие вопросы и проблемы по различным темам: Android, Kotlin, RxJava, Dagger, Тестирование
— ну и если просто хочется поговорить с коллегами по разработке, то есть чат Флудильня
— новый чат Performance для обсуждения проблем производительности и для ваших пожеланий по содержанию курса по этой теме
Источник
MVP и Dagger 2 – скелет Android-приложения – часть 1
Данная статья нацелена на новичков в Android-разработке и призвана помочь в создании минимально необходимой структуры приложения.
Так получилось, что я относительно недавно начал программировать под Android – после месяца без проекта в компании, где я работаю, меня определили в команду мобильной разработки в уругвайском офисе Tata Consultancy Services. При беседе с тимлидом команды мне был озвучен стек, с которым мне предстояло сначала ознакомиться, а затем и овладеть. В числе прочего был фреймворк Dagger 2 для DI и MVP в качестве архитектурного паттерна. И Kotlin. Но о нем в другой раз 🙂
Таким образом, я приступил к изучению сначала основы Android SDK, а затем и всего сопутствующего стека. С самим SDK проблем не возникло – исчерпывающей информации по нему в сети более чем достаточно, начиная с официальной документации и заканчивая туториалами (особенно с этим помог проект startandroid), но с Dagger 2 и MVP применительно к Android-разработке возникли некоторые затруднения ввиду довольно куцей документации первого и, на тот момент, недостаточного понимания второго. Дело в том, что до мобильной разработки я делал микросервисы на Java с использованием Spring Boot/MVC и уже имел достаточное представление и о том, что такое Dependency Injection, и о том, что такое MVC. При том, даже само название “Spring MVC” предполагает, что этот паттерн заложен в архитектуру проекта и его использование очевидно. От Dagger 2 я ожидал как такой же как в Spring “магии” и настолько же проработанной документации и туториалов. И обломался 😛
Тем не менее, при достаточном упорстве и усидчивости, нет ничего невозможного, а результатом изысканий стало осуществление моей давней идеи, возникшей еще в те времена, когда об Android-разработке я и не помышлял. Оценить идею вы можете, установив приложение из Google Store
В данной статье я хотел бы представить сухую выжимку результата моих поисков – пошаговое руководство по созданию скелета Android-приложения с использованием MVP и Dagger 2. Итак, начнем.
1.1 Abstracts
Для начала, создадим пакет abstracts в корне проекта, пусть это будет com.caesar84mx.mymvcapp.abstracts. В нем создадим 2 интерфейса: view.BaseView и presenter.BaseMvpPresenter. Следующим образом:
Это базовые архитектурные элементы, которые в дальнейшем будут использоваться в приложении. Далее, открываем BaseView и объявляем в него методы showView() getContext():
Теперь открываем BaseMvpPresenter и редактируем его следующим образом:
В пакете view создаем абстрактный класс BaseCompatActivity, наследуем его от AppCompatActivity и имплементируем недавно созданный интерфейс BaseView. Внутри класса объявляем абстрактный метод init(savedInstanceState: Bundle?) и имплементируем метод getContext() из BaseView:
От этого класса в дальнейшем мы будем наследовать все активности.
Теперь перейдем к презентеру – создадим класс BasePresenter, имплементирующий интерфейс BaseMvpPresenter и реализуем методы интерфейса следующим образом:
Отлично, базовые архитектурные элементы мы определили, теперь перейдем к компонентам, из которых будет строиться наше приложение.
1.2. Компоненты
Для начала, создадим пакет com.caesar84mx.mymvcapp.components, в нем пакет mainscreen, в котором, в свою очередь, пакеты ui и backstage, и перенесем в пакет ui класс MainScreen:
Теперь удалим из класса MainScreen автоматически сгенерированную при создании проекта имплементацию метода onCreate(), а также, наследование от AppCompatActivity и унаследуем его от BaseCompatActivity. Теперь реализуем метод init(), ранее объявленный в базовом классе. Весь код, который мы раньше поместили бы в метод onCreate(), мы поместим в него (как мы помним, метод init() вызывается в методе onCreate() базового класса):
Великолепно, элемент view паттерна MVP создан, теперь перейдем в закулисье нашего компонента – пакет backstage. Создадим интерфейс MainScreenContract – так называемый контракт, через который мы и будем реализовывать наш паттерн. В данном интерфейсе создадим 2 подинтерфейса — Presenter и View:
Теперь, перейдем к презетнеру и создадим класс MainScreenPresenter:
Скелет приложения почти готов, осталось несколько штрихов. В класс MainScreen добавим имплементацию интерфейса MainScreenContract.View, создадим и проинициализируем переменную presenter: MainScreenPresenter, а в методе init() присоединим вид к презентеру следующим образом:
Таким образом, мы создали презентер и добавили в него наш экземпляр view (не путать с android.view.View), который в презентере будет использоваться для манипуляций с видом.
1.3. Заключение первой части
Итак, мы создали базовые абстрактные элементы паттерна MVP, которые, однако, используются не напрямую, в лоб, а через т.н. контракт – базовый элемент каждого компонента приложения, который сочетает в себе как действия элемента view, так и действия элемента presenter. Контракт – достаточно гибкий элемент, состав которого варьирует от компонента к компоненту, ненавязчиво увязывая компоненты в рамках единой архитектуры.
Следует помнить, что в соответствии с концепцией MVP, элемент view должен быть максимально тупым, в нем мы производим только элементарные действия, такие, как, например, показать/спрятать текст, поменять фон или цвет текста, показать/спрятать значок загрузки и т.д. Методы, соответствующие этому элементу, мы определяем в подинтерфейсе View контракта. В то время, как логикой мы занимаемся в презентере – бизнес-логика, манипуляции данными (CRUD), запуск фоновых задач и т.д. В нем же мы решаем, когда и показать те или иные элементы на экране. В этом отличие от реализованной в спринге концепции MVC, где между бизнес-логикой и видом находится тупой контроллер, который только получает запросы от вида и вызывает сервис, который возвращает данные или выполняет иные действия, определенные бизнес-логикой. Методы, соответствующие презентеру, мы определяем в подинтерфейсе Presenter контракта.
При реализации презентера, манипуляции видом будут производиться через переменную view суперкласса BasePresenter, в то время, как методы, соответствующие view реализуются в классе активности.
Вы спросите, а где здесь Dagger 2 и зачем он нам сдался, не будет ли реализация DI в Android натягиванием совы на глобус? Ответ на второй вопрос – нет, не будет. А почему и зачем он нужен – во второй части моей статьи 😉
Источник