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

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

Структура модуля приложения

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

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

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


person Kurt Acosta    schedule 10.07.2018    source источник
comment
Вы думали об использовании глубинной ссылки для навигации? Я планировал начать рефакторинг для настройки функционального модуля, но я столкнулся с той же проблемой, и единственное решение, которое я, хотя и использую, - это использовать deeplink.   -  person Renato Almeida    schedule 12.10.2018
comment
Для действий я использую глубокие ссылки для выполнения многомодульной навигации, но для фрагментов я все еще пытаюсь найти решение.   -  person Kurt Acosta    schedule 15.10.2018
comment
Вы можете предоставить идентификатор целевого фрагмента с помощью инъекции зависимостей на уровне модуля приложения   -  person Ali mohammadi    schedule 21.10.2018


Ответы (3)


Это уже год, но теперь библиотека может поддерживать именно этот вариант использования! Начиная с 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)
       }
   }
}

А в Особенность 2 - Подробности

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

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

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

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

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

person Kurt Acosta    schedule 14.08.2019
comment
очень ценю то, что вы сделали. твоя статья мне очень помогает - person mochadwi; 21.03.2020
comment
Были ли у вас проблемы с обратной навигацией? У меня возникла эта проблема, когда я перехожу с экрана A (модуль приложения) к B (модуль B), а затем к C (модуль C). когда i C, навигация назад перестает работать. однако, если я перейду прямо от A к C, все работает нормально. - person takecare; 13.06.2020
comment
@Kurt Acosta Как я могу отправить Parcelable объект из Feature1 в Feature2? - person prashant17; 14.07.2020
comment
Привет @Kurt! Скажите, пожалуйста, нужно ли нам определять эти uri в manifest или build.gradle? Я просто следил за вашим руководством, и у меня это не сработало. - person Munazza; 25.08.2020
comment
Я просто добавил deeplink в график навигации и попытался перейти от одного фрагмента к другому в коде. Но я получаю следующую ошибку. Пункт назначения навигации, соответствующий запросу NavDeepLinkRequest {uri = app: // verily / LoginFragment}, не может быть найден в графе навигации NavGraph (com.app.verily: id / splash_navigation) startDestination = {Destination (com.app.verily: id / splashFragment ) label = Splash class = com.app.launch.splash.SplashFragment} Пожалуйста, помогите! - person Munazza; 26.08.2020
comment
@mochadwi, пожалуйста, поделитесь шаблоном рабочего кода. Мои ссылки на контент не работают. - person Munazza; 26.08.2020
comment
моя глубокая ссылка также не работает в модуле-модульном взаимодействии. получение ошибки, например: пункт назначения навигации, соответствующий запросу NavDeepLinkRequest {uri = news: // newslist} не может быть найден в графе навигации NavGraph (com.rp.th.uat: id / nav_home) startDestination = {Destination (com.rp.th .uat: id / homeFragment) label = Класс главного экрана = com.rp.home.presentation.view.fragments.HomeFragment} - person sweet_vish; 19.09.2020
comment
Как я могу контролировать поведение popUpTo с помощью этого решения? - person A1m; 23.09.2020
comment
@ A1m вы можете найти URI другого модуля? - person sweet_vish; 28.09.2020
comment
Это хороший обходной путь, но он не кажется естественным решением. Надеюсь, они подойдут к этому вопросу более элегантно. - person Yekta Sarıoğlu; 06.12.2020
comment
Для тех, кто получает NavDeepLinkRequest, не может быть найден в ошибке графа навигации, компонент навигации должен сопоставить эти графы где-то, чтобы знать пункты назначения. Поэтому, если у вас есть приложение, зависящее от других модулей, откройте приложение nav_graph и включите другие графики, например <include app:graph="@navigation/detail_nav_graph" />. После этого вы можете перейти к целевому URI - person volsahin; 28.02.2021

Один из подходов, который может оказаться полезным, - создать полностью новый независимый модуль (например, модуль ": navigation") и переместить в него все файлы 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

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

person doolle89    schedule 20.03.2019

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

Вот ключевые части этой установки:

: приложение

build.gradle

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

: features: feature-base

build.gradle

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

navigation / feature_base_nav_graph.xml

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

values ​​/ feature_base_ids.xml

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

: features: feature-one

build.gradle

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

navigation / 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)

: features: feature-two

build.gradle

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

navigation / 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)
person laenger    schedule 31.07.2018
comment
Хотя это решение может работать, я считаю, что приложение или модуль мгновенного приложения будут там, где функции будут объединены. базовый модуль просто предоставляет общий код функциональным модулям. С помощью действий я могу легко перемещаться от модуля к модулю по ссылкам на приложения / приложения, но с фрагментами я не могу найти обходного пути. - person Kurt Acosta; 14.08.2018
comment
Для перехода между фрагментами, в остальном независимыми друг от друга, я использую описанную выше настройку как обходной путь. Это правда, что объединение feature-one, а не feature-two в модуль приложения вызовет сбой во время выполнения при попытке перехода от feature-one к feature-two. Если этого и следовало ожидать, это нужно было бы обработать в коде. Меня также очень интересуют альтернативные подходы. - person laenger; 14.08.2018
comment
Я просто добавил к ответу файл app / build.gradle, которого раньше не было. - person laenger; 14.08.2018
comment
Если это так, то я предполагаю, что app/build.gradle потребуется только получить базовый модуль, поскольку в базовом модуле уже есть все. Это сработает, но, как вы сказали, это не 100% удовлетворительное решение. В любом случае, я сначала воспользуюсь этим решением! Спасибо за ваше предложение! - person Kurt Acosta; 15.08.2018
comment
Имейте в виду, что описываемая вами зависимость feature project(...), а не implementation project(...). Только включение feature-base в app / build.gradle не приведет к автоматическому включению других функций (кстати, добавление зависимости реализации от базы к функции создаст циклическую зависимость). См. Также здесь и здесь. - person laenger; 15.08.2018
comment
@KurtAcosta, не могли бы вы поделиться шаблоном этого, или, пожалуйста, дайте мне знать, как вы используете application (: app) в build.gradle - person Reprator; 30.06.2019