Dagger 2 ( Caution: PLEASE TRY AT HOME ) Part 1
WOW, we got one more day so its time to make this day awesome by learning something new 🙂.
Hello Friends, hope you are doing good. Today I am going to start a new series of something special. Which is called a Dagger 2. I am thinking from last 2 months I will start some series on this toughest and complex topic but every time I scared, how I can convey my message to my readers. I should create a video lecture for this. But now I decided I will try first by going with blog and later if I feel from my readers comments that is not good then I will go with video lecture series.
Motivation:
Truly saying Dagger 2 give me a very tough time. Instead I met with some developers and I asked them, how I can learn Dagger 2 ? But every time I got same answer with different words. “We are also trying but not able to grasp the concept”. So just like for everything I start searching Dagger 2 on Google and I got lot of tutorials. Which are good but not able to convince me to use Dagger 2 and what is Dagger 2. Common terms which I always got in these tutorials are Field Injection, Constructor Injection, Method Injection, @Component, @Module, @Provide and @inject. But in the end I am more confused. Instead some time’s I am good to write a code how tutorial is saying but not good in real world projects. I also try to read from official website of Dagger2 but there I learn how to make a Coffee Maker but I am not able to use that concept in my real life apps. May be someday I will get some project related to Coffee shop then I can use that Coffee Maker example :P . In the end I figure it out I am going to learn Dagger 2 with the same strategy how I am going to learn other things. Which I got a feeling that is wrong. So I decided I will go with the refactoring strategy. As I used my new strategy, I am amazed Dagger 2 is very easy and very useful in 1–2 days. So now I will follow the same strategy here in my this series. It’s time to ATTACK.
Note: I am giving you 100% guarantee. In the end of this series you will be the master of Dagger 2, only try to follow what I am saying, try to grasp that concepts which I will share with you and if you are a person same like me, who is confused with Dagger 2 please try to forgot for the time being what you know about Dagger 2.
Caution: Mostly we used to watch “Do not try at home”. But for this series, I have caution “PLEASE TRY AT HOME”.
Introduction:
If you guys are confused from the terms which are given below or may be you have all the question in your mind which are given below:
DI ( Dependency Injection )
Why DI?
Robo Juice
Dagger
Dagger 2
Field Injection
Construction Injection
Method Injection
Component
Module
SubComponent
@Inject
@Provide
@Scope
@Singleton
……. ….. ….. etc.
Please forgot or try to remove these questions from your mind. May be later in the end of this series you are not able to grasp these concepts from my post, you can comeback on this part and you can remember again from the above mention terms :) ( to make your self minimum confused again).
I am going to create a one Android application. In which I will use different libraries with Dagger 2 but in this post I will do all the code without using Dagger 2. Main purpose of this post we should grab the knowledge of DI or Dependency Injection. So in next post we will do refactor our today’s app code by using Dagger 2. Before start, please forgive me if you feel I am wasting your time but from next post you will be happy when you will feel I did not waste your time, instead I saved your time.
What is Dependency?
For example I have two classes A and B. Now A is using B. So we can say A is dependent on B.
public class A {
B b;
public A(B b) {
this.b = b;
}
}public class B {}
Now here we can say B is a Dependency for A. I think confusion is clear.
A -> Dependent on -> B
B-> Dependency for -> A
Just like we know in android we used to add dependencies like
compile 'com.android.support:appcompat-v7:25.3.1'
So our Android app is dependent upon appcompat lib or we can say appcompat is dependency for our app.
What is Dependency Injection?
Here we already know what is dependency. Now its time to check first Injection definition. How you are going to provide a Dependency to a class or a project that is called an Injection. So as a whole Dependency injection means, how I am going to provide the classes or libs on which my app or program dependent is called Dependency Injection.
There are three famous techniques to provide a dependency which I am going to mention below but will discuss later.
1. Constructor Injection (Means I am providing some code or object by using constructor, on which my class is dependent)
2. Method Injection (Means I am providing some code or object by using method, on which my class is dependent)
3. Field Injection (Means I am giving some code or object by using Field, on which my class is dependent)
If not clear do not take tension only try to clear what is Dependency, Dependent, and Dependency Injection.
Now I have a one repo on Github which I am going to share with you. That example I will use for whole this series. Currently I have a plan I will do code by using Java and Kotlin. Today I am already done with Java. So we will go with Java.
Dagger2 Part1 (Remember you need to checkout Part1 Branch)
Now below image is our whole app.
In this app I am not using any MVP or MVVM or any other architecture. In this app my main focus is Dagger 2 so I try to right my code maximum as readable and good so any body can learn Dagger 2.
First I am going to show you how many dependencies currently we have in our app.
apply plugin: 'com.android.application'
apply plugin: 'me.tatarka.retrolambda'
// Kotlin related dependencies
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'android {
...
// Currently Added for Retro lambda
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
....
}dependencies {
...
// Retrofit 2 for API call's
compile 'com.squareup.retrofit2:retrofit:2.3.0'// JSON Converter. Which I used with Retrofit.
compile 'com.squareup.retrofit2:converter-gson:2.3.0'// Rx Java 2. I used for callback in Recycler view adapters + with retrofit.
compile 'io.reactivex.rxjava2:rxjava:2.0.2'// Rx Android mostly used for AndroidSchedulers.mainThread()
compile 'io.reactivex.rxjava2:rxandroid:2.0.1'// Retrofit 2 work with Rx Java2. Only we need to add below adapter to make a connection
// between these two libs
compile 'com.jakewharton.retrofit:retrofit2-rxjava2-adapter:1.0.0'// This is very useful to show all the response from the API to Logcat.
compile 'com.squareup.okhttp3:logging-interceptor:3.8.1'// Dagger 2
compile 'com.google.dagger:dagger:2.11'
annotationProcessor 'com.google.dagger:dagger-compiler:2.11'// Dagger 2 with Kotlin
kapt 'com.google.dagger:dagger-compiler:2.11'compile 'com.github.bumptech.glide:glide:3.8.0'compile 'com.android.support:recyclerview-v7:25.3.1'
compile 'com.android.support:cardview-v7:25.3.1'
compile "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
}
app/build.gradle
I can say these are the dependencies which I am injecting in project gradle file. Also you can easily see our app is not able to work without these dependencies. The important point like gradle build system is managing our dependencies in a clean way.
We also want the same thing to achieve in our code classes for that we are going to use a DI technique and for that we are using Dagger 2 Framework.
Now we know what is dependency. It's time to learn how to figure it out what is a Dependency in our code. That is the most critical part of today's post. I faced a lot of issue because I started without asking this question, I try to implement directly Dagger 2 which is my biggest mistake. So we will save our time and we are ready to learn how to determine what is dependency for my project. So, as I am able to filter those classes which are dependencies, I am able to use Dagger 2 in seconds. Here we are going to create a graph of a Dependencies in our repositories package. Also I am expecting you guys know how to use Retrofit 2.
repositories
I am going to show you one class which is creating a Service for me.
public class GitHubServiceGenerator {private static GitHubService gitHubService;private GitHubServiceGenerator() {
}public static GitHubService gitHubService(String baseUrl) {if (gitHubService == null) {HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor(
message -> Log.i("Retrofit Network", message))
.setLevel(HttpLoggingInterceptor.Level.BODY);OkHttpClient okHttpClient = new OkHttpClient.Builder()
.addInterceptor(httpLoggingInterceptor)
.readTimeout(5, TimeUnit.MINUTES)
.connectTimeout(5, TimeUnit.MINUTES).build();Retrofit retrofit = new Retrofit.Builder()
.baseUrl(baseUrl)
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.client(okHttpClient)
.build();gitHubService = retrofit.create(GitHubService.class);
}
return gitHubService;
}
}
GitHubServiceGenerator.class
Here we are going to figure it our what is dependency and what is dependent.
If we check our code I got Retrofit is dependent upon baseUrl, Factories, client. So I am going to make a graph only with Client.
Next OkHttpClient is dependent on httpLoggingInterceptor.
Next if we focus on the class. Basically we need GitHubService Object from this class.
Now it's time to check where we are using this GitHubService.public class App extends Application {private static App app;private GitHubRepository gitHubRepository;@Override
public void onCreate() {
super.onCreate();
app = this;
gitHubRepository = GitHubRepository.getInstance(GitHubServiceGenerator.gitHubService("https://api.github.com"));
}public static App getApp() {
return app;
}public GitHubRepository getGitHubRepository() {
return gitHubRepository;
}
}
App.java
Now we can see easily my Application class is dependent upon this GitHubService. Basically we are getting a GitHubRepository class by using GitHubService. So our GitHubRepository is dependent on GitHubService.
Going good. Now in the end if we check GitHubRepository on how many places we are using. We can see anywhere I want any data from GitHub API I am using there and every time I am asking to my App level class to give me this object. So it's mean we need to change little bit more our dependency graph as shown below.
Now you can easily see App is our top level component in Android Application. As an Android developer we know Application class has a Global scope means that class always available when our app is running. So I can easily say GitHubRepository should be injected in App class with Application scope. I think everything is clear. Yes I will discuss with you about scope in Dagger 2 later.
I think we should do 2-3 more examples because that is the most important thing in DI.
Now I am going to use a class HomeActivity.class for next example.
public class HomeActivity extends AppCompatActivity {private RecyclerView recyclerView;
private ProgressBar progressBar;
private GitHubRepository gitHubRepository;
private HomeAdapter homeAdapter;
private RecyclerView.LayoutManager layoutManager;
private Disposable disposable;@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_home);gitHubRepository = App.getApp().getGitHubRepository();initViews();
initRecyclerView();
loadData();
homeAdapter.getClickSubject().subscribe(gitHubUser -> DetailActivity.start(this,
gitHubUser.getLogin(), gitHubUser.getAvatarUrl()));}@Override
protected void onDestroy() {
super.onDestroy();
if (disposable != null && !disposable.isDisposed())
disposable.dispose();
}private void initViews() {
recyclerView = (RecyclerView) findViewById(R.id.MainActivity_recycler_view);
progressBar = (ProgressBar) findViewById(R.id.MainActivity_progress_bar);
}private void initRecyclerView() {
layoutManager = new LinearLayoutManager(this);
homeAdapter = new HomeAdapter(new ArrayList<>());
recyclerView.setLayoutManager(layoutManager);
recyclerView.setAdapter(homeAdapter);
}private void loadData() {
gitHubRepository.getUsers()
...
}
}
Now as general our activity is dependent on all code which we have :) . Like all views and other objects but here I am going to figure it out which will be more good to inject by Dagger 2 and which we should use inside a class.
Here I have some tips.
1. Any where you will see new keyword. It's mean that is a good candidate for DI.
2. Mostly fields are good candidate for DI.
3. Any static class which we are using in our code. Like in above example App.getApp().getGitHubRepository();
Now I am going to design a one graph for dependencies which in my opinion we should inject by using Dagger2 but guys only focus on the skill how we need to figure it out the dependencies for a class.
Here basically HomeActivity is dependent on GitHubRepository but that is getting that dependency from App class. So I can say that is dependent on App class. Next I am going to take one more example of that class in which we are using Glide library. (But guys try to take your any project and make a Dependency graph on your own).
final class HomeViewHolder extends RecyclerView.ViewHolder {private final View parentView;
private final Observer<GitHubUser> clickObserrver;
private final TextView nameTextView;
private final ImageView picImageView;public HomeViewHolder(View itemView, Observer<GitHubUser> clickObserver) {
super(itemView);
nameTextView = (TextView) itemView.findViewById(R.id.RowHome_name_text_view);
picImageView = (ImageView) itemView.findViewById(R.id.RowHome_user_image_view);
parentView = itemView;
this.clickObserrver = clickObserver;
}public void bind(GitHubUser gitHubUser) {Glide.with(picImageView.getContext())
.load(gitHubUser.getAvatarUrl())
.into(picImageView);
nameTextView.setText(gitHubUser.getLogin());
parentView.setOnClickListener(v -> clickObserrver.onNext(gitHubUser));}
}
In above code all bold font code is the dependency for HomeViewHolder.class or vice versa HomeViewHolder.class dependent on the bold font code.
parentView, clickObserrver, gitHubUser, Glide its time to check its dependency graph.
Everything is good. Now here I need to discuss with you little bit more. Why we need Dependency Injection? There is a lot of reasons but here I am going to discuss one of them Unit Tests. If our code is implemented with proper dependencies we can write good unit tests easily. It's time to show you one example here in HomeViewHolder.class.
public void bind(GitHubUser gitHubUser) {
Glide.with(picImageView.getContext())
.load(gitHubUser.getAvatarUrl())
.into(picImageView);
nameTextView.setText(gitHubUser.getLogin());
parentView.setOnClickListener(v -> clickObserrver.onNext(gitHubUser));
}
Here if you focus on Glide. You can easily figure it out that is hidden dependency because that I am using directly in code without asking to any other class to give me object of Glide. Second I am using this one very easily because that is a static class which is also one more pain point in unit tests. Now it's time to refactor this dependency. So we can achieve proper DI. Two options for that. I can make this dependency like a Constructor Injection. Means I will ask that Glide object in constructor of a HomeViewHolder.class as shown below.
final class HomeViewHolder extends RecyclerView.ViewHolder {...
private final Glide glide;
...public HomeViewHolder(View itemView, Observer<GitHubUser> clickObserver, Glide glide) {
....
this.glide = glide;
}public void bind(GitHubUser gitHubUser) {
glide.with(picImageView.getContext())
.load(gitHubUser.getAvatarUrl())
.into(picImageView);
....
}
}
As you can see Glide is no more hidden dependency. Instead I am getting through constructor. We can say that is Constructor injection. Now if are writing unit test you can easily send Mock of Glide in a constructor. :)
Here also I can achieve as a method injection which I am showing below but I am not following in this example. That is only for learning purpose.
final class HomeViewHolder extends RecyclerView.ViewHolder {...
public void bind(GitHubUser gitHubUser, Glide glide) {
glide.with(picImageView.getContext())
.load(gitHubUser.getAvatarUrl())
.into(picImageView);
...
}
}
So in above code I move our hidden dependency into method injection. Here also you can write easily unit test for this method by sending Mock of glide.
Now it's time to go back and continue with Glide Constructor injection. Construction Injection code on Github is in below branch.
Part1GlideRefactoringWithConstructorInjection
Now HomeViewHolder.class is dependency for HomeAdapter.class. So it's time to review first HomeAdapter class.
public class HomeAdapter extends RecyclerView.Adapter<HomeViewHolder> {private LayoutInflater layoutInflater;
private Subject<GitHubUser> clickSubject;
private List<GitHubUser> gitHubUsers;public HomeAdapter(List<GitHubUser> gitHubUsers) {
if (gitHubUsers == null)
throw new IllegalArgumentException("List<GitHubUser> required");
this.gitHubUsers = gitHubUsers;
this.clickSubject = PublishSubject.create();
}@Override
public HomeViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (layoutInflater == null)
layoutInflater = LayoutInflater.from(parent.getContext());
return new HomeViewHolder(layoutInflater.inflate(R.layout.row_home, parent, false), clickSubject); // Getting error
}@Override
public void onBindViewHolder(HomeViewHolder holder, int position) {
holder.bind(gitHubUsers.get(position));
}@Override
public int getItemCount() {
return gitHubUsers.size();
}public Observable<GitHubUser> getClickSubject() {
return clickSubject;
}public void add(List<GitHubUser> gitHubUsers) {
if (gitHubUsers == null) {
throw new NullPointerException("Required List<GitHubUser> but getting Null");
}
this.gitHubUsers.addAll(gitHubUsers);
notifyDataSetChanged();
}
}
Above is the class which we are using as an adapter only one issue. As we know we already change HomeViewHolder.class Glide dependency into constructor. That line is giving me error because on IDE that is asking Glide Object. Also I am showing error in below image.
Now before going to resolve this issue we need to check dependency graph of HomeAapter.class
Here if you review code with focus there are some hidden dependencies in HomeAdapter.class but I am leaving that and going to fix Glide issue. Here I don't have a Glide object but I need to send that into HomeViewHolder.claas. So I am going to change HomeAdpater.class constructor as shown below.
public class HomeAdapter extends RecyclerView.Adapter<HomeViewHolder> {private final Glide glide;
...public HomeAdapter(List<GitHubUser> gitHubUsers, Glide glide) {
...
this.glide = glide;
}
@Override
public HomeViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
...
return new HomeViewHolder(layoutInflater.inflate(R.layout.row_home, parent, false), clickSubject, glide);
}
...
}
According to our new code our dependency graph change as shown below.
Remember we already saw HomeActivity.class graph. But it's time to show whole dependencies graph of HomeActivity.class again because now there is a one error. As we added Glide object in constructor of Adapter and HomeActivity.class is dependent on this adapter. So it's time to give that dependency in HomeActivity.class which we will pass inside of a HomeAdapter.class constructor. Before going to show a solution I am going to show you the error in below image.
Now what will be the solution of this issue. If I try to use Glide glide = ... here directly inside of this class. Then the issue is this, I am also using Glide object in DetailActiivity.class. So I am repeating same code for two classes just like as shown below.
Now in my opinion the good solution for Glide object we should ask to App class. In that way we only used one Object of Glide every where. Now it's time to start refactoring of App.class.
public class App extends Application {private static App app;
private GitHubRepository gitHubRepository;
private Glide glide;@Override
public void onCreate() {
...
glide = Glide.get(this);
}
public static App getApp() { ... }
public GitHubRepository getGitHubRepository() { ... }public Glide getGlide() {
return glide;
}
}
Now it's time to ask App class for Glide object in our HomeActivity.class.
public class HomeActivity extends AppCompatActivity {...
private Glide glide;@Override
protected void onCreate(Bundle savedInstanceState) {
...
glide = App.getApp().getGlide();
...
}@Override
protected void onDestroy() { ... }
private void initViews() { ... }
private void initRecyclerView() {
... homeAdapter = new HomeAdapter(new ArrayList<>(), glide);
...
}
private void loadData() { ... }
}
Now below is dependency graph of HomeActivity.class before refactoring with glide error.
After refactoring, dependency graph of HomeActivity.class.
Now before refactoring of Glide DetailActivity.class.
public class DetailActivity extends AppCompatActivity {public static void start(Context context, String userName, String imageUrl) { ... }
private static void validate(String userName, String imageUrl) { ... }private ImageView picImageView;
private RecyclerView repositoriesRecyclerView;
private ProgressBar progressBar;
private RecyclerView.LayoutManager layoutManager;
private DetailAdapter detailAdapter;
private GitHubRepository gitHubRepository;
private Disposable disposable;@Override
protected void onCreate(@Nullable Bundle savedInstanceState) { ... }
@Override
protected void onDestroy() { ... }
private void initViews() { ... }
private void initRecyclerView() { ... }
private void loadData(String username) { ... }private void loadImage(String imageUrl) {
Glide.with(this)
.load(imageUrl)
.into(picImageView);
}
}
After refactoring of Glide DetailActivity.class.
public class DetailActivity extends AppCompatActivity {public static void start(Context context, String userName, String imageUrl) { ... }
private static void validate(String userName, String imageUrl) { ... }....
private Glide glide;@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
...
gitHubRepository = App.getApp().getGitHubRepository();
glide = App.getApp().getGlide();
...
}@Override
protected void onDestroy() { ... }
private void initViews() { ... }
private void initRecyclerView() { ... }
private void loadData(String username) { ... }private void loadImage(String imageUrl) {
glide.with(this)
.load(imageUrl)
.into(picImageView);
}
}
Boom. Refactor code is pushed in branch Part1GlideRefactoringWithConstructorInjection.
I think that is enough for this post otherwise things will be confused.
Dagger 2 ( Caution: PLEASE TRY AT HOME ) Part 2
Conclusion:
In my opinion that is the most critical part if you want to learn Dependency Injection Framework. If friends you will grasp this skill, after that Dagger 2 is very simple. Instead in my opinion if you have a good skill to refactor your code according to your dependencies. I can give you 100% guarantee after this you can use any Dependency Framework like RoboGuice, Dagger 2, Toothpick etc. Next we are going to start learning Dagger 2 related things, how these dependencies will be injected by Dagger 2 + how Dagger 2 will provide that dependencies to its own component, etc....
OK Friends Bye, Have a nice week. Will meet you in next post :).