MVP stands for Model-View-Presenter, one of the most popular and commonly used design pattern in android apps. Where “Model” refers to data source, it can be a SharedPreference, Database or data from a Network call. Going by the word, “View” is the user interface and finally “Presenter”, it’s a mediator between model and view. Whatever events occur in a view are passed to presenter and the presenter fetches the data from the model and finally passes it back to the view, where the data is populated in ViewGroups. Now, the main question, why it is so widely used? One of the obvious reason is the simplicity to implement it and it completely separates the business logic, so, easy to write unit-tests. Though it is easy to implement, its implementation requires a lot of boilerplate code, which is one of its downpoints. But, using Dagger2 the boilerplate code can be reduced to a great extent. Let’s see how Dagger2 is used in Loklak Wok Android to implement MVP architecture.
Adding Dagger2 to the project
In app/build.gradle file
dependencies { ... compile 'com.google.dagger:dagger:2.11' annotationProcessor 'com.google.dagger:dagger-compiler:2.11' }
Implementation
First a contract is created which defines the behaviour or say the functionality of View and Presenter. Like showing a progress bar when data is being fetched, or the view when the network request is successful or it failed. The contract should be easy to read and going by the names of the method one should be able to know the functionality of methods. For tweet search suggestions, the contract is defined in SuggestContract interface.
public interface SuggestContract { interface View { void showProgressBar(boolean show); void onSuggestionFetchSuccessful(List<Query> queries); void onSuggestionFetchError(Throwable throwable); } interface Presenter { void attachView(View view); void createCompositeDisposable(); void loadSuggestionsFromAPI(String query, boolean showProgressBar); void loadSuggestionsFromDatabase(); void saveSuggestions(List<Query> queries); void suggestionQueryChanged(Observable<CharSequence> observable); void detachView(); } }
A SuggestPresenter class is created which implements the SuggestContract.Presenter interface. I will not be explaining how each methods in SuggestPresenter class is implemented as this blog solely deals with implementing MVP. If you are interested you can go through the source code of SuggestPresenter. Similarly, the view i.e. SuggestFragment implements SuggestContract.View interface.
So, till this point we have our presenter and view ready. The presenter needs to access the model and the view requires to have an instance of presenter. One way could be instantiating an instance of model inside presenter and an instance of presenter inside view. But, this way model, view and presenter would be coupled and that defeats our purpose. So, we just INJECT model into presenter and presenter into view using Dagger2. Injecting here means Dagger2 instantiates model and presenter and provides wherever they are requested.
ApplicationModule provides the required dependencies for accessing the “Model” i.e. a Loklak API client and realm database instance. When we want Dagger2 to provide a dependency we create a method annotated with @Provides as providesLoklakAPI and providesRealm.
@Provides LoklakAPI providesLoklakAPI(Retrofit retrofit) { return retrofit.create(LoklakAPI.class); } @Provides Realm providesRealm() { return Realm.getDefaultInstance(); }
If we look closely providesLoklakAPI method requires a Retrofit instance i.e. a to create an instance of LoklakAPI the required dependency is Retrofit, which is fulfilled by providesRetrofit method. Always remember that whenever a dependency is required, it should not be instantiated at the required place, rather it should be injected by Dagger2.
@Provides Retrofit providesRetrofit() { Gson gson = Utility.getGsonForPrivateVariableClass(); return new Retrofit.Builder() .baseUrl(mBaseUrl) .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .addConverterFactory(GsonConverterFactory.create(gson)) .build(); }
As the ApplicationModule class provides these dependencies the class is annotated with @Module.
@Module public class ApplicationModule { private String mBaseUrl; public ApplicationModule(String baseUrl) { this.mBaseUrl = baseUrl; } … // retrofit, LoklakAPI, realm @Provides methods }
After preparing the source to provide the dependencies, it’s time we request the dependencies.
Dependencies are requested simply by using @Inject annotation e.g. in the constructor of SuggestPresenter @Inject is used, due to which Dagger2 provides instance of LoklakAPI and Realm for constructing an object of SuggestPresenter.
public class SuggestPresenter implements SuggestContract.Presenter { private final Realm mRealm; private LoklakAPI mLoklakAPI; private SuggestContract.View mView; ... @Inject public SuggestPresenter(LoklakAPI loklakAPI, Realm realm) { this.mLoklakAPI = loklakAPI; this.mRealm = realm; ... } // implementation of methods defined in contract }
@Inject can be used on the fields also. When @Inject is used with a constructor the class also becomes a dependency provider, this way creating a method with @Provides is not required in a Module class.
Now, it’s time to connect the dependency providers and dependency requesters. This is done by creating a Component interface, here ApplicationComponent. The component interface defines where are the dependencies required. This is only for those cases where dependencies are injected by using @Inject for the member variables. So, we define a method inject with a single parameter of type SuggestFragment, as the Presenter needs to be injected in SuggestFragment.
@Component(modules = ApplicationModule.class) public interface ApplicationComponent { void inject(SuggestFragment suggestFragment); }
The component interface is instantiated in onCreate method of LoklakWokApplication class, so that it is accessible all over the project.
public class LoklakWokApplication extends Application { private ApplicationComponent mApplicationComponent; @Override public void onCreate() { super.onCreate(); ... mApplicationComponent = DaggerApplicationComponent.builder() .applicationModule(new ApplicationModule(Constants.BASE_URL_LOKLAK)) .build(); } public ApplicationComponent getApplicationComponent() { return mApplicationComponent; } ... }
NOTE: DaggerApplicationComponent is created after building the project. So, AndroidStudio will show “Cannot resolve symbol …”, thus build the project : Build > Make Module ‘app’.
Finally, in the onCreateView callback of SuggestFragment we call inject method of DaggerApplicationComponent to tell Dagger2 that SuggestFragment is requesting dependencies.
@Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { ... LoklakWokApplication application = (LoklakWokApplication) getActivity().getApplication(); application.getApplicationComponent().inject(this); suggestPresenter.attachView(this); return rootView; }
Resources:
- Vogella : http://www.vogella.com/tutorials/Dagger/article.html
- GDG Cincinnati: https://www.youtube.com/watch?v=cA4iEmWuSB8