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.
A scope is an annotations class with specific additional annotations: @Scope and @Retention. @Scope annotation is provided by Dagger library to define custom scopes. In our example, we create two scopes: @ActivityScope (for activities) and @FragmentScope (for fragments).
In the example above we maintain Singleton scope in the AppComponent which is exist as long as application is alive. We can also add more scope other than Singleton in the sub component of AppComponent by creating a custom scope to reduce an instance lifetime.
Note that a subcomponent can only have one parent. you cannot specify your parent actually. Your parent (or the parent of its parent) should ensure that it has all its child's dependency (aside from the modules of course).
The mechanism by which the full, dependency-injected graph is built is the primary difference between Dagger 1 and Dagger 2. In Dagger 1 the graph was composed via reflection by ObjectGraph , but in Dagger 2 it is done by a @Component -annotated, user-defined type whose implementation is generated at compile time.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With