MVP в Android на примере экрана с пин кодом

MVP в Android на примере экрана с пин кодом

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

Таким образом, экран пин кода должен поддерживать три режима: создание, проверка и смена пин кода. Реализовав этот экран, я понял, что получился неплохой пример для демонстрации принципов MVP (Model-View-Presenter).

Если вы пока не очень хорошо представляете себе, что такое MVP, то посмотрите сначала этот пост. Он проще и понятнее.

Пример

Напомню, что у экрана с пин кодом может быть три режима.

Соответственно, на экране может быть одно, два или три поля ввода. Каждое поле содержит подпись (label) и имеет ограничение по длине в 4 символа.

При реализации этого экрана я решил использовать одно View и три презентера - отдельный презентер для каждого из трех режимов.

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

Пример я оформил отдельным проектом и залил на GitHub. В проекте используются ButterKnife, Dagger 2 и RxJava. Я не буду подробно останавливаться на объяснении принципов их работы. Если вы еще не знакомы с этими инструментами, то я вам рекомендую изучить их. Они сейчас востребованы и активно используются.

Основные компоненты приложения.

Класс по работе с префами. Используется для чтения/записи пин кода.

Здесь описаны Enum для режимов экрана и текстовый ключ, для помещения данных в intent.

Это техническое Activity, без layout. С него стартует приложение.

Из префов читается текущий пин код. Если он задан, то запускается PinCodeActivity в режиме проверки пин кода, иначе - в режиме создания пин кода. И на этом работа Activity завершается.Если у вас есть Activity с заставкой, можно эту логику поместить в него.

Это основной экран приложения.

Содержит кнопки для сброса и изменения пина.

Рассмотрим классы/интерфейсы, принимающие непосредственное участие в MVP.

Базовые классы и интерфейсы MVP

Интерфейс, который будет реализован всеми View, которые будут работать по MVP. В нашем случае интерфейс пустой.

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

attachView(V mvpView) - метод для передачи View презентеру. Т.е. View вызовет его и передаст туда себя.

viewIsReady - сигнал презентеру о том, что View готово к работе. Презентер может начинать, например, загружать данные.

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

destroy - сигнал презентеру о том, что View завершает свою работу и будет закрыто. Т.е. Здесь необходимо отписываться от всех моделей, завершать все текущие операции и т.п.

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

Переходим к реализации MVP на примере экрана для работы с пин кодами.

Contract

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

В этом файле описаны интерфейсы для View и Presenter для экрана, который будет работать с пин кодом.

Полей для ввода пин кода будет всего три, поэтому названия методов содержат порядковые числительные: first, second, third.

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

В интерфейсе презентера описаны методы, которые будут вызываться, когда соответствующее поле будет полностью заполнено (когда пользователь ввел 4 символа).

Класс PinCodeActivity.java реализует интерфейс PinCodeContract.View, т.к. это Activity играет роль View в MVP схеме.

Рассмотрим некоторые фрагменты кода.

Методы запуска Activity:

Общий метод startActivity создает Intent для запуска PinCodeActivity и помещает в интент параметр - режим запуска. Методы createPinCode, checkPinCode, changePinCode используют метод startActivity и соответствующий режим.

В onCreate вытаскиваем режим PinCodeMode из интента и используем его для создания презентера.

Для создания презентера тут используется даггер. И код, который он выполняет, выглядит так:

В зависимости от режима, мы создаем один из трех презентеров. Это три разных класса, но все они реализуют интерфейс PinCodeContract.Presenter.

А в PinCodeActivity даггер помещает созданный презентер в поле:

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

Далее в onCreate мы даем созданному презентеру View (оно же Activity) и сообщаем, что все готово к работе.

Методом detachView мы просим presenter освободить View. А если Activity закрывается насовсем, то выполняем destroy для презентера и освобождаем компонент даггера.

Далее идет реализация методов интерфейса PinCodeContract.View

Ставим label текст в TextView и показываем его и само поле. Далее с помощью RxJava вешаем обработчика для поля ввода, который будет срабатывать при вводе символов в поле. Добавляем фильтр, чтобы обработчик срабатывал только, когда поле содержит 4 символа (т.е. полностью заполнено). И указываем, что обработчику необходимо будет вызвать метод presenter.onTextFirst().

Тем самым мы сообщим презентеру, что пользователь ввел 4 символа в поле и надо что-то делать дальше.

Методы showSecond и showThird полностью аналогичны.

Методы getTextFirst, getTextSecond и getTextThird просто возвращают содержимое полей ввода.

Методы focusFirst, focusSecond, focusThird ставят фокус в поля ввода.

clearAll очищает все поля ввода.

showMessage показывает Toast.

next запускает MainActivity.

close закрывает Activity.

Presenter

Давайте смотреть код презентеров и там станет понятно, зачем нужны все эти методы в Activity.

Создание пин кода

Обратите внимание, что класс презентера наследует PresenterBase (чтобы иметь базовые методы работы с View) и реализует PinCodeContract.Presenter (чтобы соответствовать требованиям контракта).

Этот презентер используется, когда мы открываем экран в режиме создания пин кода

Нам нужно отобразить два поля из трех. Это мы и делаем в методе viewIsReady. Для доступа к View используем getView из базового класса презентера.

Метод onTextFirst будет вызван из View, когда первое поле будет заполнено. В этом случае мы переводим фокус на второе поле методом focusSecond.

Метод onTextSecond будет вызван из View, когда второе поле будет заполнено. В этом случае мы сверяем значения из первого и второго полей.

Если значения равны, то мы - сохраняем новый пин- показываем сообщение, что пин успешно создан- открываем следующий экран- закрываем текущий экран

Если же значения не равны- показываем сообщение о том, что пины не совпадают- очищаем все поля- ставим фокус в первое поле

В методе onTextThird ничего не делаем. Он не будет вызван, т.к. мы не отображали третье поле ввода.

Проверка пин кода

Он используется при режиме проверки пин кода

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

Когда пользователь введет пин код в первое поле, будет вызван метод onTextFirst. В нем проверяем, что введенное значение совпадает с пин кодом который хранится в префах.

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

Иначе:- показываем сообщение об ошибке- очищаем все поля

Методы onTextSecond и onTextThird остаются пустыми. Они не будут вызваны из View, т.к. мы не отображали второе и третье поля.

Изменение пин кода

Используется в режиме смены пин кода.

Здесь мы отображаем все три поля в методе viewIsReady.

В методах onTextFirst и onTextSecond просто переводим фокус на следующее поле.

В методе onTextThird сначала проверяем, что старый пин введен верно в первое поле. Если пин не совпадает с текущим сохраненным, то - показываем сообщение об ошибке- очищаем все поля- ставим фокус на первое поле- заканчиваем работу метода

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

Если значения совпадают, то- сохраняем новый пин- показываем сообщение об успехе- закрываем экран

Иначе- показываем сообщение об ошибке- очищаем все поля- ставим курсор на первое поле

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

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

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

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

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

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

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

- новый чат Performance для обсуждения проблем производительности и для ваших пожеланий по содержанию курса по этой теме

📎📎📎📎📎📎📎📎📎📎