How to Make Dependency Injection with Dagger 2 Easier Using Model View Presenter Pattern
Dependency injection is a design principle that follows the inversion of control concept. However, there are a few drawbacks to dependency injection. Here, we’ll show you how the Model View Presenter (MVP) pattern provides a simple, but elegant solution.
A class should not instantiate its own dependencies, but rather have them configured from outside. Dependency injection wires objects in a flexible way, we can change the wiring without changing the code, and replaces the factory objects in a framework. As inversion of control is the concept behind it, dependency injection is preferred in frameworks. Spring is a popular open-source enterprise Java framework that relies on dependency injection.
Dependency Injection with Dagger 2
Dagger 2 is a popular framework on dependency injection for Android and Java, created by Square and now maintained by Google. It has compile time validation of the object graph, which makes better use of a mobile device’s constrained resources. This is the main reason Dagger 2 is favored over traditional dependency injection frameworks like Spring or Guice. The compile time validation gives Dagger 2 a significant performance advantage – and noticeably improves app start time and responsiveness.
Another significant difference: The entire stack is generated code at compile time that mimics the factory classes the developer might have handwritten. Besides taking routine work from the developer’s plate, Dagger 2 has many benefits that can make your code more elegant and easy to follow. It uses annotations to quickly implement dependencies with easy-to-read code.
Another benefit with dependency injection is the reusable and interchangeable modules. As classes are more modular, there are no more factory classes and simplified testing.
In Dagger 2 the concepts of Module and Component are introduced and they provide a clear separation between which objects are considered dependencies and how to provide them throughout the project. @Module defines the classes that provide dependencies and @Component serves as a connector between dependencies and objects that express dependency.
Of course, there are a few drawbacks to dependency injection. It takes time to understand the concept and read through the components and modules and how they interact. Also, although there is a separation between dependencies and how to classify them in components, in certain situations is hard to break down what are the app components.
The MVP pattern comes with a simple, but elegant solution. It determines what components can exist within the app so the code is easy to read and understand.
Model View Presenter Interactor pattern
The design of Android framework architecture has a great disadvantage – there is no clear separation of concerns. Business logic gets intermingled with view logic in the fragments and activities. Thus, it is harder to test business logic in isolation from the view and user interaction logic. It’s also harder to maintain because the business logic is tightly coupled to the activities and fragments.
A solution is to adopt an architecture that promotes separation of concerns. This will define package structure, classes, and roles for the business and view, keeping these key objects loose coupled.
MVP is an architectural pattern that is becoming more and more popular among Android Developers mainly because of its ability to impose separation of concerns in your code that is lacking in the Android framework. The concept is fairly simple as objects are divided into model, view and presenter. A slightly modified version of this pattern is Model View Presenter Interactor, which adds the Interactor tier for more separation of concerns.
Model – holds all your domain objects, data to be displayed and reacted upon.
View – displays the model to the user. In Android, the view layer can be an Activity, Fragment, etc.
Presenter – where business logic is implemented and serves as a middle-man between your model and view.
Interactor – provides the data to the presenter. It is used to fetch data from remote server APIs, Database etc.
Three cheers for MVP
So far, the MVP pattern is serving its purpose very well. The business logic no longer stays in the activity or fragment, all that code is moved to the presenter and hence, the activity or fragment is only responsible for presenting data to the user and responding to user actions. My favorite part about this pattern is that it improves the code maintainability, and readability as classes are much smaller.
Other popular architectural patterns widely used in software development are MVC (Model View Controller) and MVVM (Model View ViewModel). So why MVP instead of MVC or MVVM?
The advantage MVP has over MVC and MVVM is that is keeps the view completely separate from the presenter. The Android framework implementation is similar to MVC. The activities and fragments serve as controllers, but also as views. Unfortunately, these classes hold too much responsibility as business logic and view logic exists in the activities and fragments.
Data binding library was introduced at Google I/O 2015 making MVVM pattern attractive to Android community. MVVM seems to have great potential as it establishes a connection between view logic and business logic through data binding. But, because data binding is still relatively new to Android, if used incorrectly it can make your code messy and hard to test.
How can you use MVP to improve dependency injection with Dagger 2 in your Android codebase?
As I mentioned earlier, when using Dagger 2 for dependency injection, sometimes it is hard to determine how to break down the components so it best represents the application architecture. This is especially hard when the application keeps growing as new features are added and when some refactoring is made in the code.
A good approach in tackling this problem is to break down your modules and components such that it will favor the MVP pattern. For example, the presenter classes can be dependent on interactor classes that are used to fetch data from an API or a database. Therefore, we can provide an interactor module and component to inject the dependency in the presenter or other classes dependent on the interactor.
InteractorModule class
[code language=”java”]
@Module
public class InteractorModule {
public InteractorModule() { }
@Singleton @Provides
public TicketInteractor providesTicketInteractor() {
return new TicketInteractor();
}
@Singleton @Provides
public UserInteractor providesUserInteractor() {
return new UserInteractor();
}
}
[/code]
InteractorComponent interface
[code language=”java”]
@Singleton
@Component(modules = {InteractorModule.class, DataModule.class})
public interface InteractorComponent {
void inject(RequestTicketPresenter presenter);
void inject(OrderConfirmationPresenter presenter);
void inject(UserInteractor interactor);
}
[/code]
RequestTicketPresenter class
[code language=”java”]
public class RequestTicketPresenter extends BasePresenter<RequestTicketView> {    
@Inject TicketInteractor interactor;    
Ticket ticket;  
 
private RequestTicketPresenter() {        
Injector.getInteractorComponent().inject(this);    
}    
// presenter related methods
}
[/code]
Notice the @Inject annotation is used to mark the dependency needed and then in the constructor the Injector.getInteractorComponent().inject(this) method is called to inject that dependency.
There might be cases when a class has more than one dependency on objects that correspond to the same group. Following the MVP pattern, is possible to get one component that will inject dependencies in the same group.
OrderConfirmationPresenter class
[code language=”java”]
public class OrderConfirmationPresenter extends BasePresenter<OrderConfirmationView> {
@Inject
TicketInteractor ticketInteractor;
@Inject
UserInteractor userInteractor;
@Inject
OrderInteractor orderInteractor;
Ticket ticket;
User user;
public OrderConfirmationPresenter(Ticket ticket) {
this.ticket = ticket;
Injector.getInteractorComponent().inject(this);
}
//presenter related methods
}
[/code]
Notice that OrderConfirmationPresenter.class has dependencies on 3 different interactors and the Injector.getInteractorComponent().inject(this) method will provide all of them, making the code cleaner and shorter.
There might be some cases where additional components not part of MVP layers can be present. An example is the NetworkComponent often used for providing network related dependencies such as HttpClient, Retrofit Service class, etc. Nevertheless, this does not break the pattern, as often such cases need to be handled through the interactor.
Conclusion
As the MVP pattern is getting more recognition in the Android community, it compliments dependency injection well. Dependency injection with Dagger 2 does not follow any patterns for classifying the @Component classes which makes it hard to understand. Implementing the MVP pattern helps break down the modules and components into manageable pieces, greatly simplifies the visual aspect of the code with increased readability and decreases code maintainability efforts.