-
Notifications
You must be signed in to change notification settings - Fork 6.2k
Dependency Injection with Dagger 2
Many Android apps end up with some type of inherent implicit dependency chain in the source code. 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, creating another set of inter-related dependencies.
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, validated 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 and is considered to be one of the most performant dependency injection framework built to date.
Here is a list of other advantages for using Dagger 2:
-
Simplifies access to singletons. 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 our singleton instances such as
MyTwitterApiClientorSharedPreferencesin Dagger, 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);
} -
Order of instantiation is automatically managed for you. There is an implicit order in which your modules are often created. Dagger 2 walks through the dependency graph and generates code that is both easy to understand and trace, while also reducing the amount of boilerplate code you would normally need to write by hand to accomplish the same goal. It also helps simplify refactoring, since you can focus on what modules to support rather than focusing on the order in which they should be supported.
-
Easier unit and integration testing Because the dependency graph is created for us, we can easily swap out modules that make network responses that mock out this behavior.
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 but not at run-time. The Dagger compiler and Java annotation libraries are used to generate code that are used to create the dependency graph of the classes defined in your source code.
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();
// 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 should be included as part of the dependency chain. 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. 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:
@Module
public class NetModule { // Dagger will look in here
}Note that the methods that will actually expose available return types must 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 {
@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();
}Note that the method name provideGson() itself does not matter and can be named anything. Only the return type, the @Provides annotation, and @Singleton annotation cause Dagger to understand that this method is used to construct a Gson singleton instance. We also declare a provider for an OkHttpClient instance using the same approach:
@Provides
@Singleton
OkHttpClient provideOkHttpClient() {
return new OkHttpClient();
}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:
@Provides
@Singleton
Retrofit provideRetrofit(Gson gson, OkHttpClient okHttpClient) {
Retrofit retrofit = new Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create(gson))
.baseUrl(baseUrl)
.client(okHttpClient)
.build();
return retrofit;
}Dagger provides a way for the fields in your activities, fragments, or services to be assigned references to these singletons by delegating this work to an injector class that will handle this work. The fields need to be annotated with @Inject and calling an inject() method with this class, Dagger 2 will search its dependency graph to try to find references to these instances.
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 in Dagger 2 must be defined with a @Component decorator. The activities, services, or fragments that it will support should be defined as individual inject() methods:
@Singleton
@Component(modules={NetModule.class})
public interface AppComponent {
void inject(MyActivity 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.)
The @Component interface causes Dagger to generate a factory class prefixed with Dagger_ (i.e. Dagger_TwitterApiComponent.java that will be responsible for assigning values to fields annotated with @Inject. We need to use this factory class to instantiate a component with all its related dependencies. 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 AppComponent mAppComponent;
@Override
public void onCreate() {
super.onCreate();
// specify the full namespace of the component
// Dagger_xxxx (where xxxx = component name)
mAppComponent = com.codepath.dagger.components.DaggerAppComponent.builder()
.netModule(new NetModule());
}
public AppComponent getAppComponent() {
return mAppComponent;
}We also modify the app name label to correspond to MyApp to ensure that this application is called:
<application
android:allowBackup="true"
android:label="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
getApplication().getAppComponent()).inject(this);
} Dependency injection is a way to separate concerns between configuration and usage
For the Usage side (e.g. Activities, Fragments, Views) you only need to worry about:
- Getting the object you need (simply annotating with
@Inject) - Using it.
On the configuration side you get:
- Less duplicated code
- Easy singleton management
- Easier mocking for testing and variants
- Easy configuration of complex dependencies
Dagger 2 exposes a number of special annotations:
-
@Modulefor the classes whose methods provide dependencies -
@Providesfor the specific methods within@Moduleclasses -
@Componentis a collection of modules that can perform injection -
@Injectto request a dependency (a constructor argument, or a field) -
@Singletonand custom scopes to define singletons within specific scopes. (We recommend using custom scopes like@PerAppor@PerActivityfor clarity of how long a "singleton" lives for)
Let's take a look at the Dagger workflow within an app.
Dagger 2 is composed mainly 3 main parts:
Modules are responsible for creating and configuring the objects in your "dependency graph" through a set of @Provides annotated functions. Examples modules might include:
- An
ApplicationModulethat provides your baseApplicationcontext. - A
NetworkModulethat has your http client, http cache, cookie store, image loader, and configures timeouts. - A
StorageModulethat has your database, sharedPreferences, and key value store. - An
ActivityModulefor a specific Activity that provides local data shared by its childFragments
Here is an example module:
@Module
public class SampleAppModule {
static final String PREFS_DEFAULT = "myapp";
final SampleApp app;
public AppModule(SampleApp app) {
this.app = app;
}
@Provides @PerApp SampleApp provideSampleApp() {
return app;
}
@Provides @PerApp Application provideApplication(SampleApp app) {
return app;
}
@Provides @PerApp SharedPreferences provideSharedPrefs(Application app) {
return app.getSharedPreferences(PREFS_DEFAULT, Context.MODE_PRIVATE);
}
@Provides @PerApp Gson provideGson() {
return new Gson();
}
}Notice the @Module annotation on the class and the @Provides annotations on the functions. Functions in modules annotated with @Provides are called when the dependency is injected or used by another dependency. If the function is annotated with a scope, in this case @PerApp, it is only created once for that scope.
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();
}Notice within the annotation, we specify the Modules that @Provide the component and any other full components this component might depend on.
inject(Class thing) methods are used to explicitly define classes that have fields annotated with @Inject to be injected.
Class thing() methods are used to expose dependencies either to direct consumers or to dependent components to consume.
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.
Defining custom scope(s) is highly recommended as it ????? TODO
- 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
- Dagger 2 Google Developers Talk
- Dagger 1 to Dagger 2
- Testing Dagger 2 on Android
- Dagger 2 Testing with Mockito
- Snorkeling with 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.
