Dagger & Android thoughts: Dependency injection in Android
I’ve been researching about Dagger & Android to see if I should start using it in my projects or not. But before I get into that, here’s some context for this post.
Edit 4/2/18: This article specifically talks about the dagger.android package, not about Dagger in general as a DI solution for Android.
Edit 8/8/18: After few ideas from the community, this is currently under investigation. I’ll update the post with more information soon. After all, it might be possible to have Dagger Android working with multiple Component layers.
Who is This Article For?
- You know about Dependency Injection in Android.
- You know how to use Dagger 2.
- You have heard of Dagger & Android but are not sure whether you should use it and what its benefits are.
Benefits of Dagger & Android
From the Dagger documentation — Many Android framework classes are instantiated by the OS itself, like Activity and Fragment. You have to perform members injection in a lifecycle method which causes a few problems:
- Copy-pasting code makes it hard to refactor later on.
- It requires the type requesting injection to know about its injector.
Dagger Android Injections offers one approach to simplify Dependency Injection with Dagger in Android.
Disclaimer
This article is based on my understanding of how Dagger & Android v2.15works.
There’s an open issue on the Dagger Github to clarify this. I’m looking forward to hearing back from Google or Dagger including this functionality in future versions.
When should I use it?
From my point of view, it’s a really good solution for small projects , but I don’t see it scaling well in larger applications for a few reasons.
Potential drawbacks for a large app
- All subcomponents extend from the ApplicationComponent.
- Because of the above point, all the components will need to declare which modules they use without being able to extract them out and modularize the graph properly. There’s only one level of abstraction.
- Any Android class that uses DI needs to be declared in the ApplicationComponent.
- Testing would require the whole structure to be duplicated with test instances, meaning not having that much control over the mocks.
IMO, all these points are a red flag in a large app. A large application should be structured in a more composable way with the ability of extracting common logic to reusable components.
If the above points matter for your use case, Dagger & Android is maybe not the DI tool for you.
However, there are use cases where Dagger & Android make sense.
Common use case
How would the Graph look with just one feature and two screens if you use Dagger & Android?
Dagger & Android doesn’t allow you to have common (sub)components making it harder to structure and reuse code.
That graph doesn’t scale. Can you image how it would be with eight features and three screens per feature?
I’d prefer having a custom solution based on Dagger with a better and less error-prone structure that allows us to reuse as much code as possible. What about something like this?
Dagger & Android generated code
Let’s take a look at how Dagger & Android works under the hood. To inject an Activity, the only thing you have to do is:
AndroidInjection.inject(this)
1. What is AndroidInjection.inject doing?
public static void inject(Activity activity) {
checkNotNull(activity, "activity");
Application application = activity.getApplication();
// GET THE APPLICATION OBJECT. IF IT IS NOT INSTANCE OF
// HASACTIVITYINJECTOR THEN THROW AN ERROR
if (!(application instanceof HasActivityInjector)) {
throw new RuntimeException(
String.format(
"%s does not implement %s",
application.getClass().getCanonicalName(),
HasActivityInjector.class.getCanonicalName()));
}
// GET THE ANDROID INJECTOR THAT ATTACHES SUBCOMPONENT AND ACTIVITY
AndroidInjector activityInjector =
((HasActivityInjector) application).activityInjector();
checkNotNull(activityInjector, "%s.activityInjector() returned null", application.getClass());
// INJECT IT
activityInjector.inject(activity);
}
2. How does it know which subcomponent builder to use?
When you map an Activity with its Builder in a Module attached to the ApplicationComponent with some code like so:
@Module
abstract class ActivityBuilder {
@Binds
@IntoMap
@ActivityKey(MainActivity::class)
abstract fun bindMainActivity(builder:MainSubcomponent.Builder):
AndroidInjector.Factory
}
In your generated DaggerApplicationComponent.java class, it binds the Activity with the instance of the Builder you defined previous
private Map, Provider>>
getMapOfClassOfAndProviderOfFactoryOf() {
return MapBuilder
., Provider>>
newMapBuilder(1)
.put(MainActivity.class, (Provider) mainSubcomponentBuilderProvider)
.build();
}
3. Inject the builder to the class
When you call inject, it’s going to call the inject method on the instance of the Builder implementation defined in the second step.
Conclusion
To reiterate again on the point mentioned above: this article is based on my research, reading, and experimentation.
I recommend doing some of your own before deciding if Dagger & Android is suitable for your project.
Do you want to know more about Dagger? Check out this other article about Surviving configuration changes using Dagger.