
Dagger is a library that makes testing of code much possible. But Dagger can be hard to learn and difficult to wrap around your head even if the concept of Dagger revolves around Builder pattern and dependency injection.
This tutorial is not for learning Dagger but making a testable system ( for e.g. testing of UI with espresso) where you can inject mocks using Subcomponents.
What are subcomponents ?
Subcomponents are those components that are created to append modules to object graph created by main component using which we inject our activity with variables that we need to perform operations as well as mock with.
So if we need a variable that is needed application wide , we can put that in main component. But if need a few parameters that are only specific to an activity we can extend the object graph by declaring a Subcomponent and making sure that scope of that SubComponent is restricted the activity.
This post heavily borrows from and is inspired by Dagger 2 + Espresso 2 + Mockito
This post shows how to create SubComponents , inject parameters from them into Activity and mock them in Instrumentation testing.
To start with we create a basic activity and create a parameter network Api to it
@CustomScope public class MainActivity extends AppCompatActivity { @Inject NetworkApi networkApi;
Using @Inject we are telling Dagger that we want this variable to be set when activity is started. Maybe we will use it to make network calls during the lifecycle of activity
Now to fill it we have three pieces in our puzzle
An Application object that is available to all activities upon creation
A Module to provide real values to this variable when activity is started
and a component that takes data from the module and passes it to activity
( Dagger to that extent is quite simple 😉 )
So we create a Custom Application Object
public class CustomApplication extends Application {
Now we need to somehow tell the Android app to use this Custom Application when the app is started.
We can easily tell that by adding the line in AndroidManifest.xml
<application android:name=".CustomApplication"
Now the CustomApplication Object will be used in place of standard Application Object
Next
Creating a module
We know that we need to get the actual object for networkApi parameter to be instantiated somewhere. We do so in a class called as NetworkApiModule and to let Dagger know that it needs to use this module to provide the value we annotate it with @Module annotation
@Module public class NetworkApiModule { @Provides NetworkApi provideNetworkApi(){ return new NetworkApi(); } }
The implementation details are kept at minimum
Now to make passage of this variable to activity
Add component
@Component public interface AppComponent { AppSubComponent.Builder subComponentBuilder(); }
But this component is slightly different when you compare it with other examples of Dagger. The AppComponent uses a builder to inject the networkApi using a subcomponent because probably we do not need networkApi at global level ( for e.g. because we make calls in only certain actvities and not all ) .
We saw that there is a subcomponent class which is being used by AppComponet , we create it like this
@Subcomponent(modules = {NetworkApiModule.class}) public interface AppSubComponent { public void inject(MainActivity mainActivity); @Subcomponent.Builder public interface Builder{ AppSubComponent subComponent(); } }
A few points to note here.
The subcomponent class is annotated with @SubComponent annotation
It uses NetworkApiModule to get NetworkApi instance
We inject that into our activity
And since we know that we can only inject if we create a connection from AppComponent into SubComponent, we create a builder interface.
We annotate the interface with @Subcomponent.Builder. These annotations are instruction to dagger to help it connect with main component.
We know that it is not the AppComponent but SubComponent that provides that value , we need to connect AppComponent to SubComponent and that is thru the method AppSubComponent subComponent();
Once we get the SubComponent, we know we now can provide network Api object to our Activity.
You can annotate provides method with Singleton if the object that you are supplying to the activity does not need to be recreated every time.
But if that is not the case , we can use something called as Scope.
Now Scope can be used to determine what can and cannot be used for an activity. So if an activity does not need networkApi then it should not be injectible at all. So by using scope we restrict what can be provided to object we are injecting into.
So we create a custom scope ( you can have any name )
@Scope @Retention(RetentionPolicy.RUNTIME) public @interface CustomScope { } And assign this scope to the activity @CustomScope public class MainActivity extends AppCompatActivity { @Inject NetworkApi networkApi; @Override protected void onCreate(Bundle savedInstanceState) { <br data-mce-bogus="1">
And also to subcomponent
@CustomScope @Subcomponent(modules = {NetworkApiModule.class}) public interface AppSubComponent {
<br data-mce-bogus=”1″>
Now we have completed the connection (except for actual injection into activity) . Let us compile the program ( Build -> Rebuild Project) .
If you look up at what has been created you can see that a Dagger Helper is created
DaggerAppComponent . IF you open the source code you will see that all the dependencies you created are part of the code .
Now we add a few things to CustomApplication and Activity classes to help inject the instantiated object into Activity.
public class CustomApplication extends Application { AppSubComponent appSubComponent = createSubComponent(); protected AppSubComponent createSubComponent() { return DaggerAppComponent.builder().build().subComponentBuilder().subComponent(); } @Override public void onCreate() { super.onCreate(); } public AppSubComponent getAppSubComponent() { return appSubComponent; } }
There are two important points
Create Subcomopnent only once and return it
Use generated DaggerAppComponent and build the subcomponent. The last call will chain with inject.
And in activity inject this like
@CustomScope public class MainActivity extends AppCompatActivity { @Inject NetworkApi networkApi; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ((CustomApplication)getApplication()).getAppSubComponent().inject(this); } }
The network api object will now be injected into activity successfully.
In the next part of this tutorial, let us mock the subcomponent