Dagger2 dependency injection

Dagger 2 dependency injection framework:

Dagger 2 is a dependency injection framework for Android and Java. Originally created by Square, it is now being maintained by Google.

An overview of basic Dagger 2 functionality is outside the scope of this post. If you’re new to Dagger 2, you might want to read Android Dagger 2 Tutorial before proceeding.

The list of the basic “building blocks” of Dagger 2 is as follows:

  • Components. Components are “injectors” that perform actual injection into “clients”.
  • Modules. Modules are the objects in which “object graph’s” structure is defined (the way “services” need to be instantiated).
  • Scopes. Scopes are Java annotations which, when used, change the way “services” are being instantiated upon injection.

Please note the terminology: in this post, whenever I say “service” I’m not referring to android Service class, but to some object which is being injected; whenever I say “client” I’m referring to some object who’s fields are being injected.

Source code of the tutorial application used in this post is available on GitHub.

Injection without scopes:

In order to better understand what function scopes fulfill, let us review what happens when we inject “non-scoped services”.

For simplicity, we are going to inject this class:

public class NonScopedService {

    public NonScopedService() {}

}

NonScopedService is being declared as a service (i.e. being declared as “injectable”) in this module:

@Module
public class TutorialModule {

    @Provides
    public NonScopedService nonScopedService() {
        return new NonScopedService();
    }

}

The component which makes use of TutorialModule and declares MainActivity as a client (i.e. declares it as “injection target”):

@Component(modules = {TutorialModule.class})
public interface TutorialComponent {

    void inject(MainActivity mainActivity);

}

And the client is also very simple (the contents of activity_main.xml is irrelevant to our current discussion):

public class MainActivity extends AppCompatActivity {

    @Inject NonScopedService mNonScopedService;

    private TutorialComponent mTutorialComponent;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        getTutorialComponent().inject(this);

        super.onCreate(savedInstanceState);
        
        setContentView(R.layout.activity_main);
    }

    private TutorialComponent getTutorialComponent() {
        if (mTutorialComponent == null) {
            mTutorialComponent = DaggerTutorialComponent.builder()
                    .tutorialModule(new TutorialModule())
                    .build();
        }
        return mTutorialComponent;
    }
}

Since Dagger 2 employs code generation techniques, we can take a look at the actual code which performs the injection after we build the project. Starting with TutorialComponent#inject(MainActivity), we can search for usages and implementations of a series of classes, and will eventually find MainActivity_MembersInjector class:

@Generated("dagger.internal.codegen.ComponentProcessor")
public final class MainActivity_MembersInjector implements MembersInjector<MainActivity> {
  private final MembersInjector<AppCompatActivity> supertypeInjector;
  private final Provider<NonScopedService> mNonScopedServiceProvider;

  public MainActivity_MembersInjector(MembersInjector<AppCompatActivity> supertypeInjector, Provider<NonScopedService> mNonScopedServiceProvider) {  
    assert supertypeInjector != null;
    this.supertypeInjector = supertypeInjector;
    assert mNonScopedServiceProvider != null;
    this.mNonScopedServiceProvider = mNonScopedServiceProvider;
  }

  @Override
  public void injectMembers(MainActivity instance) {  
    if (instance == null) {
      throw new NullPointerException("Cannot inject members into a null reference");
    }
    supertypeInjector.injectMembers(instance);
    instance.mNonScopedService = mNonScopedServiceProvider.get();
  }

  public static MembersInjector<MainActivity> create(MembersInjector<AppCompatActivity> supertypeInjector, Provider<NonScopedService> mNonScopedServiceProvider) {  
      return new MainActivity_MembersInjector(supertypeInjector, mNonScopedServiceProvider);
  }
}

Line 19 is where the actual injection of NonScopedService takes place – the instance of Provider which was passed into this object at construction time is being used to instantiate the service.

In order to find out which implementation of Provider was used, we search for usages of MainActivity_MembersInjector#create() method. It is being called from DaggerTutorialComponent:

@Generated("dagger.internal.codegen.ComponentProcessor")
public final class DaggerTutorialComponent implements TutorialComponent {
  private Provider<NonScopedService> nonScopedServiceProvider;
  private MembersInjector<MainActivity> mainActivityMembersInjector;

  private DaggerTutorialComponent(Builder builder) {  
    assert builder != null;
    initialize(builder);
  }

  public static Builder builder() {  
    return new Builder();
  }

  public static TutorialComponent create() {  
    return builder().build();
  }

  private void initialize(final Builder builder) {  
    this.nonScopedServiceProvider = TutorialModule_NonScopedServiceFactory.create(builder.tutorialModule);
    this.mainActivityMembersInjector = MainActivity_MembersInjector.create((MembersInjector) MembersInjectors.noOp(), nonScopedServiceProvider);
  }

  @Override
  public void inject(MainActivity mainActivity) {  
    mainActivityMembersInjector.injectMembers(mainActivity);
  }

  public static final class Builder {
    private TutorialModule tutorialModule;
  
    private Builder() {  
    }
  
    public TutorialComponent build() {  
      if (tutorialModule == null) {
        this.tutorialModule = new TutorialModule();
      }
      return new DaggerTutorialComponent(this);
    }
  
    public Builder tutorialModule(TutorialModule tutorialModule) {  
      if (tutorialModule == null) {
        throw new NullPointerException("tutorialModule");
      }
      this.tutorialModule = tutorialModule;
      return this;
    }
  }
}

We can see (lines 20-21) that TutorialModule_NonScopedServiceFactory is being used as a Provider for instances of NonScopedService.

Let’s see what’s going on inside TutorialModule_NonScopedServiceFactory class:

@Generated("dagger.internal.codegen.ComponentProcessor")
public final class TutorialModule_NonScopedServiceFactory implements Factory<NonScopedService> {
  private final TutorialModule module;

  public TutorialModule_NonScopedServiceFactory(TutorialModule module) {  
    assert module != null;
    this.module = module;
  }

  @Override
  public NonScopedService get() {  
    NonScopedService provided = module.nonScopedService();
    if (provided == null) {
      throw new NullPointerException("Cannot return null from a non-@Nullable @Provides method");
    }
    return provided;
  }

  public static Factory<NonScopedService> create(TutorialModule module) {  
    return new TutorialModule_NonScopedServiceFactory(module);
  }
}

Now we see (line 12) that any time an instance of NonScopedService needs to be injected, the code that was generated by Dagger 2 simply obtains a new instance from TutorialModule#nonScopedService() method (we defined this method ourselves in TutorialModule class).

Imagine that we would like to use the same instance of TutorialComponent in order to inject NonScopedService into multiple client’s fields. From what we saw above we can conclude that multiple instances of a service would be created and each field would point to a different service. Remember this point, because, as we’ll see shortly, it is exactly this aspect that scopes will affect.

Injection with @Singleton scope:

Dagger 2 recognizes a single predefined scope: @Singleton. Let’s see what happens when we inject a service annotated with @Singleton scope.

The scoped service will also be very simple:

public class SingletonScopedService {

    public SingletonScopedService() {}

}

Please note that the service itself has no dependencies on Dagger 2 framework, and, therefore, is not aware of its scope. I include scope’s name in service’s name just for a purpose of this tutorial, but you should not do the same in your projects.

In order to assign a scope to the service, we add @Singleton annotation to its provider method in TutorialModule class:

@Module
public class TutorialModule {

    @Provides
    public NonScopedService nonScopedService() {
        return new NonScopedService();
    }

    @Provides
    @Singleton
    public SingletonScopedService singletonScopedService() {
        return new SingletonScopedService();
    }

}

Dagger 2 enforces a rule that in order to inject scoped services, the injecting component should also be annotated with the corresponding scope:

@Singleton
@Component(modules = {TutorialModule.class})
public interface TutorialComponent {

    void inject(MainActivity mainActivity);

}

And the client that uses both services becomes:

public class MainActivity extends AppCompatActivity {

    @Inject NonScopedService mNonScopedService;
    @Inject SingletonScopedService mSingletonScopedService;

    private TutorialComponent mTutorialComponent;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        getTutorialComponent().inject(this);

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);
    }

    private TutorialComponent getTutorialComponent() {
        if (mTutorialComponent == null) {
            mTutorialComponent = DaggerTutorialComponent.builder()
                    .tutorialModule(new TutorialModule())
                    .build();
        }
        return mTutorialComponent;
    }
}

Now it is time to trace the methods and classes involved in injection of SingletonScopedService just like we did before for NonScopedService. If you do this, you’ll find out that the only difference in treatment of these services is in DaggerTutorialComponent class:

@Generated("dagger.internal.codegen.ComponentProcessor")
public final class DaggerTutorialComponent implements TutorialComponent {
  private Provider<NonScopedService> nonScopedServiceProvider;
  private Provider<SingletonScopedService> singletonScopedServiceProvider;
  private MembersInjector<MainActivity> mainActivityMembersInjector;

  private DaggerTutorialComponent(Builder builder) {  
    assert builder != null;
    initialize(builder);
  }

  public static Builder builder() {  
    return new Builder();
  }

  public static TutorialComponent create() {  
    return builder().build();
  }

  private void initialize(final Builder builder) {  
    this.nonScopedServiceProvider = TutorialModule_NonScopedServiceFactory.create(builder.tutorialModule);
    this.singletonScopedServiceProvider = ScopedProvider.create(TutorialModule_SingletonScopedServiceFactory.create(builder.tutorialModule));
    this.mainActivityMembersInjector = MainActivity_MembersInjector.create((MembersInjector) MembersInjectors.noOp(), nonScopedServiceProvider, singletonScopedServiceProvider);
  }

  @Override
  public void inject(MainActivity mainActivity) {  
    mainActivityMembersInjector.injectMembers(mainActivity);
  }

  public static final class Builder {
    private TutorialModule tutorialModule;
  
    private Builder() {  
    }
  
    public TutorialComponent build() {  
      if (tutorialModule == null) {
        this.tutorialModule = new TutorialModule();
      }
      return new DaggerTutorialComponent(this);
    }
  
    public Builder tutorialModule(TutorialModule tutorialModule) {  
      if (tutorialModule == null) {
        throw new NullPointerException("tutorialModule");
      }
      this.tutorialModule = tutorialModule;
      return this;
    }
  }
}

Take a close look at lines 21-23 – whereas TutorialModule_NonScopedServiceFactory is being directly passed to MainActivity_MembersInjector, TutorialModule_SingletonScopedServiceFactory is being Decorated by ScopedProvider, which is another implementation of Provider interface. Since this is the only difference in treatment of NonScopedService and SignletonScopedService by Dagger 2 framework, then whatever functional differences @Singleton scope introduces, all of them will be due to Decoration by ScopedProvider class.

Let’s take a look at its implementation:

public final class ScopedProvider<T> implements Provider<T> {
  private static final Object UNINITIALIZED = new Object();

  private final Factory<T> factory;
  private volatile Object instance = UNINITIALIZED;

  private ScopedProvider(Factory<T> factory) {
    assert factory != null;
    this.factory = factory;
  }

  @SuppressWarnings("unchecked") // cast only happens when result comes from the factory
  @Override
  public T get() {
    // double-check idiom from EJ2: Item 71
    Object result = instance;
    if (result == UNINITIALIZED) {
      synchronized (this) {
        result = instance;
        if (result == UNINITIALIZED) {
          instance = result = factory.get();
        }
      }
    }
    return (T) result;
  }

  /** Returns a new scoped provider for the given factory. */
  public static <T> Provider<T> create(Factory<T> factory) {
    if (factory == null) {
      throw new NullPointerException();
    }
    return new ScopedProvider<T>(factory);
  }
}

We can see that ScopedProvider is, in fact, a Caching Decorator – it caches the instance returned by the first call to get() method, and returns it on any subsequent call.

Also worth noting is that the implementation of get() method in ScopedProvider makes use of a thread-safe “double check” idiom. This is important if you intend to use the injected services in multiple threads, because it ensures:

  1. Injection can be safely carried out in multiple threads simultaneously
  2. The injected service can be safely used from multiple threads

While I can hardly imagine why would anybody do #1, #2 is actually a common scenario in multi-threaded applications. If you don’t see why #2 requires such a complicated construct, I encourage you to read this great post by Aleksey ShipilŃ‘v (it concerns safe-initialization and safe-publication of objects in Java).

What would happen if we’d use the same instance of TutorialComponent in order to inject SingletonScopedService into multiple fields? All these fields would point to the same instance of a service, and all these fields would be thread-safe (in context of initialization and publication). This is different from what would happen with NonScopedService fields under the same conditions, and this is the only difference introduced by @Singleton scope annotation.

Injection with user-defined scope:

In addition to a predefined @Singleton scope, we can define our own scopes in the following manner (this code should be in a file named CustomScope.java):

@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface CustomScope {
}

Once the scope is defined in this manner, we can use it in our project. For example, we could replace all occurrences of @Singleton annotation that we had previously with @CusotmScope annotation.

If you do use a custom user-defined scope and trace the auto-generated code as we did before, you’ll see that Dagger 2 treats @CustomScope in exactly the same way as it treats @Singleton scope. What it means is that “all scopes built equal” and the names of the scopes are irrelevant from Dagger 2 framework’s perspective (though I do encourage you to name them descriptively for readability).

Conclusion:

In this post we took an in-depth look at Dagger 2 scopes: we started by investigating a non-scoped injection mechanism, and then compared it to injection with a predefined @Singleton scope and a custom user-defined scope.

The summary of our observations:

  • Any time a non-scoped service is being injected by the same component, a new instance of a service is created.
  • First time a @Singleton scoped service is being injected, a new instance of a service is created and cached inside the injecting component, and the same exact instance will be used upon injection of other fields of the same type by the same component.
  • Custom user-defined scopes are functionally equivalent to a predefined @Singleton scope.
  • Injection of scoped services is thread safe in context of safe-initialization and safe-publication of objects.

Since components cache and reuse the same instance of scoped services upon injection (irrespective of scope’s name), the question of “whether the injected service is singleton or not” can be reduced to a question of “whether instances of a service were injected with the same component”.

Please leave your comments and questions below, and consider subscribing to our newsletter if you liked the post.

This article has 3 comments

  1. Leonardo Miceli Reply

    Thank you, finally a good explanation of how scopes work.
    Please keep up doing these deep analisys.

Leave a Comment

Your email address will not be published. Required fields are marked *