- Android MVP пример для начинающих. Без библиотек и интерфейсов.
- Что такое MVP
- Практика
- UsersModel
- UserActivity
- UsersPresenter
- Плюсы MVP
- Что дальше?
- Интерфейсы.
- Асинхронные операции
- Создание объектов
- Поворот экрана
- Kotlin + MVP + Dagger 2 + Retrofit = Sample Android Application
- ogulcan/kotlin-mvp-dagger2
- kotlin-mvp-dagger2 — This example application implements MVP architecture using Kotlin, Dagger2, RxJava2 and also…
- What is MVP ? Why do we need?
- Libraries
- Project Structure
- Retrofit and Kotlin Data Classes
- Dependency Injection: Dagger2
- MVP Implementation
- What is Contract?
- What is Presenter?
- What happens step by step once application start?
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 для обсуждения проблем производительности и для ваших пожеланий по содержанию курса по этой теме
Источник
Kotlin + MVP + Dagger 2 + Retrofit = Sample Android Application
I guess, it’s a classic post-title for these days :).
In this article, I would like to show how to implement MVP (Model-View-Presenter) pattern with using Dagger2 and also by Kotlin.
Design Patterns are very remarkable topic for mobile development. And as developers (not just android or mobile developers), we are trying to write cleaner and more testable codes and applications.
For the Android, with MVP, we are able to take most of logic out from the activities (or fragments). So that we can test it without using instrumentation tests. And with Dagger2, it is easier to apply concept of dependency injection.
So, this article/application will cover two design patterns: MVP and Dependency Injection. You may heard typicode, was used as server. It will be abstracted by using Retrofit and RxJava2 will be used to make requests it as observables which is another hot topic for mobile development.
Note: This article is not for starters. It does not answer questions like: What’s dagger or dagger in the past, dagger basics or Retrofit 101 etc.
If you are just interested in project, not the story, here is github repository:
ogulcan/kotlin-mvp-dagger2
kotlin-mvp-dagger2 — This example application implements MVP architecture using Kotlin, Dagger2, RxJava2 and also…
Let’s start with common discussions.
What is MVP ? Why do we need?
In Android we have a problem arising from the fact that activities are closely coupled to interface and data access mechanisms.
The Model-View-Presenter pattern allows to separate the presentation layer from the logic, so that everything about how the interface works is separated from how we represent it on screen.
MVP lets us to make views independent from data source and it will be easier to test each layer.
Libraries
As you may see from the title; I have used dagger2, retrofit, rxjava2 as libraries. I will not go into details about what’s dagger2 or retrofit.
Project Structure
Here is I have implemented MVP in Android. Let’s start with how project structure looks like:
This project structured into 5 packages:
- api: Where Retrofit resides in.
- di: Where dagger2 resides in aka dependency injection.
- models: Data models.
- ui: Activities and also with presenter and contract.
- util: Some tweaks.
After the structure of project, I guess it would be more accurate to tell from the outside to the inside. Means: server → api →dependency injection (app) → ui (mvp).
Retrofit and Kotlin Data Classes
Typicode offers a simple server to us: Posts, users, albums. Depending on typicode, here is how retrofit interface should like:
Unlike Java, Kotlin has a great feature. Companion objects:
In Kotlin, an interface can have a companion object but it is not part of the contract that must be implemented by classes that implement the interface. It is just an object associated to the interface that has one singleton instance. So it is a place you can store things, but doesn’t mean anything to the implementation class.
Another great feature is data classes. It’s really easy to create pojos in Kotlin:
With these three lines (remember, each will be placed in its own separate file) compiler automatically derives the following members from all properties declared in the primary constructor.
Dependency Injection: Dagger2
So I have used an interface to declare api requests and data classes. Next step should be Dagger2 to implement dependency injection.
Dagger uses Components and Modules to define which dependencies will be injected in which objects.
This application will contain an activity (named as MainActivity) and several fragments. So we will need three components: ApplicationComponent, ActivityComponent (where presenter resides in) and FragmentComponent (where presenter and api service reside in).
Here is ApplicationComponent.kt:
It’s quite simple. It just injects application and provides it when needed. Let’s assume, if we want to use Calligraphy, Crashliytcs, Timber or Stetho, application module should inject those, too.
Activity/Fragment Component and Module are also similar to Application. Additionally, provides presenter and api service:
Here is I implemented Dagger on Application scope:
MVP Implementation
As mentioned above, MVP separates views from the logic. So as an initial step there is a BaseContract to abstract Presenter and View:
Since there are three layouts as Main and List and About, there are three contracts: MainContract, ListContact and AboutContract:
What is Contract?
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.
Main screen is an activity that controls fragment layout to show list or about. This is why view has just two functions.
List screen contains a list, as the name implies, that fetches data from server. During the request there will be a progress, if request fails there will be an error message, if request is okay data will be shown. And finally there will be a detail view for each item on the list.
Here is how ListContract looks like:
About view has just a text view that shows example message. Even it’s from xml or remote we should declare functionalities as well:
What is Presenter?
In MVP, 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.
I will just show how List Presenter looks like:
For the last step, here is how to inject ActivityComponent on MainActivity:
What happens step by step once application start?
- On BaseApp: Application Component will be initialized with using Dagger2. API service will be ready.
- On MainActivity: Main Activity is default activity for the application. It contains a frame layout to represent list or about fragments. After dagger injection, view will be attached to presenter.
- On ListFragment: After attachment, list fragment will be shown as default. It means that ListFragment will be shown. View of list fragment has also its presenter. So, posts list will be fetched from remote server.
- On AboutFragment: If user taps option, about fragment will be shown with animation.
Thank you so much for coming so far!
If you want to know more about this sample application please see github repository.
If you liked this article, please share. So people can also read it.
Please get in touch with me via Github, Twitter or LinkedIn.
Источник