50

I am reading the source code for Dagger2 Component Scopes Test on GitHub, and I've seen a "custom scope" defined for activities called @ActivityScope, but I've seen it in other projects including the 4-module CleanArchitecture that has its @PerActivity scope.

But literally, the code for the @ActivityScope annotation is the following:

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

import javax.inject.Scope;

/**
 * Created by joesteele on 2/15/15.
 */
@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface ActivityScope {
}

And it is "magically" usable in Modules:

@Module
public class ActivityModule {
  @Provides @ActivityScope Picasso providePicasso(ComponentTest app, OkHttpClient client) {
    return new Picasso.Builder(app)
        .downloader(new OkHttpDownloader(client))
        .listener(new Picasso.Listener() {
          @Override public void onImageLoadFailed(Picasso picasso, Uri uri, Exception e) {
            Log.e("Picasso", "Failed to load image: " + uri.toString(), e);
          }
        })
        .build();
  }
}

Or the CleanArchitecture example:

@Scope
@Retention(RUNTIME)
public @interface PerActivity {}

@PerActivity
@Component(dependencies = ApplicationComponent.class, modules = ActivityModule.class)
public interface ActivityComponent {
  //Exposed to sub-graphs.
  Activity activity();
}

@Module
public class ActivityModule {
  private final Activity activity;

  public ActivityModule(Activity activity) {
    this.activity = activity;
  }

  /**
  * Expose the activity to dependents in the graph.
  */
  @Provides @PerActivity Activity activity() {
    return this.activity;
  }
}

I can clearly see that this has to do with JSR-330 custom scopes, but I really don't understand what exactly is happening here to make it so that this code enables the given module and/or what is provided by a given module to depend on the actual Activity lifecycle, and for there to exist only a single instance but only if that given activity is active.

The docs say this:

Scope

Dagger 1 only supported a single scope: @Singleton. 
Dagger 2 allows users to any well-formed scope annotation. 
The Component docs describe the details of 
    how to properly apply scope to a component.

It says to look at the Component docs page, but that gives me 404. I also saw this, but...

May I ask for some help in clarifying why specifying this custom scope magically makes Activity-level scopes work without an issue?

(The answer is, a subscope can receive dependencies from its superscope, and a subscope exists as long as the component does. And that you need to specify the scopes on your modules, and you need to specify your component dependencies to subscope one superscope.)

EpicPandaForce
  • 79,669
  • 27
  • 256
  • 428
  • https://github.com/google/dagger/tree/master/examples/android-activity-graphs hmm... – EpicPandaForce Apr 29 '15 at 05:57
  • 1
    Here's the correct link to the Components docs page for anyone reading this: http://google.github.io/dagger/api/2.0/dagger/Component.html – Kavi Jun 28 '15 at 23:00

2 Answers2

42

Actually there is no magic. Custom scope annotations are just annotations. They can have any name.

First function of scopes is a way to tell Dagger compiler which scopes are allowed within scoped component. That's why using @ActivityScope dependency in non-@ActivityScope component will fire a compilation error.

In fact components can declare many scopes (e.g. @ActivityScope and @UiScope) and Dagger will treat both of them as single scope - it's called scope aliasing. For example, it's useful in multi module projects - when one Gradle module defines one scope with its Dagger modules and another Gradle module defines another scope, while both of them can be used as single aliased scope in some third Gradle module that defines Dagger component.

Second function is to limit number of instances allowed within scoped component. There are several types of scopes supported:

Unscoped - when no annotation declared. Unscoped dependency will have simple Provider generated without any caching and any instance of that dependency created in component will be new for every new injection (as in constructor, or in module provision method, or just as a field).

Custom scope e.g. @ActivityScope annotation defined with @javax.inject.Scope annotation - Dependencies declared with that scope with have caching Provider with double-check lock generated and only single instance will be created for it within component declared with the same scope and its creation will be thread safe. Note that for every instance of component itself new instance of that dependency will be created.

Reusable scope - declared with @dagger.Reusable annotation - Dependencies declared with that scope may be shared between different components through common parent component and will have caching Provider with single-check lock generated. It is useful when dependency does not necessarily need to have single instance but may be shared for increased performance (less allocations) in single component or between components.

For more info on how scopes work refer to user's guide and Dagger's generated code.

How to define the actual scope is your prerogative. Define the livecycle of your scope component, when it's created and when it destroyed - this is your scope. E.g. @ActivityScope is tied to Activity livecycle and defined like that:

private ActivityComponent component;

@Override
protected void onCreate(Bundle savedInstanceState) {
    component = DaggerActivityComponent.builder().build();
    component.inject(this);
}

@Override
protected void onDestroy() {
    component = null;
    super.onDestroy();
}

So there is no magic. Define your scopes by the semantics of using them. You may also find useful this answer and these examples.

EDIT 14.10.2018 Expanded on scopes functions and types to eliminate ambiguity in previous answer.

Kirill Boyarshinov
  • 6,143
  • 4
  • 33
  • 29
  • Oh. So that's for static analysis. I understand now, thank you. – EpicPandaForce Apr 29 '15 at 12:29
  • 6
    Why is it necessary to set `component = null`? – IgorGanapolsky Apr 30 '15 at 18:39
  • @IgorGanapolsky I don't think it's necessary but it defines scope pretty clear. – Kirill Boyarshinov May 01 '15 at 05:46
  • I kinda wonder if it's smarter to store a scope like this in a headless fragment, because it is attached to the activity lifecycle, but doesn't get destroyed on configuration change. – EpicPandaForce May 15 '15 at 07:13
  • Here is some thinking which suggests a solution to custom scope handling on android http://doridori.github.io/Android-Architecture-Pilot/ – Dori Oct 05 '15 at 14:56
  • @EpicPandaForce `if it's smarter to store a scope like this in a headless fragment,` it isn't. Don't do it man! Use `onRetainCustomNonConfigurationInstance()` or a mortar scope! Or just store it in a `Map` in `Application` but don't think about fragments! – EpicPandaForce Oct 29 '15 at 15:13
  • So the component does not survive screen rotation, right? – Marian Paździoch May 10 '16 at 07:07
  • @EpicPandaForce http://developer.android.com/reference/android/support/v4/app/FragmentActivity.html#onRetainCustomNonConfigurationInstance%28%29 This method is deprecated.!!!! – Marian Paździoch May 10 '16 at 07:10
  • @MarianPaździoch no it's not: onRetainCustomNonConfigurationInstance is not deprecated – EpicPandaForce May 10 '16 at 08:34
  • So if i want to create a activity singleton scope what should i do?? @Singleton really does effect for create a singleton object in a component, and cannot add @Singleton and `@PerActivity` in a provide method same time. – aiueoH Oct 24 '16 at 07:07
  • @aiueoH What you're saying kind of defeats the logic of creating custom scopes? The whole point of custom scopes is to 'scope' your dependencies you want to inject i.e. App Scope, Activity Scope, Fragment Scope etc. There is nothing magic or special about custom scopes, however they are useful for static analysis, see EpicPandaForce answer. Create your Activity without using injection, look at your dependencies ... bang thats your `@ActivityScope` ... this is how I understand it anyhow, I'm very new to Dagger 2 - if anyone can debunk what I'm saying please do! – Mark Nov 18 '16 at 00:15
19

It is worth noting that apparently Dagger2 creates a single instance per scoped provider in a module per component.

So in order to get a scoped provider in a module, you need to specify the scope for your module's provider method.

@Module
public class YourModule {
    @Provides
    @YourScope //one per component
    public Something something() { return new SomethingImpl(); }

    @Provides //new instance per injection
    public Otherthing otherthing() { return new OtherthingImpl(); }
}

@Component
@YourScope
public interface YourComponent {
    Something something();
    Otherthing otherthing();

    void inject(YourThing yourThing); // only if you want field injection
}

EDIT START: though generally, you don't need to instantiate your own implementations inside your modules, so you can actually just do this:

@Module
public abstract class YourModule {
    @Binds
    @YourScope //one per component
    public abstract Something something(SomethingImpl impl);

    @Binds //normally new instance per injection, depends on scope of Impl
    public abstract Otherthing otherthing(OtherthingImpl impl);
}

@Singleton
public class SomethingImpl implements Something {
    @Inject
    SomethingImpl() {
    }
}

// unscoped
public class OtherThingImpl implements OtherThing {
    @Inject
    OtherThingImpl() {
    }
}

@Component
@YourScope
public interface YourComponent {
    Something something();
    Otherthing otherthing();

    void inject(YourThing yourThing); // only if you want field injection
}

EDIT END

Afterwards, refer to Kirill's answer; essentially a "scope" by itself only determines that it is a different scope from the other one. Using component dependencies (or subcomponents) creates a subscope.

@Module
public class SubModule {
    @Provides
    @SubScope
    public ThatThing thatThing() { return new ThatThingImpl(); }
}

@Component(dependencies={YourComponent.class}, modules={SubModule.class})
@SubScope
public interface SubComponent extends YourComponent {
    ThatThing thatThing();

    void inject(SubThing subThing); // only if you want field injection
}

A component can depend on only one other scoped component.

EpicPandaForce
  • 79,669
  • 27
  • 256
  • 428
  • So writing `@YourScope` once on top of @Module is equals to writing `@YourScope` to each method separately inside Module right? – Jemshit Dec 21 '15 at 13:12
  • @JemshitIskenderov I'll be honest, I've never tried that before; you'd need to see if it generates scoped providers or not. – EpicPandaForce Dec 21 '15 at 14:32
  • After a long time (too long) wrestling with the idea of custom scopes, and exactly what they are, do and mean, I found your question and answer - thanks! – Mark Nov 18 '16 at 00:20
  • Why do you think your answer is correct? I think `Kirill Boyarshinov` provided a correct answer too. You're accepting your own answers. – sam_k Sep 15 '18 at 23:00
  • @sam_k because "`Scope annotations serve as a tool for static analysis of dependencies`" isn't really true. Adding a scope on a `@Provides` method makes it generate a ***scoped provider*** and this distinction is very important; though it is true that components themselves require it for validation purposes, and you manage the lifecycle yourself. But scopes really do a bit more than that. Also I should probably mention scoped class with `@Inject` constructor in this answer for completion's sake. Also, I don't get any points for accepting my answer, feel free to upvote other answers too. – EpicPandaForce Sep 16 '18 at 10:19
  • 2
    @EpicPandaForce Hi, you're right. My answer was ambiguous. By 'doing static analysis' I meant that scoped dependencies inside scoped component will be checked by dagger compiler and unknown scopes will be treated as an error unless scope aliasing was used. I'll edit my answer to expand on this. – Kirill Boyarshinov Nov 14 '18 at 05:56