Dagger 2 Scopes Demystified

Among all Dagger features that I use, the concept of so-called scopes was one of the most difficult to grasp. I finally understood how scopes work only after deep diving into the Dagger generated code.

Today I realize that the best way to learn about Dagger scopes is to simply start with such a review. It sounds complicated, but I assure you that it is not. Well, it is complicated a little bit if you do it on your own, but you don’t have to.

In this post I will take you through the code that Dagger generates and explain the inner workings of scopes.

Dagger 2 dependency injection framework:

I would like to make sure that the terminology I’m going to use is clear, so let’s briefly review the basic constructs of Dagger dependency injection framework. If you’re completely new to Dagger, you might want to start with a proper Dagger tutorial and get back to this post afterwards.

The basic “building blocks” that Dagger uses are as follows:

  • Components. Components are “injectors” that perform actual injection into “clients”.
  • Modules. Modules are the objects in which “object graph” 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 client-service terminology that I will use in this post.

Whenever I say “service” I’m not referring to android Service class, but to some object which is being injected into other object. Whenever I say “client” I’m referring to some object that is being injected with one or more services.

By the way, you can grab the source code that I’m going to use from this GitHub repository.

Injection without scopes:

To better understand what scopes do, let’s review what happens when I inject “non-scoped services”.

For simplicity, I’m 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();
    }

}

This is the component that makes use of TutorialModule and declares MainActivity as a client (i.e. declares that it can inject services into MainActivity):

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

    void inject(MainActivity mainActivity);

}

And the client is also very simple (the contents of activity_main.xml are 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;
    }
}

After building this project I can take a look at the actual code which performs the injection.

Starting with inject(MainActivity) method of TutorialComponent, I trace the usages and implementations of a series of classes and eventually reach 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. Note that the service is not constructed here, but being retrieved from an instance of Provider interface.

To find out which implementation of Provider was used, I 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;
        }
    }
}

As you can see on lines 20-21, TutorialModule_NonScopedServiceFactory is being used as a Provider for instances of NonScopedService.

Let’s see what’s going on inside this 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);
    }
}

The code on line 12 simply obtains an instance of NonScopedService from TutorialModule, which is the class that I myself defined. Looks like when a non-scoped service needs to be injected, the code that Dagger 2 generates simply delegates the instantiation of that service to a method in user-defined Module class.

Imagine that I would like to use the same instance of TutorialComponent to inject NonScopedService into multiple fields. Since each injection into a field results in a new instance being retrieved from the module, each field will point to a different service.

Remember this point because, as we’ll see shortly, it is exactly this mechanism of instantiation of a new service for each field that scopes will affect.

Injection with @Singleton scope:

Dagger 2 recognizes a single predefined scope: @Singleton. Let me show you how to apply scope to service.

Then I’ll dig into what happens when Dagger injects 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 in exactly the same manner as I did for NonScopedService beforehand.

When I do this I 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. While TutorialModule_NonScopedServiceFactory is directly used in MainActivity_MembersInjector, TutorialModule_SingletonScopedServiceFactory is wrapped by ScopedProvider (which is another implementation of Provider interface).

Since this is the only difference between injection mechanisms of NonScopedService and SignletonScopedService by Dagger, 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);
    }
}

Even though this code looks complicated, it is the standard approach to ensuring that only one instance of a service will be returned. This class caches the service returned by the first call to get() method, and then returns the same service on subsequent calls.

This makes ScopedProvider a Caching Decorator – it is a provider that wraps around another provider and caches the instance returned by it.

It is worth noting that the implementation of get() method in ScopedProvider makes use of a thread-safe “double check” idiom. This might be important if you intend to use TutorialComponent from multiple threads. However, in practice, I have never seen a reason to perform injection on non-UI thread in Android.

What would happen if I’d use the same instance of TutorialComponent to inject SingletonScopedService into multiple fields?

Well, since the first injected service is being cached, all fields would end up having a reference to the same object.

This outcome is different from what would happen with fields of type NonScopedService (each of which would point to a different service) and this is the only difference introduced by @Singleton scope annotation.

Injection with user-defined scope:

In addition to a predefined @Singleton scope, I can define my 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, I can use it in my project. For example, I could replace all occurrences of @Singleton annotation with @CustomScope annotation.

If I trace the auto-generated code just like before, I’ll find out that Dagger treats @CustomScope in exactly the same way as it treats @Singleton scope. This means that “all scopes built equal” and the names of the scopes are irrelevant from Dagger’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.

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, it is instantiated and cached inside the injecting component, and then the same exact instance will be used upon injection into 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.

That’s what scopes actually do.

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

That’s all I wanted to tell you about scopes. If you have comments or questions – please leave them below.

Check out my premium

Android Development Courses

13 comments on "Dagger 2 Scopes Demystified"

  1. Great explanation!
    I have one question, if a custom scope is just the same as singleton scope from dagger perspective. Then, why would people create custom scope instead just use the singleton scope?

    Reply
    • Hey Axell,
      I often create @ApplicationScope annotation and use it instead of @Singleton because it’s just more accurate name and I don’t like Singletons. But that’s just me.
      Some developers like using scoping in a more extensive manner than I do. For example, it’s not uncommon to see implementations that use @Singleton for global objects and something like @ActivityScope for objects that need to be reused in a single Activity.
      These are just two examples and I’m sure that there are others as well.

      Reply
      • Uhm okay, but just to be clear one more time. These custom scopes are the same as singleton, right?
        I mean an @ActivityScope service only has one instance, right?
        What I’m trying to say is that these custom scopes are just “names”, right?

        Reply
        • As I said in the article, all scopes are equal. Whether the service is “singleton” or not is determined by it being scoped (with any scope), and the life-cycle of its component.
          For instance, you can define @ActivityScope and use it in ApplicationComponent. Then all services annotated with @ActivityScope will be global objects in application scope. This will probably be a bad idea in terms of naming, but you can go through this exercise to just convince yourself that Dagger doesn’t care about scopes’ names.

          Reply
  2. Thanks a lot Vasiliy for the great article,
    I have some question which will be happy if you answer.
    in the sentence:
    “This might be important if you intend to use TutorialComponent from multiple threads. However, in practice, I have never seen a reason to perform injection on non-UI thread in Android.”
    I don’t exactly understand what you mean by “injection on non-UI thread”, does it mean for example we perform injection in views like fragments or activities?
    I myself see this thread safe concept useful for creating the database instance. I create the instance in its corresponding module with the scope so it will be thread safe as you explained, so to be insured about what I have done about the database instance do you think this is a good act having scope for DB instance and make it thread safe?
    and at last please see if I am right about what I have get from the article that ‘without scopes objects will created repeatedly and we can say Dagger without using scopes is equal to not using Dagger because the concept behind Dagger is to create an object once and reuse it until the application is alive’

    Reply
    • Hello Bita,
      When I say that there is no need to perform injection on non-UI thread, it means that you should call inject() methods on UI thread exclusively.
      In addition, having any object scoped doesn’t guarantee its thread-safety. This is a very complex topic that I can’t address in a comment. For example, it took me several hours to explain what it takes to make your code thread-safe in my Multithreading course. Just remember that scoping doesn’t guarantee thread-safety.
      I don’t think that “the concept behind Dagger is to create an object once and reuse it until the application is alive”. In fact, absolute majority of the object you provide using Dagger shouldn’t be global. IMO, the concept behind Dagger is to centralize the logic that instantiates objects and wires them together.

      Reply

Leave a Comment