Dagger Component Dependencies for Library Development

Mike Nakhimovich
ProAndroidDev
Published in
6 min readMar 9, 2019

--

Android developers are all app developers. A growing portion of us are also library developers. When building apps, I am a strong advocate for dependency injection (DI). DI gives us advantages such as the ability to externalize our dependencies, ease in testing, and most importantly, reusability. Lately I’ve been writing more and more libraries rather than client apps which has made me rethink how I use Dagger.

Normally my apps have an app component, an activity subcomponent and a view/screen subcomponent. Each subcomponent has access to all the dependencies of its parent component.

Unfortunately, that setup breaks down if, for example, activities live anywhere except inside the app module because there is a coupling between the parent and its subcomponents (Dagger-Android has a solution for this that we will not be covering in this article). Instead, using the concept of component dependencies, I settled on the following workflow that allows any host app to pass dependencies into libraries, regardless of whether they use Dagger or not.

Let’s pretend we are all working at Kittn, an up-and-coming cat photo app. Our app started as a single feed of cat photos, but Kittn has been growing by 20% monthly (take that, Zuch!). All the Kittn code was previously in a single module, but now that we have funding, and a team of 100+ devs, we can’t realistically have everyone work in the same Gradle module.

My team in particular was tasked with creating a comments module that can be integrated into the app. (For consistency, we’ll call this module Commentr, in case we decide to spin it off and sell it as its own service in the future). The host app would call a function that is exposed from my library

CommentNavigator.goTo(CommentWithId(5), this)

which will launch a Comment Activity. So far, so good. The app module has a module dependency on comment module and we are off and running.

Next, product comes back to our team and says that they have no idea how well comments are doing because we don’t have any analytics. Well…that’s not the full story. The Kittn app is using Firebase Analytics but that implementation lives inside the app and not inside the comments module.

Additionally, the company is spiraling up a new app Puppr which, as you guessed, will also want to use my team’s Commentr library. Puppr, however, is using another analytics provider.

Luckily for both apps, our module was already using Dagger and will have no problem working with many implementations of a common interface. Let’s get down to it.

Within Commentr we have a comment activity which contains a presenter CommentPresenter

We also define within Commentr the following interfaces to allow us to log analytics:

RealCommentPresenter is our implementation and will be provided by a DaggerCommentComponent along with any dependencies that it has:

Notice that RealCommentPresenter depends on the interface not the implementation of HostAnalytics. So how do we actually get the implementation? Component dependencies, that’s how!

First we create our CommentComponent

This is the component that will know how to provide dependencies that live within our Commentr module — things like our database or API. Normally, we would create an instance of this component, retain it somewhere and then perform injection with it. But we still have the issue of a circular dependency — somehow needing to depend on the host’s analytics implementation without creating a circular dependency between the comment and app module. The Kittn app module depends on the Commentr module; we can’t also set the Commentr module to depend on the app module. Instead we will set up a component dependency and only have a dependency on an interface. Per the Dagger javadoc:

While subcomponents are the simplest way to compose subgraphs of bindings, subcomponents are tightly coupled with the parents; they may use any binding defined by their ancestor component and subcomponents. As an alternative, components can use bindings only from another component interface by declaring a component dependency.

Our Kittn app module already has an implementation for Analytics that looks to be really perfect for what we need within Commentr:

How about we add our Comments HostAnalytics interface here

Now KittnFirebaseAnalytics will be an implementation of HostAnalytics. Unfortunately, it’s still in the wrong module (Commr can’t depend on the Kittn app module because the app module depends on Commentr). Wouldn’t it be nice if we can somehow pass this implementation directly into CommentComponent during its construction?

First, we are going to add a component dependency on the interface HostComponent. Remember that HostComponent is nothing more than an interface that can provide an implementation of HostAnalytics:

When we run our build, Dagger will generate an additional builder method hostComponent that will allow us to pass and set an instance of the HostComponent:

Within Commentr:

Within Kittn Application Class or wherever you inititialize the Commentr library:

Let’s unpack the above a bit… by passing in an implementation of HostComponent into our CommentComponent builder, CommentComponent’s object graph will now contain every object that was provided from the HostComponent’s implementation. Provided just means that some function within the passed in implementation returns a certain type. In our case the type that HostComponent provides is HostAnalytics. Since we have a function that was returning HostAnalytics, CommentComponent’s graph will now be able to provide HostAnalytics in the same way that it can provide anything that lives in a dagger CommentModule.

One of the things I absolutely love about this setup is that I can test my Commentr module in isolation by just passing fakes/stubs through the HostComponent interface. Another advantage is that CommentComponent does not care what implements HostComponent. This means that if I have an app with the following app component:

As long as AppComponent has an instance of HostAnalytics in its object graph, We can have AppComponent extend HostComponent and, it will fulfill our interface’s requirement.

If I get a new requirement and now need to pass an Image Loader from our app to our library we can add an additional providesMethod within our HostComponent interface like

Now my AppComponent will need to also satisfy providing an ImageLoader implementation. If it doesn’t dagger will not let me compile. Yay for failing fast

Wrapping up:

While subcomponents work well when you have a single module or want to have every subcomponent to have every dependency from the parent component, component dependencies work well if you want to set a relationship that defines exactly what a parent component exposes to the child. I have used the above pattern to solve challenges like saving data to the app’s db, delegating click handling back to a host, pulling configuration from the host app & even passing a view container to a library module within a SingleActivityApplication.

Happy Daggering

--

--