Dagger 2 is a dependency injection framework.
Example of a Dependency
Below is an example where we have Class A which requires and instance of another class.
public class A {
private B b;
public A() {
b = new B();
}
}
There are a few issues with above code:
- Class A implementation depends of constructor of Class B. E.g if constructor of class B changes, we need to update variable “b” initialization.
- If have to use same instance of variable B in several classes, we have to duplicate same initialization code in other classes’s constructor (e.g onCreate method – Android).
The idea and aim when using dependency injection is to “decouple” implementations, making class A less dependent on Class B’s implementation. E.g if we need to change implementation of B, the implementation code for Class A will not require any change.
One way to achieve this is to declare class’s B initialization in another class so that all classes which require object will get it from there. That’s what a dependency injection framework does, but in a clean and more robust way.
Getting started with Dagger 2 on Android
On Android, as an example, we would often instance of DB manager or HttpClient inside many of our Activities/Presenter. We could use “static” methods/objects as well, but DI is the “right” way and can be more reliable. So let’s take a deeper look at how to get started with Dagger 2.
Step 1: Gradle import
First let’s add this to the app module’s gradle file:
compile 'com.google.dagger:dagger:2.10' annotationProcessor 'com.google.dagger:dagger-compiler:2.10' testAnnotationProcessor 'com.google.dagger:dagger-compiler:2.10'
Step 2: Create packages
I would advise to create a package named “di”. Inside of “di”, inside we will have two packages: “component” and “module”.
Example:
Step 3: Create Module class:
Let’s create “ContextModule” class inside “di/module” package
@Module public class ContextModule { private final Context context; public ContextModule(Context context) { // Constructor this.context = context; } @Singleton @Provides public Context getContext() { // Provides means Context objects to any method inside Module class or for injecting in other classes return context; } }
Dagger 2 uses the following annotations:
-
@Module: define classes which provide dependencies
- @Provides: define method which provide dependencies. E.g Context objects will be provided by this method.
Step 4: Building your project and Wiring up DI inside Application Class
After defining the module, you will need to build your project (may require clean as well). There will be some generated classes in our example, it will be for “ContextModule”. After building project, go to you application class, and add following:
appComponent = DaggerAppComponent.builder() .contextModule(new ContextModule(getApplicationContext())) .build();
For all modules, we create an instance by providing the constructor
Step 5: Define Component
Step 6: Wiring on Base Activities/Fragments/Presenters
public class BasePresenter implements BaseContract.BasePresenter { @Nonnull BaseContract.AppBaseView mView; @Inject protected Context context; // We are injecting the context provided from Application Class public BasePresenter(@Nonnull BaseContract.AppBaseView mView) { this.mView = mView; MApplication.getAppComponent().inject(this); // Wiring dependencies so that we can inject objects from Module class mSubscription = new CompositeSubscription(); LogUtils.showLogDebug("subscribe"); } . . .
-> MApplication.getAppComponent().inject(this);
This is how we wire the BasePresenter to Dagger so that we can inject all dependencies define in our module classes.
If you used “ButterKnife” library for injecting layout elements into Activities/Fragments classes, you know you have to call “ButterKnife.bind(this)” after Activity is created. ButterKnife also uses DI for injecting the layout items into related java/activity class.
That’s it. Now for any classes which extend the BasePresenter, you will be able to inject all dependencies defined.
Below is another example for BaseActivity
public abstract class BaseActivity extends AppCompatActivity implements BaseContract.BaseView { @Nonnull MgProgressBasePresenter mPresenter; @Inject protected Context context; // We are injecting the context provided from Application Class @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(getResourceLayout()); ButterKnife.bind(this); MApplication.getAppComponent().inject(this); // "Binding" dagger dependency onViewReady(savedInstanceState); mPresenter = new BasePresenter(this); }
Too much boilerplate?
We were injecting only the Context class which might not seem very useful, but imagine if you had a Database Module, a Network module, and these constructors will be defined in only one place.
You can have a look at the Network Module for example:
@Module public class NetworkModule { private String baseUrl; public NetworkModule(String baseUrl) { this.baseUrl = baseUrl; } @Singleton @Provides public OkHttpClient.Builder getHttpClient() { return new OkHttpClient.Builder(); } @Singleton @Provides public Retrofit.Builder getRetrofitBuilder() { return new Retrofit.Builder(); } @Singleton @Provides public APIService getService(Context context, OkHttpClient.Builder httpClient, Retrofit.Builder retrofit) { . . . }
Please note here, we have getHttpClient() method, and getRetrofitBuilder() method which provides instances of OkHttpClient.Builder and Retrofit.Builder retrofit which will provide for instances for required arguments inside “getService(…)” method within the Module. So we are defining how the arguments inside getService will get it’s instances. Same apply for Context which shall be obtained from the ContextModule.
So now on our presenters (or fragments), we can inject another object without requiring any additional “wiring up” on our classes:
@Inject
APIService apiService;
I guess that’s all for today. Let me know if there is anything missing or any issue which you are getting with using Dagger 2 on Android. Catch you soon with another post.
Cheers,