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

Компонент навигации для 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 долларов в месяц, и если вы используете эту ссылку, я получаю небольшую комиссию.