Держите навигационный график для каждого модуля и визуализируйте более простую навигацию внутри вашего проекта.
Компонент навигации для Android позволяет управлять навигацией внутри вашего приложения проще, чем это было сделано ранее в Android, где раньше у нас было Activity
на экран.
Компонент навигации предполагает, что реализуется Шаблон единого действия, поэтому каждый экран в приложении теперь представляет собой Fragment
.
Он поставляется с графическим инструментом, который дает разработчику общее представление о том, как определяется логика навигации, и облегчает добавление новых Fragments
и действий для навигации без использования кода (есть также возможность использовать код, если хотите).
Для небольших или краткосрочных приложений сохранение одного графа навигации — хорошая идея и лучший вариант. Тем не менее, если вы планируете построить что-то большее, вам подойдет многомодульный проект.
Если вы новичок в многомодульных проектах, рекомендую прочитать это официальное руководство перед прочтением этой статьи.
В этой статье мы узнаем, как реализовать навигацию с помощью графа для каждого модуля, как соединить их друг с другом, а также добавить условную навигацию, например, для экрана входа в систему.
Как структурировать навигационные графы
Для многомодульного проекта мы собираемся следовать следующим модулям.
Идея состоит в том, чтобы иметь родительский навигационный граф, который действует как контейнер, с меньшими навигационными графами для каждого модуля.
Модуль app
будет отвечать за создание родительского графа навигации и определение начальной точки приложения, как мы увидим в следующем разделе, когда добавим условную навигацию.
А пока давайте представим, что у нас всегда одна и та же начальная точка нашего приложения внутри функционального модуля с именем home.
Во-первых, нам нужно создать родительский навигационный граф, который будет храниться в модуле app
, папка res/navigation
.
Поскольку начальный пункт назначения на данный момент фиксирован, мы можем установить его через свойство app:startDestination
.
Родительский навигационный граф будет содержать три навигационных графа, по одному на функциональный модуль, как было сказано ранее. В других модулях мы создали другие навигационные графы с помощью простого Fragment
.
<?xml version="1.0" encoding="utf-8"?> <navigation xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" app:startDestination="@id/home_nav_graph" > <include android:id="@+id/homeNav" app:graph="@navigation/home_nav_graph" /> <include android:id="@+id/adminNav" app:graph="@navigation/admin_nav_graph" /> <include android:id="@+id/playerNav" app:graph="@navigation/player_nav_graph" /> </navigation>
В XML, который увеличивает единственное Activity
, которое будет использовать приложение, нам нужно FragmentContainerView
вот так.
<androidx.fragment.app.FragmentContainerView android:id="@+id/navHostFragment" android:name="androidx.navigation.fragment.NavHostFragment" android:layout_width="match_parent" android:layout_height="match_parent" app:defaultNavHost="true" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="parent" />
Чтобы завершить первоначальную настройку, нам нужно перейти к классу Activity
и установить класс navController
.
val navHostFragment = supportFragmentManager .findFragmentById(R.id.navHostFragment) as? NavHostFragment navHostFragment?.apply { [email protected] = navController }
Только с помощью этого кода мы можем запустить приложение и начать использовать навигацию, реализованную в функциональном домашнем модуле.
Навигация между модулями
Чтобы перемещаться между двумя модулями, нам нужна глубокая ссылка. Каждый дочерний навигационный граф изолирован друг от друга, поэтому у нас нет доступных действий для перехода между фрагментами разных навигационных графов.
Чтобы использовать deeplink, нам нужно создать для него идентификатор, который вы можете сохранить в основном модуле, так как его будут использовать как минимум два модуля. Здесь я также добавляю аргумент, например, идентификатор в виде строки.
Если вам не нужно передавать аргументы, просто не добавляйте последнюю часть в скобки.
<string name="admin_deep_link" translatable="false">myProject://com.molidev8.myProject/admin/{adminId}</string>
Затем в дочернем навигационном графе, по которому мы собираемся перемещаться, нам нужно сделать deeplink на конкретный Fragment
вот так, с аргументом, который соответствует тому, который мы указали ранее в URL-адресе deeplink.
<fragment android:id="@+id/admin_fragment" android:name="presentation.admin.AdminFragment" android:label="AdminFragment" tools:layout="@layout/admin_fragment"> <argument android:name="adminId" app:argType="string" /> <deepLink app:uri="@string/admin_deep_link" /> </fragment>
Теперь, чтобы использовать deeplink и навигацию, нам нужен следующий код, в котором мы заменяем местозаполнитель для идентификатора администратора фактическим значением.
val deeplink = NavDeepLinkRequest.Builder.fromUri( Uri.parse( getString(R.string.admin_deep_link).replace( "{adminId}", "123456abc" ) ) ) .build() findNavController().navigate(deeplink)
В Fragment
, к которому мы переходим, мы можем прочитать аргумент, используя Safe Args.
Я предлагаю вам реализовать каждое действие навигации по диплинку как функцию расширения NavController
, если вы планируете использовать их более одного раза.
fun NavController.navigateToAdmin(adminId: String) { val deeplink = NavDeepLinkRequest.Builder.fromUri( Uri.parse( getString(R.string.player_deep_link).replace( "{adminId}", adminId ) ) ) .build() findNavController().navigate(deeplink) } // Then call findNavController().navigateToAdmin("1234556abc")
Добавление условной навигации
Теперь давайте предположим, что домашний модуль отвечает за управление аутентификацией. В этом случае приложение загрузит домашний модуль, если пользователь никогда раньше не входил в систему, или модуль администратора или игрока, если он это сделал.
Для начала нам нужно удалить свойство app:startDestination
из родительского графа навигации, так как теперь нам нужно, чтобы оно было динамическим.
Там, где мы создали до NavController
в нашем Activity
, теперь нам нужно также установить граф навигации, который будет точкой входа приложения, в зависимости от состояния аутентификации пользователя.
Для этого я создал запечатанный класс для каждого состояния аутентификации с уже заданным местом назначения для каждого состояния. Для каждого пункта назначения требуется ссылка на каждый граф, как в следующем примере.
sealed class StartDestination(val destination: Int) { object Login : StartDestination(com.molidev8.feature_home.R.id.home_nav_graph) object Admin : StartDestination(com.molidev8.feature_admin.R.id.admin_nav_graph) object Player : StartDestination(com.molidev8.feature_player.R.id.player_nav_graph) }
И, наконец, задаем такой граф, где переменная startDestination
— это один из объектов из ранее созданного нами запечатанного класса.
navController.graph = navController.navInflater.inflate(R.navigation.app_nav_graph).apply { setStartDestination(startDestination.destination) }
Заключение
Теперь у вас есть навигационный график для каждого модуля, и вам легче увидеть, как работает навигация внутри приложения. Таким образом, новые разработчики смогут легче понять навигацию, а вы сохраните высокую связность и разъединение модулей.
Если вы хотите прочитать больше подобного контента и поддержать меня, не забудьте проверить мой профиль или дать Medium шанс, став участником, чтобы получить доступ к неограниченным историям от меня и других писателей. Это всего 5 долларов в месяц, и если вы используете эту ссылку, я получаю небольшую комиссию.