# Навигация в приложении ## Оглавление - [Введение](#введение) - [Fragments](#fragments) - [Реализация навигации](#реализация-навигации) - [Добавление нового фрагмента](#добавление-нового-фрагмента) - [Добавление перехода от одного экрана к другому](#добавление-перехода-от-одного-экрана-к-другому) - [Манипуляции со стеком фрагментов](#манипуляции-со-стеком-фрагментов) - [ActionBar-меню](#actionbar-меню) - [SafeArgs](#safeargs) - [Navigation Drawer](#navigation-drawer) - [Интенты](#интенты) - [Explicit-интенты](#explicit-интенты) - [Implicit-интенты](#implicit-интенты) - [Добавление функции "Share" с использованием интента](#добавление-функции-share-с-использованием-интента) ## Введение Для работы с навигацией между экранами в Android-приложении применяется библиотека **Navigation Component**. Она входит в набор Android Jetpack и помогает реализовать навигацию, от простых нажатий на кнопки до более сложных, таких как панели приложений (action bars) и панель навигации (navigation drawers). Библиотека предоставляет ряд преимуществ: * Корректная обработка кнопок «Вверх» и «Назад» по умолчанию * Поведение по умолчанию для анимации и переходов. * Реализация шаблонов навигации пользовательского интерфейса (таких, как navigation drawer). * Безопасность типов при передаче информации во время навигации. * Инструменты Android Studio для визуализации и редактирования navigation flow приложения. В качестве примера будет использоваться готовый проект, в котором уже есть все необходимые экраны, их макеты и исходные коды. Необходимо будет лишь разобраться с навигацией между экранами. Тестовое приложение представляет собой мини-викторину. Оно будет иметь несколько экранов: * Стартовый экран с логотипом и кнопкой "Play". * Экран с вопросами и вариантами ответа. * Экран с кнопкой "Try Again" в случае ошибки. * Экран с кнопкой "Next Match" в случае успешного прохождения теста. * Экран "About" с информацией о приложении. * Экран "Rules" с текстом правил приложения. Стартовый проект с приложением содержит все эти экраны, но не содержит связей между ними, их создание будет рассмотрено далее. Каждое описанный экран — это фрагмент. Фрагмент — особый компонент системы Android. Прежде чем начать разбираться с навигацией по фрагментам, нужно разобраться с тем, что это такое. ## Fragments Фрагмент (класс `Fragment`) предоставляет часть пользовательского интерфейса в активности (`Activity`). Активности могут содержать как несколько фрагментов внутри себя, так и один фрагмент. Обычно несколько фрагментов в одной активности располагают при разработке приложения для планшетов, когда для меню навигации и для основного контента выделяются отдельные фрагменты, располагающиеся на экране одновременно. ![](fragments.png) Фрагменты имеют свой отдельный жизненный цикл и самостоятельно отдельно от активностей занимаются обработкой событий ввода. Обычно фрагменты имеют собственные макеты (layouts), описывающие элементы пользовательского интерфейса. Если в активностях инициализация и установка макетов выполняется в методе `onCreate()`, то в фрагментах — в методе `onCreateView()` общем для всех фрагментов. Вместо метода `setContentView()` для установки макета здесь используется метод `inflate()`, принимающий на вход ссылку на ресурс с макетом, объект `container`, являющийся родительским видом и булево значение, определяющее нужно ли новый макет привязать к корневому (в большинстве случаев используется `false`). ```kotlin class ExampleFragment : Fragment() { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { // Inflate the layout for this fragment return inflater.inflate(R.layout.example_fragment, container, false) } } ``` Для добавления фрагмента в активность необходимо описать тег `` в макете активности. ```xml ``` Существует подклассы фрагментов: `ListFragment`, `DialogFragment`, `PreferenceFragment`, `WebViewFragment` и др., упрощающие реализацию и работу с конкретными типами окон: списками, диалоговыми окнами, настройками и веб-содержимым, соответственно. Именно фрагменты чаще используются в качестве компонентов, описывающих экраны приложения, располагаясь в одной или паре активностей, в зависимости от задач приложения. Фрагменты позволяют настраивать боковые панели навигации (navigation drawer) и ActionBar-меню. Фрагменты упрощают вопрос навигации и передачи данных между экранами приложения. Также создание фрагментов — наиболее выгодно с точки зрения траты ресурсов системы, чем создание активностей. ## Реализация навигации Перейдем к вопросам реализации навигации между экранами приложения. Прежде всего необходимо выделить три основные принципа навигации: 1. Приложение всегда должно иметь стартовый экран. Стартовый экран — тот, который показывается при запуске приложения. 2. Пользователь должен всегда иметь возможность вернуться назад. Это подразумевает как возврат на один экран назад с помощью системной кнопки "Назад", так и с помощью аналогичной кнопки в Action Bar. 3. Системная кнопка "Назад" и кнопка "Назад" на Action Bar должны работать аналогичным образом. Исключение — нажатие на системную кнопку на стартовом экране сворачивает приложение и возвращает пользователя на рабочий экран Android. Сперва необходимо скачать и собрать стартовый проект **trivia-starter-code**. Стартовый проект уже содержит почти все необходимые фрагменты (кроме одного), все необходимые макеты, создавать их не нужно. Необходимо сосредоточиться исключительно на навигации. ### Добавление нового фрагмента Единственный фрагмент, которого не хватает в стартовом проекте — это фрагмент с начальным экраном приложения `TitleFragment`. Для его создания необходимо выполнить следующие шаги: 1. File -> New -> Fragment -> Fragment (Blank). 2. Fragment Name: `TitleFragment`. 3. Убрать галочки "Create layout XML?", "Include fragment factory methods?" и "Include interface callbacks?" Макет для этого фрагмента уже есть в стартовом проекте. 4. Нажать "Finish". Далее необходимо заменить код, сгенерированный автоматически для настройки макета фрагмента (`return TextView(activity).apply`), на использование метода `DataBindingUtil.inflate()`. Это позволит нам и инициализировать макет для фрагмента и использовать в дальнейшем Data Binding. Определение метода `onCreateView()` должно выглядеть следующим образом: ```kotlin override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { val binding = DataBindingUtil.inflate( inflater, R.layout.fragment_title, container, false) return binding.root } ``` И в конце необходимо поместить фрагмент с начальным экраном на `MainActivity`, чтобы его можно было отобразить. В файл `activity_main.xml`: ```xml ``` После запуска приложения на экране отобразится содержимое `TitleFragment` — начальный экран с логотипом и кнопкой "Play". По нажатию на кнопку ничего не выполняется. ### Добавление перехода от одного экрана к другому Для реализации непосредственно навигации между экранами необходимо добавить зависимость от библиотеки **Navigation Component**, и создать файл с графом навигации. **1. Добавление зависимости Navigation Components:** Для добавления зависимости необходимо сперва добавить переменную `version_navigation` в блок `ext` в Gradle-файл проекта. Далее эта переменная будет использоваться для добавления зависимости конкретной версии, избавляя от дублирования номера версии в нескольких местах. ```gradle buildscript { ext { ... version_navigation = '2.3.2' ... } } ``` Добавление непосредственно зависимостей в Gradle-файл модуля `app`: ```gradle dependencies { ... implementation "androidx.navigation:navigation-fragment-ktx:$version_navigation" implementation "androidx.navigation:navigation-ui-ktx:$version_navigation" } ``` **2. Добавление графа навигации:** Граф навигации — файл с описанием экранов приложения и связей между ними в формате XML. Для создания файла навигации необходимо кликнуть правой кнопкой на модуль `app` и выбрать **New** -> **Android resource file**. В качестве имени файла ресурса необходимо указать `navigation.xml`, а также выбрать тип `Navigation`. Когда файл будет создан, его можно будет найти в каталоге `res/navigation`. **3. Замена `TitleFragment` на `NavHostFragment` в активности:** Для реализации навигации также необходимо добавить `NavHostragment` — контейнер, который будет информацию о пунктах назначения на графе навигации. Класс `NavHostragment` является стандартным. Чтобы его использовать, необходимо в описании макета `activity_main.xml` заменить использование `TitleFragment` на `NavHostFragment`. ```xml ``` Параметр `name` ссылается на полное имя класса `NavHostFragment`. Параметр `navGraph` ссылается на ресурс с описанием графа навигации, откуда класс `NavHostFragment` будет брать информацию о связях между фрагментами с экранами. Параметр `defaultNavHost` определяет, что этот контейнер навигации является контейнером по умолчанию и именно к нему будет применено использование системной кнопки "Назад". **4. Добавление фрагментов на граф навигации:** Для редактирования навигации используется редактор навигации. Он так же как и редактор макетов имеет два режима (две вкладки): **Design** и **Code**. Первый позволяет использовать элементы интерфейса IDE для редактирования графа навигации, второй предоставляет доступ к исходному XML-файлу графа. Кроме этого еще есть вкладка **Split**, которая делит рабочую область на две части: в одной код, во второй — графическое представление. Для добавления фрагмента на граф навигации необходимо в режиме **Design** необходимо нажать на кнопку `+` в верхнем левом углу и среди макетов выбрать необходимый, например, `fragment_title`. Миниатюра фрагмента появится в рабочей зоне графа навигации. Далее необходимо сделать `TitleFragment` стартовым. Для этого необходимо нажать на кнопку **Assign start destination**, когда фрагмент выбран, либо указать `id` фрагмента в качестве атрибута **Start Destination** (`app:startDestination` в XML-коде) для самого графа навигации. Если запустить приложение, то будет отображен `TitleFragment`, находящийся внутри `NavHostFragment`, который в свою очередь располагается внутри одной единственной активности. Далее необходимо добавить еще один фрагмент, чтобы установить переход от начального экрана к следующему. Добавим на граф навигации `fragment_game`. Миниатюра макета также появляется в рабочей зоне графа навигации. Для установки перехода необходимо протянуть связь от фрагмента `TitleFragment` (индикатора в виде "кружочка") к `GameFragment`, и установить обработчик нажатию на кнопку "Play" на `TitleFragment`. По нажатию на эту кнопку должен выполниться переход к основному экрану игры `GameFragment`. Реализация обработчика будет выглядеть следующим образом: ```kotlin binding.playButton.setOnClickListener { Navigation.findNavController(it).navigate(R.id.action_titleFragment_to_gameFragment) } ``` Статический метод `Navigation.findNavController()` служит для получения экземпляра класса `NavController`, который является контроллером для графа навигации, предоставляет различную информацию о графе навигации и позволяет взаимодейстовать с ним. Так `NavController` имеет метод `navigate()`, выполняющий переход от одного экрана к другому по уникальному идентификатору, который генерируется автоматически при создании связи. Например, идентификатор `action_titleFragment_to_gameFragment` означает переход от `titleFragment` к `gameFragment` (эти идентификаторы описаны в элементах `` на графе навигации). Помимо начального и основного игрового экрана приложение имеет еще экран победы `GameWonFragment` и экран поражения `GameOverFragment`. Их также необходимо добавить на граф навигации, установить переход к ним от `GameFragment`. Затем в классе `GameFragment` в методе `onCreateView()` необходимо добавить код для выполнения перехода к фрагментам в зависимости от определенных условий (места для вставки кода выделены комментариями). ```kotlin // We've won! Navigate to the gameWonFragment. view.findNavController().navigate(R.id.action_gameFragment_to_gameWonFragment) ``` ```kotlin // Game over! A wrong answer sends us to the gameOverFragment. view.findNavController().navigate(R.id.action_gameFragment_to_gameOverFragment) ``` Теперь после запуска приложения и успешного прохождения теста будет выполнен переход к `GameWonFragment`, а в случае ошибки `GameOverFragment`. ### Манипуляции со стеком фрагментов Каждый новый открытый фрагмент помещается на стек фрагментов. Например, если пользователь прошел по всем экранам приложения от начального до экрана "Gave Over", то стек фрагментов содержит `TitleFragment`, `GameFragment` и `GameOverFragment`. Последовательность именно такая. Если пользователь на экране "Game Over" нажмет на системную кнопку "Назад", то вернется к экрану с игрой, где будет написан последний вопрос, а это является некорректным поведением. Для пользователя логичным было бы попасть на начальный экран, а для этого необходимо удалить фрагмент `GameFragment` из стека фрагментов. Такое поведение можно реализовать следующим образом. **1. Настройка поведения системной кнопки "Назад":** Чтобы нажатие на системную кнопку "Назад" выполняло переход от `GameOverFragment` к `TitleFragment`, необходимо настроить атрибут **Pop Behavior**. Для этого нужно выбрать уже созданную связь между `GameFragment` и `GameOverFragment`, на панели атрибутов выбрать **Pop Behavior** -> **popUpTo** -> **gameFragment** и установить галочку **popUpToInclusive**. Настройка "поведения" определяет к какому фрагменту необходимо вернуться назад по выбранной связи (все фрагменты выше выбранного на стеке будут удалены), а галочка **popUpToInclusive** говорит, что сам фрагмент `GameFragment` тоже нужно удалить со стека (это вернет пользователя на фрагмент `TitleFragment`). Аналогичное поведение необходимо реализовать и возврата с фрагмента `GameWonFragment` к `TitleFragment`. **2. Добавление обработчика для кнопок на `GameOverFragment` и `GameWonFragment`:** На экранах "Game Over" и "Congratulations" есть кнопки "Try Again" и "Next Match" соответственно. Эти кнопки должны позволять пользователю начать игру заново, т.е. возвращать пользователя к предыдущему экрану с вопросами `GameFragment`. Для этого необходимо протянуть новую связь от `GameOverFragment` к `GameFragment` и на панели атрибутов установить **Pop To** -> **titleFragment**. Здесь указывается возврат к **titleFragment** для того, чтобы в любой момент, когда показан фрагмент `GameOverFragment` можно было вернуться к `TitleFragment`. Иначе, на стеке может казаться множество `GameOverFragment` и по кнопке "Back" возврат будет происходить именно к ним. Затем необходимо перейти в класс `GameOverFragment` и установить обработчик кнопки "Try Again". ```kotlin binding.tryAgainButton.setOnClickListener { it.findNavController().navigate(R.id.action_gameOverFragment_to_gameFragment) } ``` Аналогично необходимо создать связь между `GameWonFragment` и `GameFragment`, настроить **Pop Behavior** и установить обработчик нажатия на кнопку "Match Again". **3. Добавление кнопки "Назад" на Action Bar:** Кроме системной кнопки "Назад" Android поддерживает еще и кнопку "Назад", отображающуюся на Action Bar — панели сверху приложения. Для ее добавления необходимо внести изменения в `MainActivity`. Необходимо получить экземпляр `NavController` и использовать его для установки Action Bar: ```kotlin private lateinit var navController: NavController override fun onCreate(savedInstanceState: Bundle?) { ... navController = this.findNavController(R.id.navHostFragment) NavigationUI.setupActionBarWithNavController(this, navController) } ``` Далее необходимо переопределить метод активности `onSupportNavigateUp()`: ```kotlin override fun onSupportNavigateUp(): Boolean { return navController.navigateUp() } ``` Метод `onSupportNavigateUp()` определяет, что именно должно быть выполнено при навигации с помощью кнопки "Назад". В данном случае это навигация по стеку с помощью экземпляра `NavController`. Если запустить приложение, можно убедиться, что кнопка "Назад" на Action Bar отображается и работает она аналогично системной кнопке "Назад. ## ActionBar-меню Action Bar может содержать собственное меню. Обычно меню помечается кнопкой в виде трех вертикальных точек. В случае приложения "Trivia" меню будет содержать один пункт "About", открывающий отдельный экран с информацией о приложении. Фрагмент `AboutFragment` уже создан. **1. Добавление фрагмента `AboutFragment` на граф навигации:** Для добавления возможности перехода к экрану "About" необходимо сперва добавить его на граф навигации. **2. Создание нового ресурса `menu`:** Конфигурация меню описывается в специальных ресурсах `menu`. Для его добавления необходимо кликнуть правой кнопкой по модулю `app`, выбрать **New** -> **Android Resource File**. Далее необходимо указать тип **Menu** и имя файла, например, **overflow_menu** и кликнуть "Ok". В результате будет создан файл `overflow_menu.xml` и помещен в каталог `res/menu`. В меню необходимо добавить новый элемент **Menu Item**. На панели атрибутов указать: * **ID**: `aboutFragment` — совпадает с `id` фрагмента, к которому необходимо перейти при выборе данного пункта меню (ВАЖНО, чтобы эти идентификаторы совпадали), * **title**: `@string/about` — ссылка на строковый ресурс. **3. Добавление меню на Action Bar фрагмента `TitleFragment`:** Для добавления меню на Action Bar фрагмента `TitleFragment` необходимо сперва добавить вызов метода `setHasOptionsMenu(true)` внутри метода `onCreateView()`. Вызов метода определяет, что данный фрагмент в принципе может отображать меню. ```kotlin override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { ... setHasOptionsMenu(true) return binding.root } ``` Для инициализации и установки меню из файла ресурса необходимо переопределить метод фрагмента `onCreateOptionsMenu()` и описать в нем загрузку меню из файла `overflow_menu`. ```kotlin override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { super.onCreateOptionsMenu(menu, inflater) inflater.inflate(R.menu.overflow_menu, menu) } ``` **4. Реализация обработчика нажатия на элемент меню:** Чтобы нажатие на элемент меню "About" имело эффект, необходимо реализовать обработчик нажатия. Для этого требуется переопределить метод фрагмента `onOptionsItemSelected()`. ```kotlin override fun onOptionsItemSelected(item: MenuItem): Boolean { return NavigationUI.onNavDestinationSelected(item, requireView().findNavController()) || super.onOptionsItemSelected(item) } ``` Метод `NavigationUI.onNavDestinationSelected()` выполняет навигацию к фрагменту по идентификатору меню. Именно для этого идентификаторы фрагмента и меню, по которому к фрагменту нужно перейти, важно указывать одинаковыми. По сути фрагмент `AboutFragment` имеет в графе навигации идентификатор `aboutFragment`. Добавленный пункт меню "About" имеет аналогичный идентификатор и метод `NavigationUI.onNavDestinationSelected()`, принимающий на вход элемент меню (`MenuItem`) и экземпляр `NavController` сопоставляет эти идентификаторы и выполняет переход, если это возможно. Если переход невозможен, то метод вернет `false` и будет вызвана родительская реализация метода `super.onOptionsItemSelected(item)`. ## SafeArgs Иногда перед разработчиком может встать задача передачи данных между экранами приложения и для этого в Android есть удобный механизм. **SafeArgs** — это Gradle-плагин, позволяющий реализовать передачу данных между фрагментами. Далее будет рассмотрено добавление SafeArgs для простейшей задачи — отображения текстового сообщения об общем числе вопросов и числе правильных ответов на `GameWonFragment`. **1. Добавление зависимости SafeArgs:** Сперва необходимо добавить зависимость от библиотеки SafeArgs в Gradle-файл проекта (не модуля `app`). ```gradle dependencies { ... classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$version_navigation" } ``` Затем необходимо включить плагин в Gradle-файл модуля `app`. ```gradle apply plugin: 'androidx.navigation.safeargs' ``` После включения плагина необходимо очистить и пересобрать проект, чтобы компилятор сгенерировал "Directions"-классы, содержащие информацию о навигации между фрагментами (по типу идентификаторов `action_gameFragment_to_gameWonFragment`), а также позволяющие организовать передачу данных между фрагментами. **2. Использование "Directions"-классов:** После того, как "Directions"-классы сгенерированы, можно заменить использование ссылок на идентификаторы навигации на использование этих классов. ```kotlin // GameFragment: Using directions to navigate to the GameWonFragment view.findNavController().navigate(GameFragmentDirections.actionGameFragmentToGameWonFragment()) ``` ```kotlin // GameFragment: Using directions to navigate to the GameOverFragment view.findNavController().navigate(GameFragmentDirections.actionGameFragmentToGameOverFragment()) ``` По такому же принципу выполняется замена в остальных местах. **3. Добавление аргументов в редакторе навигации:** Далее необходимо описать для `GameWonFragment` данные, которые он должен получать. В данном случае — это общее число вопросом `numQuestions` и число правильных ответов `numCorrect`. Оба числа целочисленные (`integer`). Для добавления описания параметров необходимо перейти в редактор навигации, выбрать `gameWonFragment` и добавить аргументы в разделе "Arguments" панели "Attributes". После этого сборка проекта будет невозможно, поскольку вызов `GameFragmentDirections.actionGameFragmentToGameWonFragment()` будет требовать входных параметров, которые только что были описаны. **4. Передача данных фрагменту:** Чтобы передать данные фрагменту, необходимо передать их в вызов `GameFragmentDirections.actionGameFragmentToGameWonFragment()`. ```kotlin // GameFragment: Adding the parameters to the Action view.findNavController().navigate( GameFragmentDirections.actionGameFragmentToGameWonFragment(numQuestions, questionIndex)) ``` Параметры `numQuestions` и `questionIndex` определяют общее число вопросов и индекс текущего вопроса, а в случае с выигрышем — это и число правильных ответов. Теперь проект успешно собирается, данные передаются, но никак не принимаются и не обрабатываются. **5. Обработка полученных данных:** Переданные в `GameWonFragment` данные отобразим на экране с помощью `Toast`-сообщения. Для этого необходимо воспользоваться вызовом `GameWonFragmentArgs.fromBundle(arguments)` для получения передаваемых данных. "Args"-класс `GameWonFragmentArgs` генерируется, так же как и "Directions"-классы, автоматически. Он предоставляет доступ к хранилищу `Bundle` куда сохраняются передаваемые данные. Далее для получения конкретных значений необходимо обратиться к данным по именам ранее переданных переменных. ```kotlin // GameWonFragment.onCreate val args = GameWonFragmentArgs.fromBundle(requireArguments()) Toast.makeText(context, "NumCorrect: ${args.numCorrect}, NumQuestions: ${args.numQuestions}", Toast.LENGTH_LONG).show() ``` Таким образом при открытии `GameWonFragment` на экране будет отображено `Toast`-сообщение с полученными `numCorrect` и `numQuestions`. ## Navigation Drawer Еще одним из основных элементов для навигации между экранами является боковое меню, также оно называется Navigation Drawer. Далее будут рассмотрены шаги по добавлению бокового меню. **1. Добавление зависимости от библиотеки Material:** Navigation Drawer является частью библиотеки Material, поэтому ее необходимо включить в Gradle-файл: ```gradle implementation "com.google.android.material:material:$version_material" ``` **2. Добавление XML-файла бокового меню:** Далее необходимо добавить XML-файл ресурса с описанием элементов бокового меню, аналогично тому, как это было сделано для ActioBar-меню. Файл будет называться `navdrawer_menu.xml` и будет содержать один элемент "About". Элемент меню должен иметь следующие атрибуты: * **ID**: `aboutFragment` — совпадает с идентификатором фрагмента, который должен быть открыт, * **Name**: `@string/about` — ссылка на строковый ресурс, * **Icon**: `@drawable/android` — ссылка на ресурс с изображением, ресурс уже содержится в проекте. **3. Добавление бокового меню на макет `MainActivity`:** Для возможность использования Navigation Draver необходимо добавить компонент `DrawerLayout` в активность и поместить внутрь этого компонента весь контент активности. ```xml ``` И затем добавить компонент `NavigationView` сразу после определения `LineatLayout`, внутри которого находится фрагмент. ```xml ``` Параметр `headerLayout` определяет какой макет использовать в качестве заглавной части меню. В параметре `menu` задается ссылка на ресурс с меню, которое необходимо загрузить в `NavigationView`. **4. Инициализация и настройка Navigation Drawer:** Для инициализации и настройки работы Navigation Drawer необходимо сперва создать поле `drawerLayout` и инициализировать его в методе `onCreate()`: ```kotlin class MainActivity : AppCompatActivity() { private lateinit var drawerLayout: DrawerLayout override fun onCreate(savedInstanceState: Bundle?) { ... val binding = DataBindingUtil.setContentView(this, R.layout.activity_main) drawerLayout = binding.drawerLayout ... } } ``` Для отображения кнопки бокового меню на Action Bar необходимо добавить экземпляр `drawerLayout` в качестве третьего параметра вызова `NavigationUI.setupActionBarWithNavController()`: ```kotlin NavigationUI.setupActionBarWithNavController(this, navController, drawerLayout) ``` Для добавления возможности навигации, т.е. перехода к экрану "About" при нажатии на соответствующие элемент бокового меню, необходимо добавить вызов `NavigationUI.setupWithNavController()` с передачей `NavController` и элемента `NavigationView` в качестве параметров: ```kotlin NavigationUI.setupWithNavController(binding.navView, navController) ``` Для того, чтобы корректно обрабатывалось и открытие бокового меню и кнопка "Назад", которая отображается при переходе к следующим за начальным экранам, необходимо добавит в обработчике `onSupportNavigateUp()` в вызов `NavigationUI.navigateUp()` параметры `navController` и `drawerLayout`. ```kotlin override fun onSupportNavigateUp(): Boolean { return NavigationUI.navigateUp(navController, drawerLayout) } ``` В этом случае `drawerLayout` будет использоваться, если пользователь находится на последнем фрагменте в стеке. А `navController` будет использоваться для навигации в остальных случаях. ## Интенты Интент — это объект, содержащий в себе запрос на выполнение некоторого действия. Интент может включать в себя следующие компоненты: * действие,которое необходимо выполнить (обязательный компонент); * набор категорий, позволяющих группировать действия; * URI, идентифицирующий данные, над которыми необходимо выполнить действие; * дополнительные параметры (extras), необходимые для выполнения действия. Наиболее распространённый сценарий использования интентов — запуск другой активности. При этом это может быть как запуск активности собственного приложения, так и стороннего, например, открытие галереи, приложения почты или камеры и т.д. Существует два вида интентов: явные (explicit) и неявные (implicit). ### Explicit-интенты Explicit-интенты используются для перехода к активности в рамках собственного приложения. Они требуют указания полного имени класса для выполнения действия ```kotlin val intent = Intent(MainActivity.this, SecondActivity::class.java) startActivity(intent) ``` Первый параметр в конструкторе класса `Intent` — ссылка на контекст (в данном случае на активность, из которой выполняется вызов). Второй параметр — полное имя класса второй активности, которую необходимо открыть. Также при переходе к другой активности можно поместить в интент некоторые данные. ```kotlin val intent = Intent(MainActivity.this, SecondActivity::class.java) intent.putExtra("KEY", "VALUE") startActivity(intent) ``` Для получения значения из интента внутри `SecondActivity` необходимо вызвать: ```kotlin val value = getIntent().extras["KEY"] ``` ### Implicit-интенты Implicit-интенты используются для открытия сторонних активностей сторонних приложений и выполнения каких-либо действий. Примеры: открытие приложения почты для написания нового письма и передача в него определенного текста; открытие приложения камеры для создания снимка и получения его в рамках приложения; открытие браузера с сайтом по определенной ссылке. Для создания implicit-интента необходимо указать три параметра: * **action** — действие, которое необходимо выполнить, * **category** — категория компонента (активности), который должен выполнить действие интента, * **data** — передаваемые данные. Примеры констант **ACTION**: * `ACTION_CALL` — инициализирует обращение по телефону. * `ACTION_SEARCH` — запускает активность для выполнения поиска. * `ACTION_SEND` — запускает активность экран для отправки данных, указанных в поле **data** интента. Например, диалог для выбора приложения, в которое необходимо использовать в качестве "отправителя". * `ACTION_VIEW` — Наиболее распространённое общее действие. Используется для отображения какой-либо активности. Например, для открытия браузера. Примеры констант **CATEGORY**: * `BROWSABLE` — активность может быть вызвана браузером, чтобы отобразить "ссылочные"" данные, например, изображение или почтовое сообщение. * `LAUNCHER` — активность может быть главной активностью, использующейся для запуска приложения в системе. Такую категорию имеет главная активность каждого приложения в Android. * `HOME` — активность отображает Home Screen, домашний экран, который пользователь видит после включения устройства и загрузки системы, или когда нажимает клавишу "HOME". Пример ниже демонстрирует создание интента для открытия ссылки и запуск активности, которая подойдет для выполнения действия данного интента. При наличии нескольких браузеров, пользователю будет предложено выбрать один из них. ```kotlin val address: Uri = Uri.parse("http://developer.android.com") val intent = Intent(Intent.ACTION_VIEW, address) startActivity(intent) ``` Следующий пример демонстрирует создание интента для отправки некоторого текста. В качестве действия (action) указывается `ACTION_SEND`, что при вызове `startAction()` позволит показать пользователю окно с возможностью выбора приложения для отправки сообщения (почтовые клиенты или мессенджеры). Сообщение сохраняется в интенте под ключом `EXTRA_TEXT`. Это стандартный ключ системы Android, именно он используется приложениями, которые могут принимать сообщения извне для дальнейшей отправки ([EXTRA_TEXT](https://developer.android.com/reference/android/content/Intent.html#EXTRA_TEXT)). ```kotlin val shareIntent = Intent(Intent.ACTION_SEND) shareIntent.setType("text/plain") shareIntent.putExtra(Intent.EXTRA_TEXT, "This is a text to send via any sender") startActivity(shareIntent) ``` ### Добавление функции "Share" с использованием интента Для демонстрации работы implicit-интентов будет добавлен функционал "Поделиться" с возможностью отправки данных, через сторонние приложения типа почтового клиента или мессенджера. В качестве сообщения будет использоваться добавленное ранее сообщения о количестве правильных ответов и общем числе вопросов. Кнопка "Share" будет добавлена в ActionBar-меню `GameWonFragment`. В проекте уже есть файл `winner_menu.xml`, содержащий описание меню. **1. Настройка отображения меню на `GameWonFragment`:** ``` // GameWonFragment.onCreate binding.nextMatchButton.setOnClickListener { view: View -> view.findNavController().navigate( GameWonFragmentDirections.actionGameWonFragmentToGameFragment()) } setHasOptionsMenu(true) ``` **2. Добавление метода для создания интента:** Для создания интента добавим метод `getShareIntent()`. Метод будет получать экземпляр `Bundle` с переданными данными (`numCorrect` и `numQuestions`) и создавать интент с действием `ACTION_SEND`, текстовым типом данных и строкой с самими данными. ```kotlin // Creating our Share Intent private fun getShareIntent() : Intent { val args = GameWonFragmentArgs.fromBundle(requireArguments()) val shareIntent = Intent(Intent.ACTION_SEND) shareIntent.setType("text/plain") .putExtra(Intent.EXTRA_TEXT, getString(R.string.share_success_text, args.numCorrect, args.numQuestions)) return shareIntent } ``` **3. Обработка нажатия на меню "Share":** Для обработки нажатия необходимо переопределить метод `onOptionsItemSelected` и по нажатию на пункт меню "Share" вызывать системную активность, передавая ей созданный интент. ```kotlin override fun onOptionsItemSelected(item: MenuItem): Boolean { when (item.itemId) { R.id.share -> startActivity(getShareIntent()) } return super.onOptionsItemSelected(item) } ``` После запуска приложения можно убедиться, что по нажатию на пункт меню "Share" запускается системная активность, предлагающая выбрать приложение для отправки текстовых данных. Если убрать установку типа "text/plain" для интента и запустить приложение снова, то после нажатия на пункт меню "Share" приложение упадет с сообщением о том, что активность для данного действия не найдена. **4. Добавление проверки на доступность активности:** Для того, чтобы решить проблему с падением, необходимо добавить проверку на доступность существующей активности для созданного интента. Если активности необходимой для интента нет в системе, то пункт меню "Share" логично будет скрыть. ```kotlin override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { super.onCreateOptionsMenu(menu, inflater) inflater.inflate(R.menu.winner_menu, menu) // check if the activity resolves if (null == getShareIntent().resolveActivity(requireActivity().packageManager)) { // hide the menu item if it doesn't resolve menu.findItem(R.id.share).isVisible = false } } ``` Если запустить приложение, можно убедиться, что пункт меню "Share" не отображается на экране `GameWonFragment`.