Android Dagger2: Critical things to know before you implement.

Dagger2 in a Dependency Injection framework for Android. I assume the reader is familiar with Dagger2 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 Dagger2.

  1. Introduction to Dagger 2, Using Dependency Injection in Android: Part 1
  2. 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 Dagger2 using the project I had created for Part 2 of the above Article series. The project link in mentioned below.

https://github.com/MindorksOpenSource/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 Dagger2, we create a singleton class by annotating it with @Singletonannotation. This works well when we instantiate the class using constructor injection. But fails when we provide that class using @provides in a module under specific scenarios described below.

In the above-mentioned git repository we will change few classes and test for the singleton class.

Using @Singleton on a class and providing it using new keyword in the @provides annotated method of a module.

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 DataManager class express its dependency in the constructor.

@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 DemoApplication class express its dependency.

public class DemoApplication extends Application {
    ...
    @Inject
    DependencySample1 dependencySample1Instance1;
    ...
}

In ApplicationModule provide the dependency of this class.

@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 DependencySample1 class are created even when the class is annotated with @Singleton.

  1. 01–12 08:09:25.389 D/ApplicationModule: provideDependencySample1: provideDependencySample1 called
  2. 01–12 08:09:25.390 D/DependencySample1: DependencySample1: 746992865
  3. 01–12 08:09:25.390 D/DataManager: DependencySample1: 746992865
  4. 01–12 08:09:25.390 D/ApplicationModule: provideDependencySample1: provideDependencySample1 called
  5. 01–12 08:09:25.390 D/DependencySample1: DependencySample1: 541540870
  6. 01–12 08:09:25.390 D/DemoApplication: DependencySample1: 541540870

Heap Dump confirmation:

Since we used @Singleton on DependencySample1 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?

Now let’s modify the @Provides method in the ApplicationModule class by adding @Singleton on the provide method for 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 DataManager and DemoApplication class.

  1. 01–12 08:07:41.004 D/ApplicationModule: provideDependencySample1: provideDependencySample1 called
  2. 01–12 08:07:41.006 D/DependencySample1: DependencySample1: 186196167
  3. 01–12 08:07:41.006 D/DataManager: DependencySample1: 186196167
  4. 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 DependencySample1 class with @Singleton when we are annotating the provide method with @Singleton.

So, if we need to make a class singleton and we provide it with newkeyword then annotate the method that provides it in the module with @Singleton in place of putting it on the class.

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 @Singleton i.e. we are not using new keyword in the provides method.

Let’s try understand what I mean by the above statement.

Modify the DependencySample1 class with the below code.

@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. @Named is used to help Dagger resolve Integer type dependency, so to avoid any conflict. We have used Integer Type dependency just for simplicity.

Also, modify the ApplicationModule provide method.

@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 DependencySample1 through constructor injection. As in the previous case, here also DependencySample1 is injected in DemoApplication and DataManager class.

After running the application with the above modification we observe the following.

Logs reveal that we get a singleton DependencySample1 class.

  1. 01–12 08:34:28.022 D/ApplicationModule: provideDependencySample1: provideDependencySample1Integer called
  2. 01–12 08:34:28.022 D/DependencySample1: DependencySample1: 746992865
  3. 01–12 08:34:28.022 D/DataManager: DependencySample1: 746992865
  4. 01–12 08:34:28.022 D/DemoApplication: DependencySample1: 746992865

Heap Dump confirmation:

Note

If we remove @Singleton from the DependencySample1 we get the following logs. Confirming that each class get different instance of DependencySample1 class.

  1. 01–12 08:35:35.385 D/ApplicationModule: provideDependencySample1: provideDependencySample1Integer called
  2. 01–12 08:35:35.386 D/DependencySample1: DependencySample1: 746992865
  3. 01–12 08:35:35.386 D/DataManager: DependencySample1: 746992865
  4. 01–12 08:35:35.386 D/ApplicationModule: provideDependencySample1: provideDependencySample1Integer called
  5. 01–12 08:35:35.386 D/DependencySample1: DependencySample1: 541540870
  6. 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 DependencySample1 is not instantiated till it is accessed via the component interface method.

  • Case 1: getDependencySample1() is never called i.e. applicationComponent.getDependencySample1() is not used. Also,DependencySample1 is not injected i.e. @Inject is not used on DependencySample1 in any class. In this case, DependencySample1do not get instantiated.

Heap Dump confirmation:

  • Case 2: getDependencySample1() is called i.e. applicationComponent.getDependencySample1() is used and DependencySample1 is not injected i.e. @Inject is not used on DependencySample1 in any class. In this case the DependencySample1 is instantiated when getDependencySample1() is called. All the rules of singleton we witnessed above are applicable here.

For the above test we modify the DemoApplication class to access the 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 DependencySample1 was instantiated.

  1. 01–12 05:09:56.670 D/ApplicationModule: provideDependencySample1: provideDependencySample1Integer called
  2. 01–12 05:09:56.670 D/DependencySample1: DependencySample1: 324132341

Heap Dump confirmation:

Heap dump reveals that one instance of DependencySample1 was created.

We now have understood the various aspects of Dagger2 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 @PerActivity to provide dependencies for each Activity.

Let’s modify the ActivityModule class to provide the DependencySample1 in the same fashion as we did with the ApplicationModule.

@Module
public class ActivityModule {
    ...
   @Provides
    DependencySample1 provideDependencySample1() {
        Log.d(TAG, "provideDependencySample1: called");
return new DependencySample1(3);
    }
}

Modify DependencySample1 class by annotating with @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 MainActivity class to inject the DependencySample1 twice.

public class MainActivity extends AppCompatActivity {

    ...

    @Inject
    DependencySample1 dependencySample1;

    @Inject
    DependencySample1 dependencySample2;

    ...
}

Logs reveal that two instances of the DependencySample1 class is formed if we use new keyword, which is similar to what we found with ApplicationModule.

  1. 01–12 04:35:57.697 D/ActivityModule: provideDependencySample1: called
  2. 01–12 04:35:57.699 D/DependencySample1: DependencySample1: 871533216
  3. 01–12 04:35:57.699 D/ActivityModule: provideDependencySample1: called
  4. 01–12 04:35:57.699 D/DependencySample1: DependencySample1: 474084953

Heap Dump confirmation:

When we add @PerActivity to the provide method then we will get the single instance of DependencySample1. Similar to what we found with @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 @Singleton, only that it is associated with each Activity. Each Activity creates new set of dependencies.

The above study reveals some of the aspects of the Dagger2 which must be understood clearly. It can create disasters if not used correctly.

Let’s become friends on Twitter, Linkedin, Github, and Facebook.

Learning is a journey, let’s learn together!