Dagger, a Java dependency injection framework, has just released version 2.0 and I thought I would try my hand at migrating my Android app, which is using 1.0, to the new version. It took me a few hours on an small-ish app with 3 activities, 6 fragments, 3 singleton services and 2 dagger modules (Application-level and activity-level). This was mostly because I used a fair bit of time banging my head against a brick war. To be fair, 90% of the conversion was smooth, but the 10% really had me lost. I will lay out some of the brick walls I ran into. The Dagger 2.0 website already have a migration guide which is good starting point. This blog post is meant to be a supplement to that.
error: package javax.annotation does not exist
I’m using Android studio so using the new library was a matter of changing the build.gradle dependencies.
[java]
dependencies {
apt ‘com.google.dagger:dagger-compiler:2.0’
provided ‘org.glassfish:javax.annotation:10.0-b28’
compile ‘com.google.dagger:dagger:2.0’
[/java]
Pay attention to add the new line org.glassfish:javax… or you’ll get the error: package javax.annotation does not exist.
Use Android APT if you don’t want Cannot resolve symbol Dagger* in Android Studio
Previously, I used the ‘provided’ scope for dagger-compiler in the gradle dependencies, i.e.
[java]
provided ‘com.google.dagger:dagger-compiler:2.0’
[/java]
I encountered errors in the IDE where it could not find the dagger generated classes, but compilation and running the app was fine. This was fixed when I changed to using Android APT. After adding the Android APT configuration to build.gradle and replacing ‘provided’ with ‘apt’:
[java]
apt ‘com.google.dagger:dagger-compiler:2.0’
[/java]
then rebuilding, the error went away.
MyExtremeActivityComponent (unscoped) cannot depend on scoped components: @Singleton AppComponent.
Dagger 2.0 changes how you define scoping with sub-graphs. Previously with 1.0, any object marked @Singleton is only ‘singleton’ within the top-level-graph or sub-graph depending on the scope it was first instantiated. This behaviour was confusing but manageable once you understand the quirk. Dagger 2.0 has removed this confusion such that sub-graphs would have a custom defined scope attribute, commonly:
[java]
@Scope
@Retention(RUNTIME)
public @interface PerActivity {
}
[/java]
The top-level component should have @Singleton and the sub-graph should have @PerActivity, or you may get errors like: MyExtremeActivityComponent (unscoped) cannot depend on scoped components: @Singleton AppComponent.
objectGraph.plus migration (with @Subcomponent)
I couldn’t get this to work following the migration guide and eventually moved to using components with dependencies. The issue I faced was that I declared my plus() method like in the migration document but kept hitting error: Members injection methods may only return the injected type or void. Eventually I figured out that for the sub component, instead of using @Component (with or without dependencies= parameters), I have to use @Subcomponent. The documentation links to @Subcomponent but doesn’t provide a subcomponent example. Maybe it’s obvious to some, but certainly not to me at first. I’ve taken the example from the migration and completed it:
[java]
@Component(/* … */)
interface MyComponent {
/* … */
/* Functionally equivalent to objectGraph.plus(childGraphModule). */
MySubcomponent plus(ChildGraphModule childGraphModule);
}
/* declare subcomponent */
@Subcomponent
interface MySubcomponent {
/* … */
}
/* to use */
MySubcomponent mySubcomponent = myComponent.plus(new ChildGraphModule("child!"));
[/java]
Components with dependencies (a.k.a subcomponents)
Components with dependencies (or subcomponents, but not @Subcomponent) are basically sub-graphs in Dagger 2.0. The documentation on subcomponents is thin, but basically, you do something like:
[java]
@PerActivity
@Component(
dependencies = AppComponent.class,
modules = MyExtremeActivityModule.class
)
public interface MyExtremeActivityComponent {
}
[/java]
One important point the doco mentioned that was lost on me and thus caused me a lot of headaches, was that the parent components need to expose any bindings to the subcomponents. So if you want to inject LocationManager for example, in your parent module you will have:
[java]
@Provides @Singleton
LocationManager provideLocationManager() {
return (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
}
[/java]
And in your parent component interface you will need:
[java]
LocationManager provideLocationManager();
[/java]
Otherwise you will errors like: LocationManager cannot be provided without an @Inject constructor or from an @Provides- or @Produces-annotated method.
MyExtremeActivityComponent scoped with @PerActivity may not reference bindings with different scopes
So you have built an @Singleton service that you want to get from the parent component via your sub component and you get the above error – turns out as above, any services you want to expose to the consumers of your sub-graphs need to be explicitly declared in the parent component as well. Tedious stuff, but I guess eliminates ambiguity.
Conclusion
Functionality wise Dagger 1 and 2 are similar, but the major advantage of 2.0 is the removal of reflection, thus bringing a speed up in injection and also allows the use of proguard. The generated source is human-readable and supposedly debuggable.
Pingback: Try Android | Dagger 2
Necropost, but anyway :)
> Members injection methods may only return the injected type or void
I faced with the same error, it’s because “Subcomponent” annotation was not a dagger one, but from espresso, it has the annotation with the same name.