Dagger in a multi-module project without dagger.android

Vlastimil Brečka
3 min readDec 29, 2019

--

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.

Basics

The pragmatic way of doing things in a single module app in my opinion is to have AppComponent with @AppScope’d dependencies. 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 :base

(note that it’s a plain interface, no dagger annotation). Now create the AppComponentImpl in :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 :feature1 and :feature2.

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 MessageRepository from :base and move it into the new :messages module.

The issue is, how to attach MessageRepository to the AppComponent, which lives in :base, where :base has no knowledge of :messages while still keeping it singleton.

First, :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 Feature1ViewModel

Since now, MessageRepository is no longer present in the AppComponent definition, how should you pass in the dependencies parameter?

Solution

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

Since FeatureAppComponentextends AppComponent you can access the instance again via getting the application context, casting it to AppComponentProvider and casting the appComponent()to Feature1AppComponent.

That’s it. It is quite to a lot to digest, but at least there’s no magic involved.

Thanks for reading!

--

--