In this tutorial, we’ll be implementing MVP pattern along with Dagger2 in our Android Application. It’s recommended to go through each of these design pattern concepts individually before using them together.
Android MVP and Dagger2
We know that MVP is based on the separation of concerns. MVP pattern separates our code into three layers – Model, View and Presenter.
Presenter acts as a bridge between the Model and the View. Model and View cannot communicate with each other WITHOUT the Presenter.
Presenter-Model, Presenter-View communicate using Interfaces.
Dagger2 is a Dependency Injection Library developed by Square. Instead of creating dependencies in our classes, we provide them by using the Dagger2 core structure – Module
, Component
, Inject
and Provides
.
Using the Dagger2 annotations, the compiler builds the Dagger classes automatically at compile-time.
MVP + Dagger2 = Beginning our Journey to write clean code that follows the SOLID Principles.
So in the following section, we’ll use Dagger2 in our MVP Android Studio Project from this tutorial. This would give us a clarity as to how Dagger2 works with MVP.
Android MVP Dagger2 Project Structure
model – The Model class of the Project.
view – Our activity is the view here.
presenter – The presenter class, which ultimately tells the Activity what to do with the View.
MainContract.java – Consists of the interfaces for the Model, View and Presenter.
di – Consists of Dependencies Injection – Dagger2 classes.
Don’t forget to add the following dependencies in your build.gradle file.
1 2 3 4 |
annotationProcessor 'com.google.dagger:dagger-compiler:2.13' implementation 'com.google.dagger:dagger:2.13' |
Android MVP Dagger2 Code
Let’s look at the interfaces defined for the MVP classes in our MainContract.java
class.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
package com.journaldev.mvpdagger2; public interface MainContract { interface ViewCallBack { void showProgress(); void hideProgress(); void setQuote(String string); } interface ModelCallBack { interface OnFinishedListener { void onFinished(String string); } void getNextQuote(OnFinishedListener onFinishedListener); } interface PresenterCallBack { void onButtonClick(); void onDestroy(); } } |
So the View has interfaces for setting a string, showing or hiding a ProgressBar.
The Model has an interface onFinished which triggers after the handler interval succeeds.
The Presenter controls the activity button click and onDestroy() lifecycle.
Model.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
package com.journaldev.mvpdagger2.model; import android.os.Handler; import com.journaldev.mvpdagger2.MainContract; import java.util.Arrays; import java.util.List; import java.util.Random; public class Model implements MainContract.ModelCallBack { @Override public void getNextQuote(final MainContract.ModelCallBack.OnFinishedListener listener) { new Handler().postDelayed(new Runnable() { @Override public void run() { listener.onFinished(getRandomQuote()); } }, 1200); } private List<String> arrayList = Arrays.asList( "Be yourself. everyone else is already taken.", "A room without books is like a body without a soul.", "You only live once, but if you do it right, once is enough.", "Be the change that you wish to see in the world.", "If you tell the truth, you don't have to remember anything." ); private String getRandomQuote() { Random random = new Random(); int index = random.nextInt(arrayList.size()); return arrayList.get(index); } } |
The presenter implementation is defined in the MainPresenterImpl.java
class.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
package com.journaldev.mvpdagger2.presenter; import com.journaldev.mvpdagger2.MainContract; import com.journaldev.mvpdagger2.model.Model; public class MainPresenterImpl implements MainContract.PresenterCallBack, MainContract.ModelCallBack.OnFinishedListener { private MainContract.ViewCallBack mainView; private Model model; public MainPresenterImpl(MainContract.ViewCallBack mainView, Model model) { this.mainView = mainView; this.model = model; } @Override public void onButtonClick() { if (mainView != null) { mainView.showProgress(); } model.getNextQuote(this); } @Override public void onDestroy() { mainView = null; } @Override public void onFinished(String string) { if (mainView != null) { mainView.setQuote(string); mainView.hideProgress(); } } } |
di package
In the di package we define the dependency injection modules and components.
Let’s look at the Modules first.
AppModule.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
package com.journaldev.mvpdagger2.di.module; import android.app.Application; import com.journaldev.mvpdagger2.InitApplication; import javax.inject.Singleton; import dagger.Module; import dagger.Provides; @Module public class AppModule { private InitApplication initApplication; public AppModule(InitApplication initApplication) { this.initApplication = initApplication; } @Provides @Singleton public Application provideApplication() { return initApplication; } } |
This provides an application instance across the project.
ContextModule.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
package com.journaldev.mvpdagger2.di.module; import android.content.Context; import dagger.Module; import dagger.Provides; @Module public class ContextModule { private Context context; public ContextModule(Context context) { this.context = context; } @Provides public Context provideContext() { return context; } } |
This provides getApplicationContext()
DataModule.java
1 2 3 4 5 6 7 8 9 10 11 12 13 |
package com.journaldev.mvpdagger2.di.module; import com.journaldev.mvpdagger2.model.Model; import dagger.Module; import dagger.Provides; @Module public class DataModule { @Provides public Model provideModelClass() { return new Model(); } } |
This provides an instance of Model class for us.
MvpModule.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
package com.journaldev.mvpdagger2.di.module; import com.journaldev.mvpdagger2.MainContract; import com.journaldev.mvpdagger2.model.Model; import com.journaldev.mvpdagger2.presenter.MainPresenterImpl; import dagger.Module; import dagger.Provides; @Module public class MvpModule { private MainContract.ViewCallBack viewCallBack; public MvpModule(MainContract.ViewCallBack viewCallBack) { this.viewCallBack = viewCallBack; } @Provides public MainContract.ViewCallBack provideView() { return viewCallBack; } @Provides public MainContract.PresenterCallBack providePresenter(MainContract.ViewCallBack view, Model model) { return new MainPresenterImpl(view, model); } } |
This provides an instance of the MainPresenterImpl.java
class.
It takes the View’s interface and the Model class as it’s constructor arguments.
ActivityScope.java
1 2 3 4 5 6 7 8 9 10 |
package com.journaldev.mvpdagger2.di.scope; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import javax.inject.Scope; @Scope @Retention(RetentionPolicy.RUNTIME) public @interface ActivityScope { } |
This sets the scope for the components which we’ll see next.
AppComponent.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
package com.journaldev.mvpdagger2.di.component; import android.app.Application; import android.content.Context; import com.journaldev.mvpdagger2.InitApplication; import com.journaldev.mvpdagger2.di.module.AppModule; import com.journaldev.mvpdagger2.di.module.ContextModule; import com.journaldev.mvpdagger2.di.module.DataModule; import com.journaldev.mvpdagger2.model.Model; import javax.inject.Singleton; import dagger.Component; @Singleton @Component(modules = {AppModule.class, DataModule.class, ContextModule.class}) public interface AppComponent { void inject(InitApplication initApplication); Context getContext(); Model getFindItemsInteractor(); Application getApplication(); } |
This is the App level Component. The fields would be accessible throughout the application.
ActivityComponent.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
package com.journaldev.mvpdagger2.di.component; import com.journaldev.mvpdagger2.MainContract; import com.journaldev.mvpdagger2.di.module.MvpModule; import com.journaldev.mvpdagger2.di.scope.ActivityScope; import com.journaldev.mvpdagger2.view.MainActivity; import dagger.Component; @ActivityScope @Component(dependencies = AppComponent.class, modules = MvpModule.class) public interface ActivityComponent { void inject(MainActivity mainActivity); MainContract.PresenterCallBack getMainPresenter(); } |
This Component is used to inject the Presenter in our MainActivity.
The code for the InitApplication.java is given below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
package com.journaldev.mvpdagger2; import android.app.Application; import android.content.Context; import com.journaldev.mvpdagger2.di.component.AppComponent; import com.journaldev.mvpdagger2.di.component.DaggerAppComponent; import com.journaldev.mvpdagger2.di.module.AppModule; import com.journaldev.mvpdagger2.di.module.ContextModule; import com.journaldev.mvpdagger2.di.module.DataModule; public class InitApplication extends Application { private AppComponent component; public static InitApplication get(Context context) { return (InitApplication) context.getApplicationContext(); } @Override public void onCreate() { super.onCreate(); component = DaggerAppComponent.builder() .appModule(new AppModule(this)) .contextModule(new ContextModule(this)) .dataModule(new DataModule()) .build(); } public AppComponent component() { return component; } } |
The code for the activity_main.xml layout file is given below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
<?xml version="1.0" encoding="utf-8"?> <android.support.constraint.ConstraintLayout xmlns:android="https://schemas.android.com/apk/res/android" xmlns:app="https://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:id="@+id/textView" android:layout_width="0dp" android:layout_height="wrap_content" android:gravity="center" android:padding="8dp" android:text="Implementing MVP and Dagger2 in this Application." android:textAppearance="?android:attr/textAppearanceSearchResultTitle" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" /> <Button android:id="@+id/button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="@android:dimen/notification_large_icon_height" android:text="GET NEXT QUOTE" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toBottomOf="@+id/textView" /> <ProgressBar android:id="@+id/progressBar" style="?android:attr/progressBarStyleLarge" android:layout_width="wrap_content" android:layout_height="wrap_content" android:visibility="gone" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" /> </android.support.constraint.ConstraintLayout> |
The code for the MainActivity.java class is given below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
package com.journaldev.mvpdagger2.view; import android.content.Context; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.ProgressBar; import android.widget.TextView; import android.widget.Toast; import com.journaldev.mvpdagger2.InitApplication; import com.journaldev.mvpdagger2.MainContract; import com.journaldev.mvpdagger2.R; import com.journaldev.mvpdagger2.di.component.DaggerActivityComponent; import com.journaldev.mvpdagger2.di.module.MvpModule; import javax.inject.Inject; import static android.view.View.GONE; public class MainActivity extends AppCompatActivity implements MainContract.ViewCallBack { @Inject MainContract.PresenterCallBack presenterCallBack; @Inject Context mContext; private TextView textView; private ProgressBar progressBar; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); DaggerActivityComponent.builder() .appComponent(InitApplication.get(this).component()) .mvpModule(new MvpModule(this)) .build() .inject(this); textView = findViewById(R.id.textView); Button button = findViewById(R.id.button); progressBar = findViewById(R.id.progressBar); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { presenterCallBack.onButtonClick(); } }); } @Override protected void onResume() { super.onResume(); } @Override protected void onDestroy() { super.onDestroy(); presenterCallBack.onDestroy(); } @Override public void showProgress() { progressBar.setVisibility(View.VISIBLE); textView.setVisibility(View.INVISIBLE); } @Override public void hideProgress() { progressBar.setVisibility(GONE); textView.setVisibility(View.VISIBLE); } @Override public void setQuote(String string) { textView.setText(string); Toast.makeText(mContext, "Quote Updated", Toast.LENGTH_SHORT).show(); } } |
Voila! We inject the Presenter in here without instantiating it. Thanks to Dagger2. Now compare this activity source code with the one here.
The output of the above application in action is given below.
This brings an end to MVP and Dagger2 integration tutorial. You can download the AndroidMVPDagger2 Project from the link below.