web-dev-qa-db-fra.com

Navigation multi-module avec composants d'architecture

J'ai donc cette structure pour mes modules dans mon application actuelle.

App Module Structure

Je n'ai pas encore trouvé de documentation officielle sur la navigation multi-modules mais j'ai trouvé ceci article à ce sujet alors voici comment sont mes fichiers gradle:

Caractéristique 1 - Détail

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

Fonctionnalité 2 - Détail

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

Caractéristique 3 - Détail

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

Et voici mes graphiques de navigation:

Caractéristique 1 - Détail

<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>

Fonctionnalité 2 - Détail

<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>

Caractéristique 3 - Détail

<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>

Donc, tout fonctionne avec ce type de configuration, mais le problème ici est que pour connecter le module à un autre module, nous devons ajouter l'autre fonctionnalité en tant que dépendance à la fonctionnalité actuelle. Comme dans mon cas, Fonctionnalité 1 - Détail peut aller à Fonctionnalité 2 - Détail et vice versa et cela me donne une dépendance circulaire en gradle.

Existe-t-il une autre façon de faire de la navigation multi-modules? J'ai essayé d'utiliser des liens profonds mais en vain.

Toute aide serait appréciée! Merci!

17
Kurt Acosta

Cela fait déjà un an, mais la bibliothèque peut désormais prendre en charge ce cas d'utilisation exact! Depuis 2.1.0-alpha , nous pouvons naviguer à travers les URI de liens profonds.

Au lieu d'ajouter les fonctionnalités en tant que détails d'implémentation les unes aux autres, nous pouvons les laisser ignorer entre elles et utiliser la navigation par lien profond.

Fonctionnalité 1 - Détail - build.gradle

dependencies {
    implementation project(':base')
}

Même chose avec Fonction 2 - Détail . Pas besoin de connaître les autres modules.

Pour avoir une navigation inter-modules, nous devons d'abord définir le lien profond pour naviguer à travers cette destination via une balise deepLink.

Fonctionnalité 1 - Détail - Graphique de navigation

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

    </fragment>
</navigation>

Fonctionnalité 2 - Détail - Graphique de navigation

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

    </fragment>
</navigation>

Maintenant que nous avons des liens profonds avec les URI définis, nous pouvons directement l'utiliser dans un NavController

Donc, dans le fragment de Feature 1 - Detail , peut-être sur un clic de bouton? Partout où vous devez effectuer la navigation

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

Et dans Feature 2 - Detail ,

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

Et le tour est joué! Navigation inter-modules.

Au moment de la rédaction, la dernière version stable est 2.1.0-rc01.

Bien que je n'ai pas essayé cela sur des projets plus complexes, j'adore cette bibliothèque et j'espère voir cette bibliothèque mûrir davantage!

J'ai créé un article moyen à ce sujet. Vous pouvez y jeter un œil. À votre santé!

10
Kurt Acosta

Mise à jour 2:

Citant la discussion d'un outil de suivi des problèmes problème

Dev:

Vous vous demandez si nous pourrions utiliser la même chose avec des modules ordinaires (modules de fonctionnalités non dynamiques)?

Ingénieur Google:

Oui, vous pouvez également l'utiliser avec des modules non dynamiques.

Dev:

Avoir des documents serait bien pour une utilisation avec des modules non dynamiques.

Pouvez-vous s'il vous plaît me donner un exemple de code sur la façon de l'utiliser avec des modules non dynamiques?

Ingénieur Google:

Tout navigation-dynamic-* les composants s'étendent de leurs homologues non dynamiques. Vous devriez pouvoir les utiliser de la même manière que l'utilisation de la bibliothèque de navigation. Si vous rencontrez des bogues sur ce chemin, veuillez nous en informer par dépôt d'un problème .

Limitations de navigation-dynamic-* sont couverts ici .


Mise à jour 1:

Maintenant la bibliothèque Dynamic Navigator est sortie au niveau du développement.

Configuration

dependencies {
    def nav_version = "2.3.0-alpha01"

    api "androidx.navigation:navigation-fragment-ktx:$nav_version"
    api "androidx.navigation:navigation-ui-ktx:$nav_version"
    api "androidx.navigation:navigation-dynamic-features-fragment:$nav_version"
}

Utilisation de base

Pour prendre en charge les modules de fonctionnalités dynamiques, changez d'abord toutes les instances de NavHostFragment dans votre application en androidx.navigation.dynamicfeatures.fragment.DynamicNavHostFragment:

<fragment
    Android:id="@+id/nav_Host_fragment"
    Android:name="androidx.navigation.dynamicfeatures.fragment.DynamicNavHostFragment"
    app:navGraph="@navigation/nav_graph"
    ... />

Ensuite, ajoutez un app:moduleName attribut à tout <activity>, <fragment>, ou <navigation> destinations dans vos graphiques de navigation associées à un DynamicNavHostFragment. Cet attribut indique à la bibliothèque Dynamic Navigator que la destination appartient à un module d'entités dynamiques portant le nom que vous spécifiez.

<fragment
    app:moduleName="myDynamicFeature"
    Android:id="@+id/featureFragment"
    Android:name="com.google.Android.samples.feature.FeatureFragment"
    ... />

Lisez le docs pour plus d'informations.


Il existe maintenant une solution au niveau du développement pour naviguer entre les modules de fonctionnalités dynamiques par Android Team. Mais il semble que cela puisse également être utilisé pour naviguer entre les modules de fonctionnalités standard.

3
user158

L'une des approches qui pourrait être utile consiste à créer un tout nouveau module indépendant (par exemple, le module ": navigation") et à y déplacer tous les fichiers navigation.xml de tous les autres modules. Ensuite, nous dépendons de ce nouveau module (": navigation") dans tous les autres modules où des éléments liés à la navigation sont nécessaires, et nous pourrons accéder à sa R. navigation et aux classes d'arguments générées, etc.

Étant donné que le nouveau module (": navigation") ne sait rien d'autre dans le projet IDE marquera en rouge tout fragment, activité et autres classes que nous utilisons dans les fichiers navigation.xml, qui sont défini à l'extérieur dans d'autres modules, mais tant que nous utilisons des noms de classe complets (com.exampel.MyFragment), il compilera et fonctionnera.

<?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

Cela crée une dépendance "cachée" à toutes les classes vers lesquelles nous voulons naviguer d'une manière dont nous devons connaître les noms de classe et potentiellement les arguments, et nous devons la maintenir manuellement mais cela nous permet de séparer facilement la navigation dans un module indépendant.

1
doolle89

Il est possible de supprimer toutes les dépendances entre fonctionnalités Gradle lorsque vous déclarez explicitement chaque ID de graphique de navigation d'entités dans l'entité de base. Je ne suis pas satisfait à 100% de cette solution car ces identifiants créent des dépendances inter-fonctionnalités "cachées" mais sinon cela fonctionne très bien.

Voici les éléments clés de cette configuration:

: app

build.gradle

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

: fonctionnalités: base de fonctionnalités

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>

: fonctionnalités: fonctionnalité-un

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>

naviguer

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

: fonctionnalités: fonctionnalité-deux

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>

naviguer

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