Урок 14. Навигация по условию в андроид приложении. Android Conditional Navigation & Firebase Authentication

Продолжаем серию уроков по разработке android-приложений в Android Studio на языке Kotlin. Пришло время познакомиться с Android Conditional Navigation и Firebase Authentication.

На прошлом уроке мы научились работать с библиотекой Navigation Architecture Component, которая позволяет пользователям перемещаться между различными экранами и расположениями контента в андроид-приложении. Мы интегрировали навигацию в проект, добавили пункты назначения и переходы между ними.

На этом уроке

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

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

Для аутентификации пользователей воспользуемся сервисом Firebase Authentication, который позволяет логиниться по email и паролю, а также поддерживает много других способов аутентификации.

Кроме Firebase, также будем использовать в проекте актуальные библиотеки из набора Android JetPack: Data Binding, View Model, Live Data, которые мы рассматривали на прошлых уроках. Таким образом, мы будем использовать правильный подход для построения архитектуры приложения с точки зрения быстродействия, стабильности и корректной обработки изменений конфигурации.

Создаем проект

Откройте среду разработки Android Studio и создайте новый проект с использованием шаблона Empty Activity.

Откройте среду разработки Android Studio и создайте новый проект с использованием шаблона Empty Activity.

Строковые ресурсы

В папке res/values откройте файл strings.xml и добавьте туда такие строки:

Эти строки будут служить текстом надписей для кнопок и полей.
Обратите внимание на строку «welcome_message» — она содержит символы %1$s. Это выражение позволяет в данном случае заменить его в коде первым переданным параметром (строковой переменной). Мы будем использовать это в коде класса LoginFragment для подстановки в строку приветствия имени пользователя.

Пункты назначения и NavGraph

Добавление библиотеки Android Navigation в проект

Для поддержки пользовательской навигации нужно добавить в проект библиотеки androidx.navigation:navigation-fragment-ktx и androidx.navigation:navigation-ui-ktx актуальных версий.

Для этого добавьте в файл build.gradle модуля app в секцию dependecies:

Создание графа навигации

Далее перейдите в папку res и создайте в ней папку navigation.  Внутри папки navigation создайте Navigation Resource File с именем nav_graph.xml и корневым элементом <navigation>.

Добавление фрагментов — пунктов назначения

Добавьте новые пункты назначения. Для этого:

  1. В окне редактора дизайна нажмите кнопку «New destination»
  2. Выберите «Create new destination»
  3. Далее в окне добавления фрагмента выберите Fragment (Blank):

Далее в окне добавления фрагмента выберите Fragment (Blank):

Создайте таким образом три фрагмента:

  • MainFragment
  • LoginFragment
  • AccountFragment

У вас в окне дизайна редактора ресурсов должно добавиться три фрагмента:

У вас в окне дизайна редактора ресурсов должно добавиться три фрагмента:

А в файле nav_graph.xml должен появиться такой код:

На этом создание графа навигации завершено. Обратите внимание – в отличие от прошлых уроков, мы не добавляем здесь никаких секций <action> и не задаем переходов между фрагментами. Переходы по пунктам назначения мы пропишем непосредственно в коде классов экранов в зависимости от условий.

А чтобы фрагменты из графа навигации отображались на экране, нужно добавить в макет главного активити activity_main.xml компонент fragment — хост навигации:

 

DataBinding

Подключение DataBinding в android-проект

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

  1. Откройте файл сборки build.gradle модуля app
  2. В секции plugins {…} добавьте строку id ‘kotlin_kapt’
  3. В секции android {…} добавьте строку dataBinding { enabled = true }.
  4. Синхронизируйте файл сборки с Gradle.

Макеты разметки экранов

Теперь займемся файлами разметки макетов.

В папке res/layout откройте файл fragment_main.xml. Удалите текстовое поле добавьте кнопку.  Приведите файл к такому виду:

Как видим, здесь корневой элемент – это <layout>, который обеспечивает поддержку макетом разметки функции Data Binding.  Все директивы xlmns, соответственно, переместились в корневой элемент. Также здесь добавлена кнопка входа в личный кабинет.

Аналогично меняем макеты остальных фрагментов. Макет fragment_login.xml:

Здесь кнопка аутентификации и текстовое поле с приглашением.

И макет fragment_account.xml:

Здесь текстовое поле, отображающее приветствие, и кнопка выхода из личного кабинета.

 

Firebase Authentication

Создание проекта в Firebase

Для работы с Firebase Authentication нужно создать новый проект в консоли разработчика на сайте https://console.firebase.google.com/

  1.  В консоли Firebase нажмите «Добавить проект».
  2. Выберите или введите имя проекта.
  3. Нажмите «Продолжить».
  4. Настройку Google Analytics можно пропустить.
  5. Нажмите «Создать проект», чтобы завершить настройку проекта FireBase.

Регистрация приложения в Firebase

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

  1. Откройте созданный проект и Нажмите «Добавить приложение».
  2. Выберите платформу «Android»
  3. Откроется такое окно:Регистрация приложения в Firebase
  4. В поле «Название пакета Андроид» укажите имя пакета вашего приложения.
    Его можно скопировать в файле сборки gradle, ищите параметр applicationID в секции android { … defaultConfig { … } …}.
  5. По желанию укажите псевдоним для приложения.
  6. Далее укажите хеш сертификата для отладки – его можно получить в Android Studio на вкладке Gradle правой панели. Выберите пункт Tasks\android\signingReport и, через некоторое время, внизу в открывшейся вкладке Run скопируйте ключ SHA1:хеш сертификата для отладки – его можно получить в Android Studio на вкладке Gradle правой панели. Выберите пункт Tasks\android\signingReport и, через некоторое время, внизу в открывшейся вкладке Run скопируйте ключ SHA1
  7. Вставьте ключ в поле формы регистрации приложения на сайте Firebase.
  8. Нажмите кнопку «Зарегистрировать приложение».
  9. После успешной регистрации станет доступен файл конфигурации google-service.json. Его нужно скачать и сохранить в папке app вашего проекта:файл конфигурации google-service.json. Его нужно скачать и сохранить в папке app вашего проекта.
  10. Добавьте Firebase SDK в проект путем изменения файлов gradle вашего проекта по инструкции, которая откроется на следующем шаге:

Файл build.gradle уровня проекта (<project>/build.gradle):

Файл build.gradle уровня приложения (<project>/<app-module>/build.gradle):

 

  1. Синхронизируйте проект с Gradle.

Регистрация приложения завершена. Откройте проект, и вы увидите его в списке приложений.

Включение аутентификации Firebase в андроид-приложении

Для подключения аутентификации в консоли Firebase нужно выполнить следующие шаги:

  1. В панели вашего проекта слева перейдите в раздел «Authentification»
  2. Выберите вкладку «Sign-in method»
  3. В списке «Провайдеры авторизации» включите 2 вида авторизации —  Адрес электронной почты и пароль, и Google:3. В списке «Провайдеры авторизации» включите 2 вида авторизации - Адрес электронной почты и пароль, и Google.

При подключении авторизации от Google нужно также указать общедоступное название проекта, которое будут видеть пользователи, например, в письмах подтверждения авторизации, и email поддержки вашего проекта. Здесь я указал свой email.

  1. Вернитесь в Android Studio и в файле build.gradle модуля app добавьте библиотеки для работы с Firebase аутентификацией:

 

LiveData

Теперь переходим непосредственно к написанию кода.

Реализуем в нашем приложении класс, который позволит подписываться на события аутентификации пользователей вашего приложения. Будем использовать класс LiveData, который является хранилищем данных и реализует паттерн Наблюдатель (Observer). Таким образом, можно подписаться на данные, хранящиеся в LiveData.

Создайте класс FirebaseUserLiveData, наследник LiveData с параметризированным типом <FirebaseUser>.

Класс FirebaseUser предоставляет информацию о зарегистрированных пользователях.

Инициализируем переменную для инстанса класса FirebaseAuth, который является точкой входа в Firebase Authentication SDK.

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

Переопределенные методы onActive и onInactive срабатывают при изменении количества активных подписчиков. Метод onActive будет вызван, когда у LiveData появится хотя бы один подписчик. А onInactive — когда не останется ни одного подписчика.  В первом мы будем подключать, а во втором – отключать слушатель для экономии ресурсов.

 

ViewModel

Для получения данных из LiveData будем использовать ViewModel. Этот класс обеспечит сохранность данных при изменениях конфигурации, например, повороте устройства.

Создайте класс LoginViewModel, унаследуйте его от класса ViewModel.

Объявим класс-перечисление AuthenticatioState для разных состояний пользователя – аутентифицирован, неаутентифицирован, и ошибка аутентификации.

Далее создадим переменную authenticationState, которая, используя объект ранее созданного нами класса FirebaseUserLiveData будет получать и хранить состояние аутентификации пользователя при любых изменениях этого состояния.

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

Чтобы маппинг корректно работал здесь, добавьте такие зависимостти в файл сборки build.gradle (app):

 

MainFragment

В теле класса фрагмента MainFragment удалите ненужный код. Объявите переменные для LoginViewModel и binding.

Переменную viewModel инициализируем через viewModels — делегированное свойство для доступа к ViewModel, по умолчанию привязанное к текущему фрагменту. Таким образом, при пересоздании фрагмента ViewModel не будет пересоздан.

В методе onCreateView при помощи DataBindingUtil привязываем файл макета разметки к этому фрагменту. Посредством переменной binding мы сможем обращаться ко всем элементам разметки экрана.

Создаем функцию observeAuthenticationState, в которой подписываемся на данные о состоянии аутентификации пользователя. Метод observe добавляет наблюдателя в список наблюдателей в пределах жизненного цикла текущего фрагмента. Наблюдатель получает данные из LiveData и мы на основе этих данных строим цикл when. Здесь, если пользователь аутентифицирован, обрабатываем нажатие кнопки, по которому отправляем его на экран личного кабинета. Если пользователь не аутентифицирован, то по нажатию кнопки отправляем его на экран входа.

 

Функцию observeAuthenticationState будем вызывать в переопределенном методе жизненного цикла onViewCreated, который вызывается непосредственно перед отображением фрагмента на экране.

Если мы повернем экран, то активити и фрагмент в нем будет пересоздан. Но LiveData и ViewModel будут активны и сразу передадут актуальные данные подписанному наблюдателю.

 

LoginFragment

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

Создадим вспомогательный объект и объявим в нем две константы. Первая – TAG – будет использоваться в качестве тега для логирования (подробнее об этом здесь https://www.fandroid.info/urok-12-logcat-logi-prilozheniya-isklyucheniya-exception-obrabotka-oshibok-v-kode-android-studio/).

Вторая константа — SIGN_IN_RESULT_CODE — для кода, который будет использоваться в вызове startActivityForResult. Её значение – просто произвольное число. Мы будем стартовать активити для аутентификации, передавая туда эту константу, с ожиданием результата. Результат будет приходить в наш текущий фрагмент, а идентифицировать его мы будем по этой самой константе.  Более подробно об этой технологии можно посмотреть здесь https://www.fandroid.info/urok-29-vyzov-vtorogo-activity-s-vozvrashheniem-dannyh-uroki-android-studio/

Далее объявим и инициализируем переменную viewModel, аналогично, как мы это делали в классе MainFragment.

Также объявим пременную navController для контроллера навигации, с ленивой инициализацией.

Далее в методе onCreateView подключаем binding и инфлейтим макет разметки фрагмента. Через binding обращаемся к кнопке и назначаем ей слушатель. В слушатель пока ничего не пишем.

Создаем функцию launchSignInFlow() для запроса аутентификации. Создаем переменную providers для списка провайдеров аутентификации. Как вы помните, в настройках проекта в консоли Firebase мы выбрали два провайдера аутентификации – по email и паролю, и Google.  Соответственно, вызываем здесь два билдера через Firebase классы AuthUI.IdpConfig  для каждого провайдера.

Далее в методе startActivityForResult получаем экземпляр класса AuthUI, с помощью которого создаем экран подписки с выбором провайдеров из созданного нами ранее списка. В конце передаем константу SIGN_IN_RESULT_CODE. Вызов функции launchSignInFlow() будем выполнять по нажатию кнопки входа, пропишем ее в слушателе.

Таким образом, методом startActivityForResult мы запускаем активити с выбором способов авторизации. Такой способ запуска гарантирует возврат какого-то результата. Результатом будет ответ от Firebase о том, аутентифицирован пользователь или нет.

Получать и обрабатывать результат от Firebase будем в текущем фрагменте. Для этого нужно переопределить метод onActivityResult, который принимает такие параметры:

  • requestCode – это наша константа, которую мы передали в startActivityForResult;
  • resultCode – это код результата, успешно или нет;
  • data – Intent с данными.

Проверяем, что requestCode соответствует коду нашей константы, и извлекаем данные из полученного интента методом IdpResponce.fromResultIntent. Далее проверяем —  если результат успешен, то пишем в логи сообщение об успешной аутентификации пользователя с добавлением имени пользователя, полученном через класс FirebaseAuth. В противном случае отображаем в логах ошибку, извлеченную из интента.

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

 

Инициализируем контроллер навигации.

Нам нужно переопределить поведение системной кнопки «Назад», чтобы при ее нажатии пользователь всегда возвращался на главный экран с кнопкой «Личный кабинет», а не уходил в бесконечную карусель экранов.  Реализуем это через onBackPressedDispatcher. В его функцию addCallback передаем текущий фрагмент и вызываем контроллер навигации с передачей в его бэк стек фрагмента MainFragment. Таким образом, нажатие кнопки «Назад» будет вести на MainFragment. Подробнее об этой технологии смотрите здесь: https://www.fandroid.info/urok-25-task-i-backstack-aktiviti-android-prilozhenij-uroki-android-studio/

Дальнейший код похож на код получения состояния пользователя в главном фрагменте. Мы подписываемся на состояние аутентификации пользователя. И если пользователь залогинился – вызываем метод контроллера навигации popBackStack(), который удалит текущий экран из стека, чтобы пользователь потом переходил на главный экран при нажатии кнопки «Назад». Затем отправляем пользователя на экран личного кабинета, в AccountFragment.

Если аутентификация пользователя по какой-либо причине не удалась, отображаем снекбар с сообщением о неудаче. Если не то, и не другое – отображаем в логах сообщение, что состояние пользователя не изменилось.

AccountFragment

Осталось реализовать последний фрагмент AccountFragment. Он отображает текст приветствия с именем пользователя и кнопку выхода из аккаунта. Его реализация похожа на главный фрагмент MainFragment.

Здесь в функции observeAutenticationState, где мы подписываемся на состояние аутентификации пользователя, мы создаем строковую переменную hello для приветствия, куда включаем полученное посредством класса FirebaseAuth имя текущего залогиненного пользователя. Мы используем строку с регулярным выражением, которую мы создавали в начале урока:

Ее мы передаем функции String.format вместе с именем пользователя, и имя подставляется вместо выражения %1$s.

Затем через binding обращаемся к текстовому полю и помещаем туда форматированный текст переменной hello.

А по нажатию кнопки вызываем метод signOut класса AuthUI, таким образом пользователь выходит из аккаунта и далее отправляем его на главный экран вызовом функции popBackStack(), которая удаляет текущий фрагмент из стека. Таким образом, происходит переход к предыдущему фрагменту в стеке. А поскольку это был LoginFragment и мы тоже его удалили из стека аналогичным образом, то пользователь попадет на экран, который точно в cтеке есть – MainFragment.

Вызов функции observeAutenticationState() пропишем в переопределенном методе onViewCreated.

 

Запуск и тестирование приложения

С кодом мы закончили, переходим к тестированию. Запускаем приложение на эмуляторе.Запускаем приложение на эмуляторе

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

После нажатия кнопки входа отображается список провайдеров аутентификации.список провайдеров аутентификации

Если выбрать способ с адресом электронной почты и паролем, то далее откроется окно для ввода email и пароля.окно для ввода email и пароля

А если выбрать Google, то откроется стандартный диалог выбора Google аккаунта из аутентифицированных на устройстве.

стандартный диалог выбора Google аккаунта

После аутентификации попадаем на экран Личного кабинета. Видим приветствие с собственным именем и кнопку выхода из аккаунта.После аутентификации попадаем на экран Личного кабинета

Системная кнопка «Назад» ведет нас на главный экран. Если мы снова нажмем кнопку Личного кабинета, то попадем непосредственно на его экран, поскольку уже аутентифицированы.

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

Исходный код

На этом урок закончен. Надеемся, он был вам полезен. Отзывы, пожелания и вопросы пишите в комментариях.

Исходный код проекта из урока можно скачать по ссылке.
До встречи на следующем уроке!

 

Урок 15. Передача данных между экранами — пунктами назначения. Android Navigation. Bundle vs Safe Args

 

 

 

 

 

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