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.
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.
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.
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.
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.
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:
navigation
taginclude
.Module1
I haveinterface Module1Navigation {
fun navigateToFragment2(arg1: String, arg2: Int)
...
}
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
...
}
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With