Android Dagger 2: Critical things to know before you implement.
Dagger in a Dependency Injection framework for Android. I assume the reader is familiar with Dagger and it’s usage pattern in Android for the sake of this Article.
I strongly recommend the reader to go through the two part series Article by me on Dagger.
- Introduction to Dagger 2, Using Dependency Injection in Android: Part 1
- Introduction to Dagger 2, Using Dependency Injection in Android: Part 2
Remember this is a complex topic so get involved actively and analyse the code with a focused mind.
We will analyze Dagger using the project I had created for Part 2 of the above Article series. The project link in mentioned below.
https://github.com/janishar/android-dagger2-example
What are we going to study in the following analysis?
We will study when and how a dependency class is instantiated? And take into account the scenarios that may produce unexpected results.
Let’s understand how we create a singleton class?
A Singleton class only exists with a single instance for the entire application. In Dagger, we create a singleton class by annotating it with
annotation. This works well when we instantiate the class using constructor injection. But fails when we provide that class using @Singleton
in a module under specific scenarios described below.@provides
In the above-mentioned git repository we will change few classes and test for the singleton class.
Using
on a class and providing it using @Singleton
keyword in the new
annotated method of a module.@provides
Create a package sample and add a class DependencySample1 in it.
@Singleton
public class DependencySample1 {
private static final String TAG = "DependencySample1";
private int value;
public DependencySample1(int value) {
this.value = value;
Log.d(TAG, "DependencySample1: " + this.hashCode());
}
}
In
class express its dependency in the constructor.DataManager
@Singleton
public class DataManager {
...
@Inject
public DataManager(@ApplicationContext Context context,
DbHelper dbHelper,
SharedPrefsHelper sharedPrefsHelper,
DependencySample1 dependencySample1Instance) {
mContext = context;
mDbHelper = dbHelper;
mSharedPrefsHelper = sharedPrefsHelper;
mDependencySample1Instance1 = dependencySample1Instance;
}
...
}
Also, in
class express its dependency.DemoApplication
public class DemoApplication extends Application {
...
@Inject
DependencySample1 dependencySample1Instance1;
...
}
In
provide the dependency of this class.ApplicationModule
@Module
public class ApplicationModule {
...
@Provides
DependencySample1 provideDependencySample1() {
Log.d(TAG, "provideDependencySample1: provideDependencySample1 called");
return new DependencySample1(3);
}
}
After runing the app, we observe the following.
Logs reveal that two instances of the
class are created even when the class is annotated with DependencySample1
.@Singleton
- 01–12 08:09:25.389 D/ApplicationModule: provideDependencySample1: provideDependencySample1 called
- 01–12 08:09:25.390 D/DependencySample1: DependencySample1: 746992865
- 01–12 08:09:25.390 D/DataManager: DependencySample1: 746992865
- 01–12 08:09:25.390 D/ApplicationModule: provideDependencySample1: provideDependencySample1 called
- 01–12 08:09:25.390 D/DependencySample1: DependencySample1: 541540870
- 01–12 08:09:25.390 D/DemoApplication: DependencySample1: 541540870
Heap Dump confirmation:
Since we used
on @Singleton
class, we expected to get the same object reference in both the places but we got two different objects. So, how can we solve the above problem?DependencySample1
Now let’s modify the
method in the @Provides
class by adding ApplicationModule
on the provide method for @Singleton
.DependencySample1
@Module
public class ApplicationModule {
...
@Provides
@Singleton
DependencySample1 provideDependencySample1() {
Log.d(TAG, "provideDependencySample1: provideDependencySample1 called");
return new DependencySample1(3);
}
...
}
After running the application with the above modification we observe the following.
Logs reveal that this time only one instance is created and shared in both
and DataManager
class.DemoApplication
- 01–12 08:07:41.004 D/ApplicationModule: provideDependencySample1: provideDependencySample1 called
- 01–12 08:07:41.006 D/DependencySample1: DependencySample1: 186196167
- 01–12 08:07:41.006 D/DataManager: DependencySample1: 186196167
- 01–12 08:07:41.006 D/DemoApplication: DependencySample1: 186196167
Heap Dump confirmation:
Note: In the above example we don’t need to annotate the
class with DependencySample1
when we are annotating the provide method with @Singleton
.@Singleton
So, if we need to make a class singleton and we provide it withkeyword then annotate the method that provides it in the module with
new
in place of putting it on the class.
@Singleton
In cases where we are providing the dependency of a class and the class is able to construct itself from the existing dependencies in the graph. We will get a singleton class by annotating that class with
i.e. we are not using @Singleton
keyword in the provides method.new
Let’s try understand what I mean by the above statement.
Modify the
class with the below code.DependencySample1
@Singleton
public class DependencySample1 {
private static final String TAG = "DependencySample1";
private int value;
@Inject
public DependencySample1(@Named(value = "DependencySample1_Integer") Integer value) {
this.value = value;
Log.d(TAG, "DependencySample1: " + this.hashCode());
}
}
Here we are providing the dependency of DependencySample1
through constructor injection.
is used to help Dagger resolve @Named
type dependency, so to avoid any conflict. We have used Integer Type dependency just for simplicity.Integer
Also, modify the
provide method.ApplicationModule
@Module
public class ApplicationModule {
...
@Provides
@Named(value = "DependencySample1_Integer")
Integer provideDependencySample1Integer() {
Log.d(TAG, "provideDependencySample1: provideDependencySample1Integer called");
return 3;
}
...
}
This time we are providing the Integer dependency that will construct the
through constructor injection. As in the previous case, here also DependencySample1
is injected in DependencySample1
and DemoApplication
class.DataManager
After running the application with the above modification we observe the following.
Logs reveal that we get a singleton
class.DependencySample1
- 01–12 08:34:28.022 D/ApplicationModule: provideDependencySample1: provideDependencySample1Integer called
- 01–12 08:34:28.022 D/DependencySample1: DependencySample1: 746992865
- 01–12 08:34:28.022 D/DataManager: DependencySample1: 746992865
- 01–12 08:34:28.022 D/DemoApplication: DependencySample1: 746992865
Heap Dump confirmation:
Note
If we remove
from the @Singleton
we get the following logs. Confirming that each class get different instance of DependencySample1
class.DependencySample1
- 01–12 08:35:35.385 D/ApplicationModule: provideDependencySample1: provideDependencySample1Integer called
- 01–12 08:35:35.386 D/DependencySample1: DependencySample1: 746992865
- 01–12 08:35:35.386 D/DataManager: DependencySample1: 746992865
- 01–12 08:35:35.386 D/ApplicationModule: provideDependencySample1: provideDependencySample1Integer called
- 01–12 08:35:35.386 D/DependencySample1: DependencySample1: 541540870
- 01–12 08:35:35.386 D/DemoApplication: DependencySample1: 541540870
What happens when we mention a class in a get method in a component class interface and don’t inject it anywhere?
@Singleton
@Component(modules = ApplicationModule.class)
public interface ApplicationComponent {
...
DependencySample1 getDependencySample1();
}
In this case, the
is not instantiated till it is accessed via the component interface method.DependencySample1
- Case 1:
is never called i.e.getDependencySample1()
is not used. Also,applicationComponent.getDependencySample1()
is not injected i.e.DependencySample1
is not used on@Inject
in any class. In this case,DependencySample1
do not get instantiated.DependencySample1
Heap Dump confirmation:
- Case 2:
is called i.e.getDependencySample1()
is used andapplicationComponent.getDependencySample1()
is not injected i.e.DependencySample1
is not used on@Inject
in any class. In this case theDependencySample1
is instantiated whenDependencySample1
is called. All the rules of singleton we witnessed above are applicable here.getDependencySample1()
For the above test we modify the
class to access the DemoApplication
.getDependencySample1()
public class DemoApplication extends Application {
...
private DependencySample1 dependencySample1;
@Override
public void onCreate() {
super.onCreate();
applicationComponent = DaggerApplicationComponent
.builder()
.applicationModule(new ApplicationModule(this))
.build();
applicationComponent.inject(this);
dependencySample1 = applicationComponent.getDependencySample1();
}
...
}
Logs reveal that this time
was instantiated.DependencySample1
- 01–12 05:09:56.670 D/ApplicationModule: provideDependencySample1: provideDependencySample1Integer called
- 01–12 05:09:56.670 D/DependencySample1: DependencySample1: 324132341
Heap Dump confirmation:
Heap dump reveals that one instance of
was created.DependencySample1
We now have understood the various aspects of Dagger with singleton classes. Now let’s focus on Scope to understand how and what happens in a scoped variable?
Scope creates the instance of a class that has the same rules as the Singleton but the difference is that Singleton creates the global single instance and Scope creates the single instance in that scope.
In the above project, I have created a custom scope
to provide dependencies for each Activity.@PerActivity
Let’s modify the
class to provide the ActivityModule
in the same fashion as we did with the DependencySample1
.ApplicationModule
@Module
public class ActivityModule {
...
@Provides
DependencySample1 provideDependencySample1() {
Log.d(TAG, "provideDependencySample1: called");
return new DependencySample1(3);
}
}
Modify
class by annotating with DependencySample1
.@PerActivity
@PerActivity
public class DependencySample1 {
private static final String TAG = "DependencySample1";
private int value;
public DependencySample1(int value) {
this.value = value;
Log.d(TAG, "DependencySample1: " + this.hashCode());
}
}
Modify
class to inject the MainActivity
twice.DependencySample1
public class MainActivity extends AppCompatActivity {
...
@Inject
DependencySample1 dependencySample1;
@Inject
DependencySample1 dependencySample2;
...
}
Logs reveal that two instances of the
class is formed if we use DependencySample1
new
keyword, which is similar to what we found with
.ApplicationModule
- 01–12 04:35:57.697 D/ActivityModule: provideDependencySample1: called
- 01–12 04:35:57.699 D/DependencySample1: DependencySample1: 871533216
- 01–12 04:35:57.699 D/ActivityModule: provideDependencySample1: called
- 01–12 04:35:57.699 D/DependencySample1: DependencySample1: 474084953
Heap Dump confirmation:
When we add
to the provide method then we will get the single instance of @PerActivity
. Similar to what we found with DependencySample1
.@Singleton
@Module
public class ActivityModule {
...
@Provides
@PerActivity
DependencySample1 provideDependencySample1() {
Log.d(TAG, "provideDependencySample1: called");
return new DependencySample1(3);
}
...
}
All other rules are valid as with the
, only that it is associated with each Activity. Each Activity creates new set of dependencies.@Singleton
The above study reveals some of the aspects of the Dagger which must be understood clearly. It can create disasters if not used correctly.