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?

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.15 works.

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 Graph
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?

Custom solution Graph

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<Activity> 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<out Activity>

}

In your generated DaggerApplicationComponent.java class, it binds the Activity with the instance of the Builder you defined previously.

private Map<Class<? extends Activity>, Provider<AndroidInjector.Factory<? extends Activity>>>
getMapOfClassOfAndProviderOfFactoryOf() {
return MapBuilder
.<Class<? extends Activity>, Provider<AndroidInjector.Factory<? extends Activity>>>
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 in Android.

Thanks for reading,

Manuel Vicente Vivo