-
Notifications
You must be signed in to change notification settings - Fork 6.2k
Dependency Injection with Dagger 2
Many Android apps rely on instantiating objects that often require other dependencies. For instance, a Twitter API client for instance may be built using a networking library such as Retrofit. To use this library, you might also need to add parsing libraries such as Gson. In addition, classes that implement authentication or caching may require accessing shared preferences or other common storage, requiring instantiating them first and creating an inherent dependency chain.
Dagger 2 analyzes these dependencies for you and generates code to help wire them together. While there have been other Java dependency injection frameworks in the past, many of them suffered limitations in relying on XML, required validating dependency issues at run-time, or incurred performance penalties during startup. Dagger 2 relies on purely using Java annotation processors and compile-time checks to analyze and verify dependencies. It is considered to be one of the most efficient dependency injection framework built to date.
Here is a list of other advantages for using Dagger 2:
-
Simplifies access to shared instances. Just as the ButterKnife library makes it easier to define references to Views and event handlers, Dagger 2 provides a simply way to obtain references to shared instances. For instance, once we declare in Dagger our singleton instances such as
MyTwitterApiClientorSharedPreferences, we can declare fields with a simple@Injectannotation:
public class MainActivity extends Activity {
@Inject MyTwitterApiClient mTwitterApiClient;
@Inject SharedPreferences sharedPreferences;
public void onCreate(Bundle savedInstance) {
// assign singleton instances to fields
InjectorClass.inject(this);
} -
Easy configuration of complex dependencies. There is an implicit order in which your objects are often created. Dagger 2 walks through the dependency graph and generates code that is both easy to understand and trace, while also saving you from writing the large amount of of boilerplate code you would normally need to write by hand to obtain references and pass them to other objects as dependencies. It also helps simplify refactoring, since you can focus on what modules to build rather then focusing on the order in which they need to be created.
-
Easier unit and integration testing Because the dependency graph is created for us, we can easily swap out modules that make network responses and mock out this behavior.
-
Scoped instances Not only can you easily manage instances that can last the entire application lifecycle, you can also leverage Dagger 2 to define instances with shorter lifetimes (i.e. bound to a user session, activity lifecycle, etc.)
Add these three lines to your app/build.gradle file:
dependencies {
compile 'com.google.dagger:dagger:2.0'
provided 'com.google.dagger:dagger-compiler:2.0'
provided 'org.glassfish:javax.annotation:10.0-b28'
}Note that the provided keyword refers to dependencies that are only needed at compilation. The Dagger compiler generates code that are used to create the dependency graph of the classes defined in your source code. These classes are added to the IDE class path during compilation.
Android Studio by default will not recognize a lot of generated Dagger 2 code as legitimate classes, but adding the android-apt plugin will add these files into the IDE class path and enable you to have more visibility:
Add this line to your root build.gradle:
dependencies {
// other classpath definitions here
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
}Then make sure to apply the plugin in your app/build.gradle:
// add after applying plugin: 'com.android.application'
apply plugin: 'com.neenbedankt.android-apt'The simplest example is to show how to centralize all your singleton creation with Dagger 2. Suppose you weren't using any type of dependency injection framework and wrote code in your Twitter client similar to the following:
OkHttpClient client = new OkHttpClient();
// Enable caching for OkHttp
int cacheSize = 10 * 1024 * 1024; // 10 MiB
Cache cache = new Cache(getApplication().getCacheDir(), cacheSize);
client.setCache(cache);
// Instantiate Gson
Gson gson = new GsonBuilder().create();
GsonConverterFactory converterFactory = GsonConverterFactory.create(Gson);
// Build Retrofit
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com")
.addConverterFactory(converterFactory)
.client(client) // custom client
.build();You need to define what objects should be included as part of the dependency chain by creating a Dagger 2 module. For instance, if we wish to make a single Retrofit instance tied to the application lifecycle and available to all our activities and fragments, we first need to make Dagger aware that a Retrofit instance can be provided.
Because we wish to setup caching, we need an Application context. Our first Dagger module, AppModule.java, will be used to provide this reference:
@Module
public class AppModule {
Application mApplication;
public AppModule(Application application) {
mApplication = application;
}
@Provides
@Singleton
SharedPreferences providesSharedPreferences(Application application) {
return PreferenceManager.getDefaultSharedPreferences(application);
}
}We create a class called NetModule.java and annotate it with @Module to signal to Dagger to search within the available methods for possible instance providers.
The methods that will actually expose available return types should also be annotated with @Provides decorator. The Singleton annotation also signals to the Dagger compiler that the instance should be created only once in the application. In the following example, we are specifying the Gson return type that can be used as part of the dependency list.
@Module
public class NetModule {
String mBaseUrl;
// Constructor needs one parameter to instantiate.
public NetModule(String baseUrl) {
this.mBaseUrl = baseUrl;
}
@Provides
@Singleton
// Application reference must come from AppModule.class
Cache provideOkHttpCache(Application application) {
int cacheSize = 10 * 1024 * 1024; // 10 MiB
Cache cache = new Cache(application.getCacheDir(), cacheSize);
return cache;
}
@Provides // Dagger will only look for methods annotated with @Provides
@Singleton
Gson provideGson() {
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES);
return gsonBuilder.create();
}
@Provides
@Singleton
OkHttpClient provideOkHttpClient() {
return new OkHttpClient();
}
@Provides
@Singleton
Retrofit provideRetrofit(Gson gson, OkHttpClient okHttpClient) {
Retrofit retrofit = new Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create(gson))
.baseUrl(baseUrl)
.client(okHttpClient)
.build();
return retrofit;
}
}Note that the method name provideGson() itself does not matter and can be named anything. The return type annotated with a @Provides decorator is used to associate this instantiation with any other modules of the same time. The @Singleton annotation is used to declare to Dagger to be only initialized only once.
A Retrofit instance depends both on a Gson and OkHttpClient instance, so we can define another method within the same class that takes these two types. The @Provides annotation and these two parameters in the method will cause Dagger to recognize that there is a dependency on Gson and OkHttpClient to build a Retrofit instance.
Dagger provides a way for the fields in your activities, fragments, or services to be assigned references simply by annotating the fields with an @Inject annotation and calling an inject() method. Calling inject() will cause Dagger 2 to locate the singletons in the dependency graph to try to find a matching return type. If it finds one, it assigns the references to the respective fields. For instance, in the example below, it will attempt to find a provider that returns MyTwitterApiClient and a SharedPreference type:
public class MainActivity extends Activity {
@Inject MyTwitterApiClient mTwitterApiClient;
@Inject SharedPreferences sharedPreferences;
public void onCreate(Bundle savedInstance) {
// assign singleton instances to fields
InjectorClass.inject(this);
} The injector class used in Dagger 2 is called a component. It assigns references in our activities, services, or fragments to have access to singletons we earlier defined. We will need to annotate this class with a @Component declaration. Note that the activities, services, or fragments that will can be added should be declared in this class with individual inject() methods:
@Singleton
@Component(modules={AppModule.class, NetModule.class})
public interface NetComponent {
void inject(MainActivity activity);
// void inject(MyFragment fragment);
// void inject(MyService service);
}Note that base classes are not be sufficient as injection targets. Dagger 2 relies on strongly typed classes, so you must specify explicitly which ones should be defined. (There are suggestions to workaround the issue, but the code to do so may be more complicated to trace than simply defining them.)
An important aspect of Dagger 2 is that the library generates code for classes annotated with the @Component interface. You can use a class prefixed with Dagger_ (i.e. Dagger_TwitterApiComponent.java) that will be responsible for instantiating an instance of our dependency graph and using it to perform the injection work for fields annotated with @Inject. See the setup guide and make sure you've included the android-apt plugin.
We should do all this work within an Application class since these instances should be declared only once throughout the entire lifespan of the application:
public class MyApp extends Application {
private NetComponent mNetComponent;
@Override
public void onCreate() {
super.onCreate();
// specify the full namespace of the component
// Dagger_xxxx (where xxxx = component name)
mNetComponent = com.codepath.dagger.components.DaggerNetComponent.builder()
// list of modules that are part of this component need to be created here too
.appModule(new AppModule(this))
.netModule(new NetModule("https://api.github.com"))
.build();
// If a Dagger 2 component does not have any constructor arguments for any of its modules,
// then we can use .create() as a shortcut instead:
// mAppComponent = com.codepath.dagger.components.DaggerNetComponent.create();
}
public NetComponent getNetComponent() {
return mNetComponent;
}
}Because we are overriding the default Application class, we also modify the appplication name to launch MyApp. This way your application will use this application class to handle the initial instantiation.
<application
android:allowBackup="true"
android:name=".MyApp">Within our activity, we simply need to get access to these component and call inject().
public class MyActivity extends Activity {
@Inject MyTwitterApiClient mTwitterApiClient;
@Inject SharedPreferences sharedPreferences;
public void onCreate(Bundle savedInstance) {
// assign singleton instances to fields
((MyApp) getApplication()).getNetComponent()).inject(this);
} The example above showed that we used singletons that lasted the entire lifecycle of the application. If we wish to create instances that are tied to the lifespan of an activity or fragment, Dagger 2 also enables the ability to create scoped instances. Dagger 2 itself does not know about an activity or fragment so the responsibility lies on you to be consistent about keeping references.
The @Singleton annotation is provided as part of the Java annotation library. We need to define our own PerActivity interface:
import java.lang.annotation.Retention;
import javax.inject.Scope;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* A scoping annotation to permit objects whose lifetime should
* conform to the life of the activity to be memoized in the
* correct component.
*/
@Scope
@Retention(RUNTIME)
public @interface PerActivity {
}See this example code and Stack Overflow discussion.
Components are used to group together and build Modules into an object we can use to get dependencies from, and/or inject fields with. Basically, it's the glue between the Module and the injection points, first by configuring what modules and other components it depends on, then by listing the explicit classes it can be used to inject.
A Component must:
- Define the modules it is composed of as an argument in the
@Componentannotation - Define any dependencies on other
Components it has. - Define functions to inject explicit types with dependencies. TODO: Link to details
- Expose any internal dependencies to be accessed externally or by other Components.
Example Component:
@PerApp
@Component(
modules = {
SampleAppModule.class,
DataModule.class,
NetworkModule.class
}
dependencies = {
OtherComponent.class
}
)
public interface SampleAppComponent {
public void inject(SomeActivity someActivity);
public void inject(SomeFragment someFragment);
public void inject(SomeOtherFragment someOtherFragment);
Application application();
Picasso picasso();
Gson gson();
OkHttpClient okHttpClient();
}Dependencies are used or "injected" through the functions on a Component.
There are 3 ways to get a dependency from a component:
- Directly by calling
component.thing(). - Annotating a field on an object with
@Inject, then callingcomponent.inject(object). Often the object isthis. - Using constructor injection by annotating your constructor with
@Inject, and letting Dagger create your class for you.
- Dagger 2 Github Page
- Sample project using Dagger 2
- Vince Mi's Codepath Meetup Dagger 2 Slides
- http://code.tutsplus.com/tutorials/dependency-injection-with-dagger-2-on-android--cms-23345
- Jake Wharton's Devoxx Dagger 2 Slides
- Jake Wharton's Devoxx Dagger 2 Talk
- Dagger 2 Google Developers Talk
- Dagger 1 to Dagger 2
- Testing Dagger 2 on Android
- Dagger 2 Testing with Mockito
- Snorkeling with Dagger 2
- Dependency Injection in Java
- Component Dependency vs. Submodules in Dagger 2
Created by CodePath with much help from the community. Contributed content licensed under cc-wiki with attribution required. You are free to remix and reuse, as long as you attribute and use a similar license.
Finding these guides helpful?
We need help from the broader community to improve these guides, add new topics and keep the topics up-to-date. See our contribution guidelines here and our topic issues list for great ways to help out.
Check these same guides through our standalone viewer for a better browsing experience and an improved search. Follow us on twitter @codepath for access to more useful Android development resources.