Dagger2's 2.10 and 2.11 updates brought Android developers a new set of APIs designed to make injecting Android components even easier. This post will explore how you can take advantage of these new classes and remove some injection boilerplate from your Android components.
I am going to assume that you are already familiar with using Dagger2 as your app's dependency injection framework, but that maybe you haven't heard of these more recent Android additions.
First let's talk about what we mean by "Android components". We're talking about the Android classes that you interact with in your application that you are not in charge of creating. The most obvious example of this is any instance of an Activity, but the Dagger2 updates also provide an easier way to inject Fragments, Services, BroadcastReceivers, and ContentProviders.
We will need to include a few new dependencies before we can begin writing code that takes advantage of the updates.
Once we have added the new dependencies we will need to make sure that we install the AndroidInjectionModule in our ApplicationComponent. This module isn't doing anything magical. It is simply providing 5 different maps that are keyed on class and provide values that implement the AndroidInjector.Factory interface.
The majority of our work in this post will be in writing some dagger code that fills these maps up so that at runtime they can be used to inject each of our Android components. You can see the source code for the class right here:
If you've been using Dagger2 then you are probably familiar with how Module classes work, but you might not have seen or used the multibinding feature.
This is one of the more powerful features provided by Dagger2 because it will let us define a Set or a Map in one module and then let us provide values for that set or map in any other module in our application. It even works from modules that are part of child subcomponents of the declaring module. This is the key piece of the Dagger2 puzzle that allows the AndroidInjection APIs to do their magic.
We install the AndroidInjectionModule in our root component and then in each of our subcomponents we install a module that provides an AndroidInjector.Factory for a given Android "component"(activity/fragment etc...). We will look at exactly how we do this further down in the blog post, for now though, just understand that by installing the AndroidInjectionModule in our root component we are simply providing some maps to our dagger graph that will enable us to do injection in our android components with less boilerplate at the injection site.
Let's move on to the next step in the process by creating a subcomponent for our app's MainActivity. A very simple verison might look like this:
This is just a plain old Dagger2 subcomponent with one important addition. It implements AndroidInjector
seedInstance(T instance) method which we will look closer at in just a moment.
Once we have our activity subcomponent that is also an AndroidInjector we will need to build a module that installs this object into one of the multi bound maps provided via the AndroidInjectionModule class.
We use @Binds because we are providing the implementation for the AndroidInjector.Factory interface. The @IntoMap and @ActivityKey are used to make sure that the value provided is placed into the appropriate map. Dagger provides several "key" annotations for use in multi-bound maps, it even includes @ClassKey for use in maps where class is used as the key, but we use @ActivityKey here because it adds the additional restriction that the class must inherit from Activity.
We are also setting the
subcomponents value of our @Module annotation to the class for our Activity component. This will ensure that the subcomponent class we list becomes a subcomponent of whatever component we install our module in. We will be installing this module in our root component so our Activity component will automatically be a subcomponent for that root component.
The final step is to make sure that our application class is implementing
HasActivityInjector and that it provides a
DispatchingAndroidInjector<Activity> instance via that interface's
HasActivityInjector interface let's dagger know that this class has an instance of AndroidInjector
DispatchingAndroidInjector<T> is a dagger provided implementation of the AndroidInjector
Once we've done all that we can inject our activity with one simple call in our
This not only eliminates all of the boilerplate that we had to write around creating a subcomponent instance and then using it to inject our activity, it also saves us from violating one the principles of dependency injection, a class shouldn't know the details of how it is injected.
Now at this point you might be asking yourself "How can I provide the instance of the activity to a dependency within my activity scoped subcomponent?".
In the old way you would have simply passed
this to either an @BindsInstance method on your subcomponent or passed it into the constructor of some module installed in your subcomponent.
With the new Android injection APIs Dagger has you covered because it will automatically make the activity instance available.
If you need to provide something else, like maybe an argument that gets passed in via an intent then you will need to override the
seedInstance method of your subcomponent builder.
Let's look at a simple example of how you might do that
In the example we override
seedInstancce and manually create an instance of a module that needs both the activity instance and the intent extra in its constructor. We then make sure we pass the instance of that module to our subcomponent's builder so that dagger can successfully create the subcomponent.
One important thing to keep in mind is that the components that are created and injected in this way are scoped to the instance of the activity. This may be different from how you are currently handling your "activity" scoped components and is very important to keep in mind when migrating a project to these new APIs.
Hopefully this post has helped explain how you can make use of dagger's new Android specific APIs.