9

I'm confused about scoped dependencies in Dagger using dagger-android.

Using @ContributesAndroidInjetor I have a code something like the following:

@Module
public abstract class ActivityBindingModule {

    @ContributesAndroidInjector(modules = PotatoesModule.class)
    public abstract MainActivity contributeMainActivityInjector();

    @ContributesAndroidInjector
    public abstract UserActivity contributeUserActivity();
}

The ActivityBindingModule is defined as a module in my AppComponent. But the problem is. How can I do something like

@UserScope
@Component(dependencies = AppComponent.class)
public interface UserComponent {...}

And annotate an Activity to use that scope? Is all my dependencies inside activity "local singletons"? Because each Activity injector is a subcomponent of AppComponent.

Maybe I'm not understanding the concept of "scopes" using dagger-android, I would be glad if someone could explain it.

2 Answers2

3

Here's some clarification on scopes:

Say you had an AppComponent and you Annotate it with the @Singleton annotation:

@Singleton
@Component(modules = {
        AndroidInjectionModule.class,
        AppModule.class

})
public interface AppComponent extends AndroidInjector<BaseApplication> {

    @Component.Builder
    interface Builder{

        @BindsInstance
        Builder application(Application application);

        AppComponent build();
    }

}

And you had an AppModule which provide App level dependencies (i.e. a Retrofit Instance for example that you annotate with @Singleton):

@Module
public class AppModule {

    @Singleton
    @Provides
    static Retrofit provideRetrofitInstance(){
        return new Retrofit.Builder()
                .baseUrl(Constants.BASE_URL)
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .addConverterFactory(GsonConverterFactory.create())
                .build();
    }
}

Then we can say that AppComponent owns the @Singleton scope and therefore the @Singleton annotation that you put on the Retrofit instance that you provided now has the same scope as the AppComponent - i.e. it's an application level scope.

If you want to scope to Activities - you should make a custom scope like this:

@Scope
@Documented
@Retention(RUNTIME)
public @interface UserScope {
}

Then in your ActivityBindingModule (that you've written), annotate the UserActivity with @UserScope if you want the UserActivity to "own" the @UserScope scope. Also, add a module next to the @ContributesAndroidInjector - let's call it UserModule.class:

@Module
public abstract class ActivityBindingModule {

    @ContributesAndroidInjector(modules = PotatoesModule.class)
    public abstract MainActivity contributeMainActivityInjector();

    @UserScope
    @ContributesAndroidInjector(modules = UserModule.class)
    public abstract UserActivity contributeUserActivity();
}

And now, creating UserModule.class and annotating a provided dependency with @UserScope:

@Module
public class UserModule {

    @UserScope
    @Provides
    static User provideUser(){
        return new User();
    }
}

This dependency now has the same scope as the UserActivity. So when UserActivity is destroyed and re-created, the dependency provided will also be destroyed and recreated.

To finish up:

Create a POJO User:

public class User {
    public User() {

    }
}

and now, if you go to your UserActivity and do:

public class UserActivity extends DaggerAppCompatActivity {

 private static final String TAG = "UserActivity";

 @Inject
 User aUser;

 @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_user);

        Log.d(TAG, "onCreate: " + aUser);

    }
}

If you run your App now you will see a memory address being printed to the log. Rotate the device to destroy and re-create the activity and you'll see that the memory address changes. This is how we know that the @UserScope is working correctly.

If you want to see your Application scope in action i.e. @Singleton, then create an AppModule, add it to your AppComponent and provide a User dependency in that module and annotate it with @Singleton. Remember to use the @Named annotation too since you now have 2 dependencies that are provided with the same return type (that can both be accessed within the Activity Scope).

Go to your UserActivity again and Inject both Users (remember to use @Named). Log it in another logging statement and after rotating the device you will notice you have the same memory address for the Application scoped dependency.

I hope this cleared things up.

F.Z
  • 31
  • 3
0

Is all my dependencies inside activity "local singletons"? Because each Activity injector is a subcomponent of AppComponent.

The subcomponents generated by dagger-android are unscoped, unless you annotate the @ContributesAndroidInjector-annotated method with a scope.

But the problem is. How can I do something like ... @Component(dependencies = AppComponent.class) ... And annotate an Activity to use that scope?

As far as I know, you can only use subcomponents with dagger-android. Furthermore activity subcomponents must be declared in modules installed in the application component, while fragment subcomponents may be declared in modules installed in either application, activity or fragment components.

I'm not sure what you mean by "annotate an Activity to use that scope" though.

arekolek
  • 9,128
  • 3
  • 58
  • 79