Setting up Dagger in a multi-module project is non-trivial. Usual solution I’ve seen is to use dagger.android. I dare to say I know my way around Dagger but the android library never clicked for me, as it seemed too magical. So didn’t bother using it. And it seems it was the right choice as Google seems to abandon it.
The pragmatic way of doing things in a single module app in my opinion is to have
AppComponent instance lives in Application subclass, and you access it via casting
ApplicationContext. I also use (Sub)Components per feature, to inject
ViewModels. But I’m sure having them in a single module and use assisted injection etc., is fine also. This is pretty standard and common setup.
Where this breaks down, is in a multi-module (gradle) setup. Issue is the cyclical reference nature of
AppComponent and the call site inside of the feature module.
Obviously there are many ways to modularize, but in my opinion the most basic and scalable is modularizing by feature. That yields a diamond shaped module graph, with
:base module for shared code, bunch of
:featureN which depend on
:base, and at the tip
:app module which depends on features, containing only minimal code.
However now in a feature, given the direction of module dependencies, there is no way to access the AppComponent.
So we need to jump some hoops. Luckily Dagger allows the @Component annotated interface to extend some other interface. This is the key.
Create the base AppComponent interface in
(note that it’s a plain interface, no dagger annotation). Now create the
:app and slap the
@Component annotation on it
Instantiate the component like this
And feature looks like this
Note that in the sample I use Conductor’s Controller instead Fragment, and the ViewModel is a simple interface, not necessarily the Jetpack’ one. But it’s the same concept.
This works and is relatively straight forward. However doesn’t allow for breaking apart :base module, which will grow over time and will be your bottleneck in compile times. So we want to break :base apart. Don’t do this prematurely as it complicates things, but you should have a direct line of sight as how to do it, when needed.
Breaking :base apart
Okay, let’s say we have a
MessageRepository. It’s a singleton, you only want one instance per app.
MesageRepository is used by
Let’s also say it becomes too big, you break it’s implementation into several classes, or you want to share this logic with some other project you’re doing i.e. it warrants its own
:messages module. So we extract the
:base and move it into the new
The issue is, how to attach
MessageRepository to the
AppComponent, which lives in
:base has no knowledge of
:messages while still keeping it singleton.
:messages should expose a public
@Module (or use
@Inject constructor if you’re that kind of a person).
Second, let’s again assume our
Feature1ControllerComponent which provides
MessageRepository is no longer present in the
AppComponent definition, how should you pass in the dependencies parameter?
The trick with Dagger is that @Component(dependencies=[…]) allows to take any random interface, not just @Component annotated ones! The interface just has to have getters which match with the dependency types our module needs. I wish somebody told me this earlier
So, let’s declare a feature module local “partial” app component
Now let the
AppComponentImpl extend it and attach the
AppComponent you can access the instance again via getting the application context, casting it to
AppComponentProvider and casting the
That’s it. It is quite to a lot to digest, but at least there’s no magic involved.
Thanks for reading!