This sample is based on the MVP sample and uses Dagger to externalize the creation of dependencies from the classes that use them.
Dependency injection is a concept valid for any programming language. The general concept behind dependency injection is called Inversion of Control. According to this concept a class should not configure its dependencies statically but should be configured from the outside.
A Java class has a dependency on another class, if it uses an instance of this class. We call this a _class dependency. For example, a class which accesses a logger service has a dependency on this service class.
Ideally Java classes should be as independent as possible from other Java classes. This increases the possibility of reusing these classes and to be able to test them independently from other classes.
If the Java class creates an instance of another class via the new operator, it cannot be used (and tested) independently from this class and this is called a hard dependency.
Dagger 2 exposes a number of special annotations:
@Module
for the classes whose methods provide dependencies@Provides
for the methods within@Module
classes@Inject
to request a dependency (a constructor, a field, or a method)@Component
is a bridge interface between modules and injection
These are the most important annotations you need to know about to get started with dependency injection using Dagger 2.
To implement Dagger 2 correctly, you have to follow these steps:
- Identify the dependent objects and its dependencies.
- Create a class with the
@Module
annotation, using the@Provides
annotation for every method that returns a dependency. - Request dependencies in your dependent objects using the
@Inject
annotation. - Create an interface using the
@Component
annotation and add the classes with the@Module
annotation created in the second step. - Create an object of the
@Component
interface to instantiate the dependent object with its dependencies.
We will briefly look at two annotations : @Binds
and @ContributesAndroidInjector
.
This annotation provides a replacement of @Provides
methods which simply return the injected parameter. Let’s take an example,
We have a MoviesPresenter which implements MoviesContract.Presenter. Without @Binds
, the provider method for it will be something as follows :
@Module
public class MovieModule {
@Provides
public MoviesContract.Presenter provideMoviesPresenter(MoviesPresenter moviesPresenter) {
return moviesPresenter;
}
}
In the above case, we can instead use @Binds
annotation and make the above method abstract
:
@Module
public abstract class MovieModule {
@Binds
public abstract MoviesContract.Presenter provideMoviesPresenter(MoviesPresenter moviesPresenter);
}
Of course, we will also need to mark our Module as abstract in this case, which is more efficient than a concrete one and thus makes @Binds
more efficient.
@Provides
methods are instance methods and they need an instance of our module in order to be invoked. If our Module is abstract and contains @Binds
methods, dagger will not instantiate our module and instead directly use the Provider of our injected parameter (MoviesPresenter in the above case).
In case, your Module has both @Provides
and @Binds
methods, you have two options :
- Simplest would be to mark your
@Provides
instance methods asstatic
. - If it is necessary to keep them as instance methods, then you can split your module into two and extract out all the
@Binds
methods into an abstract Module.
@Module
public abstract class MovieDetailsModule {
@Provides
static Movie provideMovie(DetailsActivity activity) {
return activity.getIntent().getParcelableExtra(Constants.MOVIE_EXTRA);
}
@Binds
abstract DetailsContract.presenter detailsPresenter(DetailsPresenter presenter);
}
Dagger Android introduced this annotation which can reduce the Component
, Binds
, IntoSet
, Subcomponent
, ActivityKey
, FragmentKey
etc. boilerplate for you.
If you have a simple module like the following, you can then let dagger handle the rest.
@Module
public abstract class MovieModule {
@Binds
public abstract MoviesContract.Presenter provideMoviesPresenter(MoviesPresenter moviesPresenter);
}
All you need to do is write the following snippet inside the Module
of the component, which is going to be the super-component of the generated DetailsComponent
. Example, If you have an AppComponent
and you want dagger to generate a DetailsSubcomponent
for your DetailsActivity
, you will write the following snippet inside your ActivityBindingModule
.
@Module
public abstract class ActivityBindingModule {
@ContributesAndroidInjector(modules = {MovieDetailsModule.class})
abstract DetailsActivity detailsActivity();
}
We want Dagger.Android
to create a Subcomponent which has a parent Component
of whichever module ActivityBindingModule
is on, in our case that will be AppComponent
.
The beautiful part about this setup is that you never need to tell AppComponent
that it is going to have all these subcomponents nor do you need to tell these subcomponents that AppComponent
exists.
It’s a good practice to extract out a separate ActivityBindingModule
for all such bindings and include it in the modules list of your AppComponent
as follows :
@Singleton
@Component(modules = {
ActivityBindingModule.class,
AndroidSupportInjectionModule.class})
public interface AppComponent extends AndroidInjector<ApplicationClass> {
}
AndroidSupportInjectionModule.class
is added because we used support Fragment. AndroidInjectionModule
binds your app.Fragment
to dagger. But If you want to use injection in v4.fragment
then you should add AndroidSupportInjectionModule.class
to your AppComponent
modules.
Following is the boilerplate that gets generated for you when you use @ContributesAndroidInjector
:
@Module(subcomponents = ActivityBindingModule_DetailsActivity.DetailsActivitySubcomponent.class)
public abstract class ActivityBindingModule_DetailsActivity {
private ActivityBindingModule_DetailsActivity() {}
@Binds
@IntoMap
@ActivityKey(DetailsActivity.class)
abstract AndroidInjector.Factory<? extends Activity> bindAndroidInjectorFactory(
DetailsActivitySubcomponent.Builder builder);
@Subcomponent(modules = MovieDetailsModule.class)
@ActivityScope
public interface DetailsActivitySubcomponent extends AndroidInjector<DetailsActivity> {
@Subcomponent.Builder
abstract class Builder extends AndroidInjector.Builder<DetailsActivity> {}
}
}
- It generates the
DetailsActivitySubcomponent
. - It adds the necessary
@Subcomponent
annotation for us. - It adds an entry of (
DetailsActivity.class
,DetailsActivitySubcomponent.Builder
) to the Map of Injector Factories used byDispatchingAndroidInjector
.Dagger-Android
uses this entry to build ourDetailsActivitySubcomponent
and perform injections forDetailsActivity
. - Also, binds DetailsActivity to the object-graph.
This version of the app uses some other libraries:
- Dagger 2: externalize the creation of dependencies from the classes that use them
- Picasso: used for loading, processing, caching and displaying remote and local images.
- ButterKnife: used for perform injection on objects, views and OnClickListeners.
- CardView: used for representing the information in a card manner with a drop shadow and corner radius which looks consistent across the platform.
- RecyclerView: The RecyclerView widget is a more advanced and flexible version of ListView.
- GSON: Gson is a Java library that can be used to convert Java Objects into their JSON representation. It can also be used to convert a JSON string to an equivalent Java object.
- Retrofit: This library used to send HTTP request to the server and retrieve response.
- ROOM Library: Room provides an abstraction layer over SQLite to allow fluent database access while harnessing the full power of SQLite.
- BlurView Library: It blurs its underlying content and draws it as a background for its children.
In order for the movieapp-mvp-clean app to function properly as of January 26th, 2018 an API key for themoviedb.org must be included with the build.
Include the unique key for the build by adding the following line to util/Constants.java or find the TODO Line.
API_KEY = "";
Copyright (C) 2018 Shehab Salah Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.