Dependency injection on Android

This is the first part of a write up about Dagger 2 on Android.

Dagger 2

Dagger 2 is a dependency injection framework for Java built on the JSR 330 standard. It works in all Java projects but this article will focus on Android development where Dagger is de facto standard for dependency injection.

The latest version, Dagger 2, is developed by Google together with Square, the original developers of Dagger.

Dagger 2 works at compile time in comparison to Dagger 1 which used reflections. So, one of its main benefits is compile time errors instead of run time errors. Dagger 2 will also produce cleaner stack traces, run slightly faster and ProGuard works out of the box.

All usages of Dagger below refer to Dagger 2

Setup for Android:

Requires Android Gradle Plugin 2.3 or later

dependencies {
    compile 'com.google.dagger:dagger:2.9'
    annotationProcessor 'com.google.dagger:dagger-compiler:2.9'
    provided 'javax.annotation:jsr250-api:1.0'    
}

The dependencies are Dagger itself, the Dagger compiler that generate classes at compile time, and a library with all annotations we’ll need. Since Dagger does a lot of its work in compile time we’ll only need the Dagger-dependency at run time. annotationProcessor and provided tells Gradle that the compiler and annotation library shouldn’t be included in the final APK.

There is more than one package that you can use for Java annotations. So if you see examples with javax.annotation:javax.annotation-api:1.2 or org.glassfish:javax.annotation:10.0-b28 they all works just as well, I prefer the one above as it's the smallest of the 3.

Usage

In Android, we have 3 types of classes when working with Dagger: classes we have full control over, 3rd party- & Android classes that we instantiate but don’t control the constructor, and classes that Android instantiate for us such as Activities & Fragments.

Own classes

The first case is the simplest. We only need to add a @Inject annotation to an empty constructor to make our class injectable in other classes. And if our class have any dependencies we can just ask for them in the constructor.

public class MyClass1 {
    @Inject
    public MyClass1() {
    }
}

public class MyClass2 {

    private MyClass1 myClass1;

    @Inject
    public MyClass2(MyClass1 myClass1) {
        this.myClass1 = myClass1;
    }
}

3rd party classes

@Inject will of course not work on 3rd party classes since we can't annotate their constructor. In those cases, we'll have to use the @Provides annotation on a function that returns an instance of the class we want to inject elsewhere.

@Provides
ExternalDependency1 provideExternalDependency1() {
    return new ExternalDependency1();
}

The function can be named anything but Google recommends a provide-prefix. Like @Inject we can request dependencies we need directly as function parameters.

All @Provides function must belong to a module. Modules are just simple Java classes annotated with @Module and we can have more than one module in our project.

We can provide our own classes with this method as well. That is useful if we want more than one instance of our class or if our class need to be configured for example.

@Module
public class MyModule {

    @Provides
    ExternalDependency1 provideExternalDependency1() {
        return new ExternalDependency1();
    }

    @Provides
    ExternalDependency2 provideExternalDependency2(ExternalDependency1 externalDependency1) {
        return new ExternalDependency2(externalDependency1);
    }
}

Android instantiated classes

The final type is Activities, Fragments & Services. They are instantiated by Android so we can't annotate their constructors and we can't use @Provides.

In these cases, we'll annotate fields (or methods) with @Inject and ask Dagger to set them for us.

public class MyActivity extends Activity {

    //Fields can't be private since Dagger must have access to them
    @Inject
    MyClass myClass;

    @Inject 
    ExternalDependency externalDependency;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
        //TODO: Tell Dagger to inject all fields annotated with @Inject
    }
}

Dagger component

That's all we need to do to setup the dependencies. Now we only need to add a little Dagger boilerplate and we're done.

To do that we create a Dagger Component. It's a simple interface annotated with @Component and the modules you wish to use. Dagger will generate a class that can create a instance of that interface. That class is called Dagger%interface_name%, in our case DaggerMyComponent.

@Component(modules = MyModule.class)
interface MyComponent {

}

That class won't be available until Android Studio has built once so a manual rebuild might be required.

We can expose dependencies directly in that interface and Dagger will implement a function in the generated class that return the dependency. Or we can specify a function without return type that takes a Activity or a Fragment as input. Dagger will generate a function that, when called, injects all @Inject annotated fields in the provided class.

@Component(modules = MyModule.class)
interface MyComponent {
    ExternalDependency getExternalDependency();
    void inject(MyActivity act);
}

Component usage

We only want one instance of the component so we'll instantiate it in an Android Application class and expose it from there.

public class MyApp extends Application {

    private MyComponent myComponent;

    @Override
    public void onCreate() {
        super.onCreate();
        
        myComponent = DaggerMyComponent.create();
    }

    public MyComponent getComponent() {
        return myComponent;
    }
}

Don't forget to register your Application class in the manifest

And now when we have the component exposed we can use it in our Activity to ask Dagger to inject our dependencies into our Activity.

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    
    ((MyApp) getApplication()).getComponent().inject(this);
}

Working example

Putting all the above into one working example would look something like this:

@Module
public class MyModule {

    @Provides
    ExternalDependency provideExternalDependency() {
        return new ExternalDependency();
    }
}

public class MyClass {

    private ExternalDependency externalDependency;

    @Inject
    public MyClass(ExternalDependency  externalDependency) {
        this.externalDependency = externalDependency;
    }
}

@Component(modules = MyModule.class)
interface MyComponent {
    void inject(MyActivity myActivity);
}

public class MyApp extends Application {

    private MyComponent myComponent;

    @Override
    public void onCreate() {
        super.onCreate();

        myComponent = DaggerMyComponent.create();
    }

    public MyComponent getComponent() {
        return myComponent;
    }
}

public class MyActivity extends Activity {

    @Inject
    MyClass myClass;

    @Inject 
    ExternalDependency externalDependency;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
        ((MyApp) getApplication()).getComponent().inject(this);
    }
}