Android DI using Dagger
We will continue with our dependency graph from the previous tutorial on Dependency Injection in Android, to learn how to implement DI using the Dagger Framework. By end of this tutorial you will grasp the basics of Dagger, from where you can go on to more advanced Dagger, Hilt or other DI frameworks tutorials.
1- Basics of Dagger Framework
For all parts of this tutorial we will be comparing with what we did in the DI manual implementation tutorial to understand the role of Dagger in evolving from boilerplate code to more efficient dependencies management. Using Dagger on the previous manual DI code example we will have the following benefits:
- AppContainer class will be generated by Dagger as what we call an ” application graph ” .
- Using scopes will help decide whether to reuse a created dependency or create a new instance.
- Using Dagger subcomponents to create containers for different flows in the app (as the single container we created for the elections flow in previous manual DI example) .
- Creating factories for the objects of the application graph (as how we manually created the Factory interface for our VoterViewModel in previous manual DI example) .
With dagger you only need to specify, using annotations, how to satisfy your class dependencies. The framework takes care of everything automatically at build time and generates code similar to what we wrote manually in previous DI tuorial: for each class of the dependencies graph, it creates a factory class to be used to generate instances of that dependency.
Now let’s see each of this Dagger notions by exapmle, we’ll take our previous VoterRepository example to understand Dagger factories, components, and scopes :
Dagger factories:
In the manual implementation we defined the VoterRepository as follows :
class VoterRepository( private val localDataSource: VoterLocalDataSource, private val remoteDataSource: VoterRemoteDataSource ) { ... }
Using the @Inject annotation on VoterRepository’s constructor as shown below, Dagger will know how to create instances of the VoterRepository. Dagger will also know through this annotation on the construcor that the VoterRepository class has two dependecies to VoterLocalDataSource and to VoterRemoteDataSource.
class VoterRepository
@Inject
constructor(
private val localDataSource: VoterLocalDataSource,
private val remoteDataSource: VoterRemoteDataSource
) { ... }
Dagger components:
As explained, Dagger creates a graph of the dependencies and uses this graph to find out where to get these dependencies when needed. This is made possible with the use of @Component annotation on an interface, in which we define functions that return instances of the dependency classes we need.
Using the @Component annotation on an interface with defined functions is the Dagger alternative for the AppContainer Class we wrote in the previous manual DI tutorial : it contains a graph that consists of the objects that Dagger knows how to provide and their respective dependencies.
@Component
public interface ApplicationGraph {
VoterRepository voterRepository();
}
At build time Dagger generates an implementation of this interface: DaggerApplicationGraph. Then based on the @Inject annotations in the VoterRepository constructor, Dagger creates a dependency graph presenting the relationships between the three classes (VoterRepository, VoterLocalDataSource, and VoterRemoteDataSource) with only one entry point: getting a VoterRepository.
ApplicationGraph applicationGraph = DaggerApplicationGraph.create(); VoterRepository voterRepository = applicationGraph.voterRepository();
Dagger scopes:
The previous implementation allows us to have a new instance of VoterRepository everytime it’s created:
ApplicationGraph applicationGraph = DaggerApplicationGraph.create(); VoterRepository voterRepository = applicationGraph.voterRepository(); VoterRepository voterRepository2 = applicationGraph.voterRepository();
If an object is expensive to create or if our logic requires the same instance of an object across multipe functions, we might need to impose having a unique instance of a dependency in a container.
This is achieved by simply informing Dagger that if a component have some @xAnnotation, the all classes annotated with it are scoped to the component and that the same single instance of that type is provided everytime the type is requested.
To do so, we can either create our custom scope annotation as follows :
@Scope @Retention(RetentionPolicy.RUNTIME) public @interface CertainCustomFlowScope {}
Or, simply use the @Singleton annotation as follows:
@Singleton ( or @CertainCustomFlowScope ) @Component public interface ApplicationGraph { VoterRepository voterRepository(); } @Singleton ( or @CertainCustomFlowScope ) public class VoterRepository { private final VoterLocalDataSource voterLocalDataSource; private final VoterRemoteDataSource voterRemoteDataSource; @Inject public VoterRepository(VoterLocalDataSource voterLocalDataSource, VoterRemoteDataSource voterRemoteDataSource) { this.voterLocalDataSource = voterLocalDataSource; this.voterRemoteDataSource = voterRemoteDataSource; } }
2- Dagger in Android projects
So to simply apply the basic Dagger principles we saw in the first section to an Android application, we will have to start with:
1- adding the Dagger library dependency to gradle:
implementation 'com.google.dagger:dagger:2.x' annotationProcessor 'com.google.dagger:dagger-compiler:2.x'
2- defining our Application Component interface:
@Component public interface ApplicationComponent { }
3- referencing the application component within our application class so that it shares its life cycle:
public class MyApplication extends Application { ApplicationComponent appComponent = DaggerApplicationComponent.create(); ... }
After these basic steps, we will have to think about injection of and within activities. As we know, activities and fragments are instanciated by the system, which means that Dagger can not create them for us, and we can not annotate ” their constructor ” with @Inject !
In this case, we use field injection :
public class ElectionsActivity extends Activity { @Inject ElectionsViewModel electionsViewModel; }
In the previous section, we added the voterRepository() definition to our AppComponent interface to expose or enable getting objects for type voterRepository from the graph. In this case – working with activities- , we will tell our appComponent that we have an interface wich requires X object, instead of exposing X object .
So in ApplicationComponent we ” mention ” that our activity needs dependencies to be satisfied, and in that activity we define the dependencies needed using field injection, as an equivalence to, simply exposing objects in appComponent when we dont have activities or fragments (but simple classes instead):
@Component public interface ApplicationComponent { void inject(ElectionsActivity electionsActivity); }
So, to inject an object in the ElectionsActivity, we have to use the appComponent we defined in our application class and call the inject method as follows :
public class ElectionsActivity extends Activity { @Inject ElectionsViewModel electionsViewModel; @Override protected void onCreate(Bundle savedInstanceState) { ((MyApplication) getApplicationContext()).appComponent.inject(this); // now electionsViewModel is available super.onCreate(savedInstanceState); } }
Then from here, we continue to define the rest of our dependencies to build the graph :
public class VoterViewModel { private final VoterRepository voterRepository; @Inject public VoterViewModel(VoterRepository voterRepository) { this.voterRepository = voterRepository; } } public class VoterRepository { private final VoterLocalDataSource voterLocalDataSource; private final VoterRemoteDataSource voterRemoteDataSource; @Inject public VoterRepository(VoterLocalDataSource voterLocalDataSource, VoterRemoteDataSource voterRemoteDataSource) { this.voterLocalDataSource = voterLocalDataSource; this.voterRemoteDataSource = voterRemoteDataSource; } }
That’s all coders ! This is all you need to grasp Dagger basics in Android. More is left to be checked, like Dagger modules, Dagger subcomponents, testing in Dagger, and others. In coming tutorials we will see a more advanced Dagger example to cover real implementation of an Android project with Dagger for DI.
Recent Comments