Android pin code screen

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, и далее в работе используете именно их. В этом случае, вы всегда сможете подменить одну реализацию презентера (или экрана) другой без каких-либо последствий.

Читайте также:  Open docx file android

В этом файле описаны интерфейсы для 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 для обсуждения проблем производительности и для ваших пожеланий по содержанию курса по этой теме

Источник

Как я писал кастомный локер

Привет хабрастарожилам от хабрановичка. Ровно год назад я решил написать кастомный локер (экран блокировки) для моего старичка Samsung Galaxy Gio в стиле популярного тогда Samsung Galaxy s3. Какие причины заставили меня это сделать, писать не буду, но добавлю лишь то, что в Google Play я программу не собирался выкладывать и каким-либо другим способом заработать на ней не планировал. Данный пост посвящен последствиям моего решения.

Начну издалека. Многие хвалят Android за открытость и возможность заменить и настроить встроенные программы под свои нужды. Что тут сказать? В сравнении с другими популярными ОС, это, безусловно, так, но если копнуть глубже в архитектуру Android возникают трудности и вопросы. Локскрин (в Android это называется keyguard) как раз и вызывает вопросы: почему Google не поступили с ним, так как с лаунчерами, почему не сделали диалог со всеми доступными на устройстве локерами и с возможностью выбрать нужный по умолчанию? Где-то в глубине мозга тихим нерешительным голосом кто-то отвечает: может быть Google (Android Ink. если быть точнее) поступил так из соображений безопасности. Этот голос вероятно прав и многим разработчикам локеров и мне (скромность не позволила приписать себя к их числу) пришлось изобретать велосипед, и не один.

Изучаем исходники

Начал я с использования одного из плюсов Android – из изучения исходников. Я один из тех консерваторов, которые уже 2,5 года сидят на стоковой прошивке (2.3.6), поэтому и исходники изучал соответствующие. Классы, отвечающие за блокировку экрана, лежат в android.policy.jar, что в system/framework. Первоначальной целью было найти «точку входа», т.е. где и когда вызывается локер. Искал здесь.

Читайте также:  Не работает разделение экрана android 10

В классе PhoneWindowManager.java есть метод screenTurnedOff(int why), который вызывает одноименный метод класса KeyguardViewMediator. Проследив, кто кого вызывает, я нашел метод в классе KeyguardViewManager, создающий непосредственно View стокового локера.

Что ж, все гениальное – просто. Решил повторить этот код для своего приложения и получил ошибку – нет нужного permission. Немного погуглив, добавил следующие разрешения: SYSTEM_ALERT_WINDOW и INTERNAL_SYSTEM_WINDOW. Это не помогло.

Вернулся к изучению класса PhoneWindowManager.java:

Для требуемого окна TYPE_KEYGUARD нужно второе из моих добавленных разрешений. Задней точкой тела начал ощущать, что не все так просто, как я себе представлял. Решено было посмотреть на описание этого permission. Вот выдержка из AndroidManifest.xml пакета framework-res.apk.

Вот она – черная полоса в жизни. Ведь я понимал, «signature» – это значит, что использовать этот пермишн может только пакет, подписанный тем же ключом, что и пакет, выдавший это разрешение (в нашем случае — framework-res.apk). Ладно, достаем инструменты для изготовления велосипедов.

Версия один

Первым решением было использовать activity в качестве локскрина. На stackoverflow советуют использовать следующий код:

Признаюсь, в первых версиях я использовал этот метод. У него есть существенные недостатки: статусбар не блокируется, начиная с версии API11 этот метод не работает.

Решение первого недостатка (переполнениестека опять помогло) следующее. Поверх статусбара с помощью WindowManager рисуется прозрачный View, который перехватывает все TouchEvent. Вот служба, реализующая это:

Второго недостатка для меня не существовало, на Gingerbread данный код работал превосходно. На 4pda, куда я опрометчиво выложил свое творение, пользователи жаловались, что на многих телефонах мой локер сворачивался как обычное приложение. Для них найдено такое решение. В качестве стандартного лаунчера устанавливается пустышка. При нажатии кнопки HOME система вызывает мой лаунчер-пустышку. Если кастомный локер активен, лаунчер сразу же закрывается в методе onCreate(), т.е. визуально нажатие кнопки HOME ни к чему не приводит. Если кастомный локер не активен, мой лаунчер тут же вызывает другой правильный лаунчер, который пользователь указал в настройках.

Вот код пустышки:

Выглядело это следующим образом:

Эти велосипеды ездили долго и хорошо, пока я не решил сделать «правильный» локскрин, и уже в стиле Samsung Galaxy S4.

Версия два

Когда системе необходимо запускать кастомный локер? Очевидно, что при выключении экрана. Создадим службу, регистрирующую BroadcastReceiver, т.к. из манифеста данный фильтр не работает.

Необходимо учесть две особенности:

1. Служба должна быть запущена в момент загрузки устройства. Создадим BroadcastReseiver с IntentFilter «android.intent.action.BOOT_COMPLETED». Есть одно НО: служба при запуске должна отключить стандартную блокировку экрана. Особенностью Android является то, что стандартное окно ввода PIN-кода является частью стокового экрана блокировки. Поэтому служба должна запускаться только когда PIN буден введен.

Максимум, на что хватило моей фантазии:

2. Проанализировав PhoneWindowManager видно, что в метод screenTurnedOff(int why) передается переменная why, принимающая 3 значения:
— экран выключился по истечению таймаута (в этом случае стоковый локер запускается с задержкой),
— экран выключился при срабатывании сенсора приближения (во время телефонного разговора),
— экран выключился при нажатии кнопки.
В моем случае такого разнообразия нет. Поэтому служба мониторит состояние телефона, и при входящем звонке или во время разговора экран не блокируется.

Вот основной код службы:

Идея не использовать activity, а использовать WindowManager была еще сильна. Из пяти типов окон, использующих разрешение SYSTEM_ALERT_WINDOW, мне подошел TYPE_SYSTEM_ALERT. Причем у него были очевидные достоинства: блокировался статусбар (по крайней мере, на Gingerbread) и перехватывалось нажатие кнопки HOME (работает даже на Jelly Bean).

Промежуточным звеном между службой и KeyguardView является класс KeyguardMediator:

Дальше история становится менее интересной, так сказать, будничной. На мой локер можно добавлять ярлыки приложений (здесь все стандартно и просто) и виджеты (а вот этот момент достоин отдельной статьи).

Теперь все стало выглядеть современней:

Источник

Оцените статью