Лучшие практики навигации для многомодульных проектов

Навигационный граф может состоять из любой комбинации следующих элементов:

  • Единственное место назначения, например место назначения <fragment> .
  • Вложенный граф , инкапсулирующий набор связанных пунктов назначения.
  • Элемент <include> , который позволяет встроить другой файл графа навигации, как если бы он был вложенным.

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

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

график зависимостей для примера многомодульного приложения
начальный пункт назначения примера приложения
Рис. 1. Архитектура приложения и начальный пункт назначения для примера приложения.

Каждый функциональный модуль представляет собой автономный блок со своим собственным графиком навигации и пунктами назначения. Модуль app зависит от каждого из них, добавляя их в качестве деталей реализации в файл build.gradle , как показано:

классный

dependencies {
    ...
    implementation project(":feature:home")
    implementation project(":feature:favorites")
    implementation project(":feature:settings")

Котлин

dependencies {
    ...
    implementation(project(":feature:home"))
    implementation(project(":feature:favorites"))
    implementation(project(":feature:settings"))

Роль модуля app

Модуль app отвечает за предоставление полного графика вашего приложения и добавление NavHost в ваш пользовательский интерфейс. В графе навигации модуля app вы можете ссылаться на графики библиотеки с помощью <include> . Хотя использование <include> функционально аналогично использованию вложенного графа, <include> поддерживает графики из других модулей проекта или из проектов библиотеки, как показано в следующем примере:

<?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"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/nav_graph"
    app:startDestination="@id/home_nav_graph">

    <include app:graph="@navigation/home_navigation" />
    <include app:graph="@navigation/favorites_navigation" />
    <include app:graph="@navigation/settings_navigation" />
</navigation>

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

<?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"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/nav_graph"
    app:startDestination="@id/home_nav_graph">

    <include app:graph="@navigation/home_navigation" />
    <include app:graph="@navigation/favorites_navigation" />
    <include app:graph="@navigation/settings_navigation" />

    <fragment
        android:id="@+id/random_fragment"
        android:name="com.example.android.RandomFragment"
        android:label="@string/fragment_random" >
        <!-- Launch into Settings Navigation Graph -->
        <action
            android:id="@+id/action_random_fragment_to_settings_nav_graph"
            app:destination="@id/settings_nav_graph" />
    </fragment>
</navigation>

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

В предыдущем примере действие указывает пункт назначения навигации @id/settings_nav_graph . Этот идентификатор относится к пункту назначения, который определен во включенном графе @navigation/settings_navigation.

Навигация верхнего уровня в модуле приложения

Компонент навигации включает класс NavigationUI . Этот класс содержит статические методы, которые управляют навигацией с помощью верхней панели приложения, панели навигации и нижней навигации. Если пункты назначения верхнего уровня вашего приложения состоят из элементов пользовательского интерфейса, предоставляемых функциональными модулями, модуль app является естественным местом для размещения элементов навигации и пользовательского интерфейса верхнего уровня. Поскольку модуль приложения зависит от взаимодействующих функциональных модулей, все их пункты назначения доступны из кода, определенного в вашем модуле приложения. Это означает, что вы можете использовать NavigationUI для привязки пунктов назначения к пунктам меню, если идентификатор элемента соответствует идентификатору пункта назначения.

На рисунке 2 пример модуля app определяет BottomNavigationView в своем основном действии. Идентификаторы пунктов меню в меню соответствуют идентификаторам графов навигации графов библиотеки:

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <item
        android:id="@id/home_nav_graph"
        android:icon="@drawable/ic_home"
        android:title="Home"
        app:showAsAction="ifRoom"/>

    <item
        android:id="@id/favorites_nav_graph"
        android:icon="@drawable/ic_favorite"
        android:title="Favorites"
        app:showAsAction="ifRoom"/>

    <item
        android:id="@id/settings_nav_graph"
        android:icon="@drawable/ic_settings"
        android:title="Settings"
        app:showAsAction="ifRoom" />
</menu>

Чтобы позволить NavigationUI обрабатывать нижнюю навигацию , вызовите setupWithNavController() из onCreate() в вашем основном классе активности, как показано в следующем примере:

Котлин

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    val navHostFragment =
        supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
    val navController = navHostFragment.navController

    findViewById<BottomNavigationView>(R.id.bottom_nav)
            .setupWithNavController(navController)
}

Ява

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    NavHostFragment navHostFragment =
            (NavHostFragment) supportFragmentManager.findFragmentById(R.id.nav_host_fragment);
    NavController navController = navHostFragment.getNavController();
    BottomNavigationView bottomNav = findViewById(R.id.bottom_nav);

    NavigationUI.setupWithNavController(bottomNav, navController);
}

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

Имейте в виду, что для вашего модуля приложения обычно является плохой практикой иметь жесткую зависимость от конкретного пункта назначения, встроенную глубоко в навигационный граф ваших функциональных модулей. В большинстве случаев вы хотите, чтобы модуль вашего приложения знал только о точке входа во все встроенные или включенные навигационные графики (это применимо и за пределами функциональных модулей). Если вам нужно создать ссылку на пункт назначения глубоко в графе навигации вашей библиотеки, предпочтительный способ сделать это — использовать глубокую ссылку . Глубокие ссылки также являются единственным способом для библиотеки перейти к месту назначения в графе навигации другой библиотеки.

Навигация по функциональным модулям

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

Продолжая предыдущий пример, представьте, что вам нужно перейти от кнопки в модуле :feature:home к пункту назначения, вложенному в модуль :feature:settings . Вы можете сделать это, добавив глубокую ссылку на пункт назначения в графе навигации настроек, как показано:

<?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"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/settings_nav_graph"
    app:startDestination="@id/settings_fragment_one">

    ...

    <fragment
        android:id="@+id/settings_fragment_two"
        android:name="com.example.google.login.SettingsFragmentTwo"
        android:label="@string/settings_fragment_two" >

        <deepLink
            app:uri="android-app://example.google.app/settings_fragment_two" />
    </fragment>
</navigation>

Затем добавьте следующий код в onClickListener кнопки в домашнем фрагменте:

Котлин

button.setOnClickListener {
    val request = NavDeepLinkRequest.Builder
        .fromUri("android-app://example.google.app/settings_fragment_two".toUri())
        .build()
    findNavController().navigate(request)
}

Ява

button.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        NavDeepLinkRequest request = NavDeepLinkRequest.Builder
            .fromUri(Uri.parse("android-app://example.google.app/settings_fragment_two"))
            .build();
        NavHostFragment.findNavController(this).navigate(request);
    }
});

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

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