May 15, 2013

Dependency Injection with Dagger on Android

Limitations of Android Dependency Injection Solutions

A question that I often in my work as the Spring Developer Advocate at SpringSource is: How do I do dependency injection in Android? Spring for Android doesn't have a dependency injection story as there are a lot of restrictions to what an inversion-of-control (IoC) container can hope to achieve on Android.

First, Android controls all the important objects in an Android application including the Activity instances. Normally, Spring instantiates an object ("bean") -satisfying any constructor injections required - and then attempts to satisfy any injection sites on fields (field injection) and through methods (setter injection). When it's done, Spring hands back the fully configured reference and makes it available for injection in other beans. As Android creates the Activity instance - it's not possible to simply tell Android to use a bean configured with a dependency injection container - the only thing a DI container can do is handle injections after the Activity has been created. This implies some fragile state management and explicit dependencies on the dependency injection container that is akin to the sort of hacks required to use Spring inside of EJB 2.x beans from years ago. At a minimum - it requires invasive code in the heart of your business logic components.

Second, reflection has historically been very slow on Android (and any constrained environment, including the original PCs that debuted Java in 1994!), and a lot of the tried-and-true solutions for byte-code manipulation don't work nearly so well on Android as they should. A lot of this has improved in recent years, but nonetheless solutions that favor compile-time weaving or other solutions that minimize reflection are preferable.

Dagger

With all of these limitations in mind, I still personally like Dagger, from Square. Dagger provides a JSR 330 compliant dependency injection container (and so - in theory - you could run JSR 330-compliant Spring code on Android). Using it is fairly simple, except that it's a bit more invasive than I'm used to in Spring. It relies on a Java agent that changes the code at compile time, inserting code that eliminates the need for costly, or sometimes impossible, reflection.

A Dagger Module

The simplest way to get started with it is to create a module class. Let's look at an example:


import ...

// Dagger and JSR 330 APIs
import dagger.*;
import javax.inject.*;

// RestTemplate 
import org.springframework.http.converter.json.MappingJacksonHttpMessageConverter;
import org.springframework.web.client.RestTemplate;


@Module(
 injects = {
  WelcomeActivity.class
 }
)
public class CrmModule {
  // ...
} 

You'll note a few things of interest already. This class is annotated with the @Module annotation. The @Module annotation is more or less like Spring's @Configuration annotation. It tells the container that this class contains the definition of beans, which themselves are annotated with @Provides, much like Spring's @Bean-annotated configuration annotation.

The injects attribute in Dagger's @Module annotation enumerates all the classes that may that may receive injections that *are not defined explicitly using an @Provider-annotated method in the module. I think. I'll be honest, I haven't completely figured this out. The nice thing is that if you get it wrong, you get a compile-time error! Handy!

As shown, we could start declaring fields or constructor arguments in our WelcomeActivity instance and annotating them with JSR 330's @Inject. When Dagger sees @Inject, it attempts to instantiate an instance of the bean using the default, no-args constructor and then inject it at the injection site.

However, not all beans can be provided this way. For example, if the type declared is an interface, then how's Dagger to know which implementation to provide? What if we only want one, and only one, instance (a singleton)? What if we want to inject values into a third party bean where it would be impractical to attempt to update the code with @Inject annotations? For all these scenarios, you can explicitly wire up the bean definitions using provider methods in your module, like this:


import ...

// Dagger and JSR 330 APIs
import dagger.*;
import javax.inject.*;

// RestTemplate 
import org.springframework.http.converter.json.MappingJacksonHttpMessageConverter;
import org.springframework.web.client.RestTemplate;


@Module(
  injects = {
    WelcomeActivity.class
  }
)
public class CrmModule {
    private Crm application;
    private Context context;

    public CrmModule(Crm crm) {
        this.application = crm;
        this.context = this.application.getApplicationContext();
    }

    @Provides
    @InjectAndroidApplicationContext
    public Context provideApplicationContext() {
        return this.context;
    }

    @Singleton
    @Provides
    public RestTemplate provideRestTemplate() {
        RestTemplate restTemplate = new RestTemplate();
        restTemplate.getMessageConverters().add(new MappingJacksonHttpMessageConverter());
        return restTemplate;
    }

    @Singleton
    @Provides // CustomerService is the interface, CustomerServiceClient is the implementation
    public CustomerService provideCustomerService(
             @InjectAndroidApplicationContext Context context, 
             RestTemplate restTemplate ){
        String baseUri = context.getString(R.string.base_uri);
        CustomerServiceClient customerServiceClient = new CustomerServiceClient( baseUri );
        customerServiceClient.setRestTemplate(restTemplate);
        return customerServiceClient;
    }

}

The @Provides-annotated methods work as expected: you construct an object in a method and the value returned is made available for consumption through injection in other beans by type(s, as it also creates a binding between any interfaces implemented by the bean. We use this to our advantage when we create the CustomerService bean.

Note that - just as with Spring's @Configuration class - you can declare provider methods with arguments and the container will attempt to provide a value for the argument based on the type of the argument. In some cases, there may be many types that satisfy the requirement and so you can use a @Qualifier-annotated meta-annotation, like @InjectAndroidApplicationContext, to specify which instance you want. Here, we annotate the Crm argument in the provideCustomerService method with @InjectAndroidApplicationContext. By using the same annotation at the provider method as well as at the site of the injection, we establish a unique binding.

Using the Android Application instance as an Application-Wide Global Context.

Our module class expects an instance of the current application's Application class. I've specified the class ....Crm for this application class in my AndroidManifest.xml through the android:name attribute in the manifest element. As I mentioned earlier, key components in an Android application, like the Application subclass, are instantiated, managed and destroyed by Android itself. Dagger can't instantiate the Application subclass and provide the reference for us. Instead, we need to insert Dagger in a place that it can capture a reference to that Application instance. We do this in our onCreate method in our Application instance.

import ...
import android.app.Application;
import dagger.ObjectGraph;

import java.util.*;

public class Crm extends Application {

    private ObjectGraph objectGraph;

    @Override
    public void onCreate() {
        super.onCreate();
        Object[] modules = getModules().toArray();
        objectGraph = ObjectGraph.create(modules);
    }

    protected List<Object> getModules() {
        return Arrays.<Object>asList(
                new CrmModule(this)
        );
    }

    public ObjectGraph getObjectGraph() {
        return this.objectGraph;
    }
}

Here, in the onCreate method we assemble the various module classes - including our CrmModule - that we want to instantiate and use it to create an objectGraph. Because Application subclasses provide an effective application-global singleton, it's convenient to stash a reference to the Dagger objectGraph in this class, as well. The objectGraph more or less corresponds to Spring's ApplicationContext. In our onCreate method we create the CrmModule passing in a reference to this which can then be used in a provide method in our module so that we can easily inject an Android application Application instance (which in turn provides a Context, which is a very useful sort of god object in Android applications) wherever we use Dagger for dependency injection.

Dependency Injection with Android Activity Instances and Other Android Components

Since we want to inject beans into our Activity subclasses, and Activity classes are managed by Android in the same way as Application instances, we need a similar hook into the Activity's lifecycle to let Dagger work its magic after the object's been created. For convenience, you'll probably have a common base-class where you can insert this behavior and then simply extend. Here is such a base-class.

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import ...Crm;

import javax.inject.Inject;

public class CrmBaseActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Crm crm = (Crm) getApplication(); 
        crm.getObjectGraph().inject(this);
    }
}

The code simply obtains the current Application instance (the Application subclass, Crm), which we know has a pointer to the current, stashed, ObjectGraph instance, and then uses the ObjectGraph#inject(Object) method to have Dagger scan the object given as an argument and satisfy any dependencies.

You'll use a similar approach when dealing with other Android components like Fragment classes.

Summary

This blog has demonstrated how to use Dagger in the context of Android. Dagger also works outside of Android, of course, but if you're working outside of Android, you're better off just using Spring. For another look at how to use Dagger, check out the Dagger introduction on the Square site which focuses on Dagger, generically.