Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SafeArgs not generating Directions / Args classes on a multi module project

I created an empty project to try out the Navigation component. I wanted to see how it would behave with a multi module project (one common module with the majority of the dependencies, plus modules that would hold different parts of the app, and the :app module that would implement all of the modules).

The top level gradle file has the dependency like so:

dependencies {
    classpath "com.android.tools.build:gradle:3.6.1"
    classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.61"
    classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.3.0-alpha04"
}

The common module has these dependencies (among others):

dependencies {
    …
    api "androidx.navigation:navigation-fragment-ktx:2.2.1"
    api "androidx.navigation:navigation-ui-ktx:2.2.1"
    api "androidx.navigation:navigation-dynamic-features-fragment:2.3.0-alpha04"
    …
}

And absolutely all the modules have this plugin applied on top:

apply plugin: "androidx.navigation.safeargs.kotlin"

Now, each "ui module" has fragments in it, and only the :app one implements a main_graph.xml that references to them. An example would be:

<fragment
    android:id="@+id/registerFragment"
    android:name="example.register.RegisterFragment"
    android:label="RegisterFragment">
    <action
        android:id="@+id/action_registerFragment_to_loginFragment"
        app:destination="@id/loginFragment" />
</fragment>

After all this is finished, a good Clean Project + Rebuild Project is done.

To me, this makes sense. However, when I try calling the theoretically auto generated file RegisterFragmentDirections is not there, not in the specific sub module, not in the main one. The NavDirections can be found (so the dependencies somehow are working), but not the generated ones.

I've tried implementing all the dependencies in each module, rolling back the navigation version to the previous alpha ones… no success.

like image 764
Sergi Juanola Avatar asked Mar 29 '20 17:03

Sergi Juanola


People also ask

What is safeargs in Android navigation?

But Navigation has something even better: SafeArgs. SafeArgs is a gradle plugin that allows you to enter information into the navigation graph about the arguments that you want to pass.

Can I use navigation directions and arguments in a separate module?

If you include all modules to main app module and create a single graph with all the included fragments, then the navigation directions and arguments files generated are generated for main app module. So, that would mean, you cannot use those inside a separate module.

Why do I need to pull safeargs from a Gradle?

First of all, I needed some library dependencies. SafeArgs is not the same kind of library module as the other parts of navigation; it’s not an API per se, but rather a gradle plugin that will generate code. So I needed to pull it in as a gradle dependency and then apply the plugin to run at build time, to generate the necessary code.

Can I use raw bundle arguments directly?

You could totally use raw Bundle s directly… but we recommend using SafeArgs instead. It’s not only easier to write — with a lot less code for you to maintain — but it also enables type-safety for your arguments, making your code inherently more robust.


1 Answers

I had the exact same requirement for my project as well. When I was searching for answers, I stumbled upon this, but none of the answer actually answered the question. So, here's how I solved it.

I have a main app module. Let's say app. I have about 7 feature modules with their own fragments and their own flow. From my experience this is what happens:

If you include all modules to main app module and create a single graph with all the included fragments, then the navigation directions and arguments files generated are generated for main app module. So, that would mean, you cannot use those inside a separate module. But, if you create navigation graphs in each module, and use that as included module from main graph, the main module will not know about those directions and arguments internal to the module's graph. And hence, therein lies the problem, catch 22 style.

How I solved it:

  1. Each module have their own navigation graph. Make sure they are id'ed in navigation tag
  2. Those graphs are included in main graph with include.
  3. In each module, I have defined an interface that defines all the navigation actions. For example, in Module1 I have
interface Module1Navigation {
    fun navigateToFragment2(arg1: String, arg2: Int)
    ...
}
  1. In the main app, I have a Navigator class that implements all these interfaces, like:
object Navigator: Module1Navigation, Module2Navigation, ... {
    private var navController: NavController? = null
    // bind in onResume for activity implementing the graph
    fun bind(navController: NavController) {
        this.navController = navController
    }

    // bind in onPause for activity implementing the graph
    fun unbind() {
        this.navController = null
    }
    // Implement all the members
    ...
}
  1. Each of the module level interfaces are injected with DI in each fragment that is gonna navigate away, or you could pass that in. I used DI to get it:
    private val navigation: Modudle1Navigation by lazy {
        XInjectionManager.findComponent<Modudle1Navigation>()
    }

With this setup now all modules are free to have their own graphs, their own safe args, and integrates well all together.

Best thing about this is also the fact that you module does not even need to know about this particular navigation framework, or how it is implemented, and is easy to scale.

Also, you can create each modules own app for more controlled module level quality assurance.

Hope this helps someone out there.

like image 121
Sujit Poudel Avatar answered Oct 16 '22 00:10

Sujit Poudel