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

Итак, у меня есть эта структура для моих модулей в моем текущем приложении.

App Module Structure

Я не нашел никакой официальной документации по мультимодульной навигации, но нашел эту статью, так что вот мои файлы Gradle:

Особенность 1 - Деталь

...
implementation project(":base")
implementation project(":feature-2-detail")
...

Функция 2 - Подробно

...
implementation project(":base")
implementation project(":feature-1-detail")
...

Функция 3 - Подробно

...
implementation project(":base")
implementation project(":feature-1-detail")
...

А вот мои навигационные графики:

Особенность 1 - Деталь

<navigation ...
    android:id="@+id/graph_feature_1_id">
    <include app:graph="@navigation/graph_feature_2" />
    <fragment ...
        android:id="@+id/nav_feature_1">
        <action ...
            app:destination="@+id/graph_feature_2_id" />

    </fragment>
</navigation>

Функция 2 - Подробно

<navigation ...
    android:id="@+id/graph_feature_2_id">
    <include app:graph="@navigation/graph_feature_1" />
    <fragment ...
        android:id="@+id/nav_feature_2">
        <action ...
            app:destination="@+id/graph_feature_1_id" />

    </fragment>
</navigation>

Функция 3 - Подробно

<navigation ...
    android:id="@+id/graph_feature_3_id">
    <include app:graph="@navigation/graph_feature_1" />
    <fragment ...
        android:id="@+id/nav_feature_3">
        <action ...
            app:destination="@+id/graph_feature_1_id" />

    </fragment>
</navigation>

Итак, все работает с такого рода настройкой, но проблема здесь в том, что для подключения модуля к другому модулю мы должны добавить другую функцию в качестве зависимости от текущей функции. Как и в моем случае, Feature 1 - Detail может перейти к Feature 2 - Detail и наоборот, и при этом я получаю круговую зависимость в gradle.

Есть ли другой способ сделать многомодульную навигацию? Я пытался использовать глубокие ссылки, но безрезультатно.

Любая помощь будет оценена! Спасибо!

Ответ 1

Это уже год, но библиотека теперь может поддерживать именно этот вариант использования! Начиная с 2.1.0-alpha03 мы можем перемещаться по URI с глубокими ссылками.

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

Особенность 1 - Деталь - build.gradle

dependencies {
    implementation project(':base')
}

То же самое с функцией 2 - подробности. Нет необходимости знать другие модули.

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

Особенность 1 - Деталь - График навигации

<navigation ...
    android:id="@+id/graph_feature_1_detail_id">
    <fragment ...
        android:id="@+id/nav_feature_1_detail">
        <deepLink app:uri="myApp://feature1detail"/>

    </fragment>
</navigation>

Функция 2 - Подробно - График навигации

<navigation ...
    android:id="@+id/graph_feature_2_detail_id">
    <fragment ...
        android:id="@+id/nav_feature_2_detail">
        <deepLink app:uri="myApp://feature2detail"/>

    </fragment>
</navigation>

Теперь, когда у нас есть глубокие ссылки с установленными URI, мы можем напрямую использовать это в NavController

Так во фрагменте в Feature 1 - Detail, может быть, по нажатию кнопки? Везде, где вы должны выполнять навигацию

class Feature1DetailFragment {
   fun onViewCreated(...) {
       ...
       view.setOnClickListener {
           val uri = Uri.parse("myApp://feature2detail")
           findNavController().navigate(uri)
       }
   }
}

А в Feature 2 - Detail,

class Feature2DetailFragment {
   fun onViewCreated(...) {
       ...
       view.setOnClickListener {
           val uri = Uri.parse("myApp://feature1detail")
           findNavController().navigate(uri)
       }
   }
}

И вуаля! Межмодульная навигация.

На момент написания статьи последний стабильный выпуск был 2.1.0-rc01.

Хотя я не пробовал это на более сложных проектах, мне нравится эта библиотека, и я надеюсь, что эта библиотека станет более зрелой!

Я создал среднюю статью об этом. Вы можете взглянуть на это. Ура!

Ответ 2

Можно удалить все зависимые зависимости Gradle, когда вы объявляете каждый идентификатор графического символа функции явно в базовой функции. Я не на 100% удовлетворен этим решением, так как эти идентификаторы создают "скрытые" зависимости между функциями, но в остальном они отлично работают.

Вот основные части этой настройки:

:приложение

build.gradle

dependencies {
    implementation project(':features:feature-base')
    implementation project(':features:feature-one')
    implementation project(':features:feature-two')
}

: особенности: функция базы

build.gradle

dependencies {
    application project(':app')
    feature project(':features:feature-one')
    feature project(':features:feature-two')
}

навигации /feature_base_nav_graph.xml

<navigation ...>
    <include app:graph="@navigation/feature_one_nav_graph" />
    <include app:graph="@navigation/feature_two_nav_graph" />
</navigation>

Значения /feature_base_ids.xml

<resources>
    <item name="feature_one_nav_graph" type="id" />
    <item name="feature_two_nav_graph" type="id" />
</resources>

: особенности: функция-один

build.gradle

dependencies {
    implementation project(':features:feature-base')
}

навигации /feature_one_nav_graph.xml

<navigation
    android:id="@id/feature_one_nav_graph"
    ...>

    <fragment
        android:id="@+id/oneFragment"
        ...>
        <action
            android:id="@+id/navigateToFeatureTwo"
            app:destination="@id/feature_two_nav_graph"
            ... />
    </fragment>

</navigation>

проводить

findNavController().navigate(R.id.navigateToFeatureTwo)

: Особенности: функция-два

build.gradle

dependencies {
    implementation project(':features:feature-base')
}

навигации /feature_two_nav_graph.xml

<navigation
    android:id="@id/feature_two_nav_graph"
    ...>

    <fragment
        android:id="@+id/twoFragment"
        ...>
        <action
            android:id="@+id/navigateToFeatureOne"
            app:destination="@id/feature_one_nav_graph"
            ... />
    </fragment>

</navigation>

проводить

findNavController().navigate(R.id.navigateToFeatureOne)

Ответ 3

Один из подходов, который может быть полезен, - это создание совершенно нового независимого модуля (например, модуля ": навигация") и перемещение в него всех файлов navigation.xml из всех других модулей. Затем мы зависим от этого нового (": navigation") модуля во всех других модулях, где требуется материал, связанный с навигацией, и мы сможем получить доступ к его R.navigation или сгенерированным классам аргументов и т.д.

Поскольку новый модуль (": navigation") ничего не знает в IDE проекта, он будет помечать красным любой фрагмент, действие и другие классы, которые мы используем в файлах navigation.xml, которые определены снаружи в других модулях, но пока мы используйте полные имена классов (com.exampel.MyFragment), они будут компилироваться и работать.

<?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"
    android:id="@+id/nav_graph_id"
    app:startDestination="@id/some_navigation_id">

    <fragment
        android:id="@+id/some_navigation_id"
        android:name="com.exampel.MyFragment".../>
        // com.exampel.MyFragment will be marked red since IDE can't link it
        // to the existing class because it is in the other module

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