dagger-android custom scopes - android

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.

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.

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.

Related

Dagger 2 Android: inject the scoped activity

I'm using Dagger Android to link dependencies in my Android application.
I have 2 Activities, both have their own Scope (#ActivityScoped).
Here is my code so far :
MyApplication.java
public class MyApplication extends Application implements HasActivityInjector {
#Inject DispatchingAndroidInjector<Activity> activityDispatchingAndroidInjector;
#Override
public void onCreate() {
super.onCreate();
DaggerAppComponent.builder()
.application(this)
.build()
.inject(this);
}
#Override
public AndroidInjector<Activity> activityInjector() {
return activityDispatchingAndroidInjector;
}
}
AppComponent.java
#Singleton
#Component(modules = {
AndroidInjectionModule.class,
AppModule.class,
ActivityBindingModule.class
})
public interface AppComponent {
#Component.Builder
interface Builder {
#BindsInstance
Builder application(Application app);
AppComponent build();
}
void inject(MyApplication app);
}
ActivityBindingsModule.java
#Module(subcomponents = ActivityComponent.class)
public abstract class ActivityBindingModule {
#ActivityScoped
#ContributesAndroidInjector
public abstract MainActivity contributeMainActivityInjector();
#ActivityScoped
#ContributesAndroidInjector
public abstract SecondActivity contributeSecondActivityInjector();
}
Let's say I have a class MyClass tied to ActivityScoped that I want to inject into both Activities :
MyClass.java
#ActivityScoped
public class MyClass {
#Inject
public MyClass(Activity activity) {
// Do something with the activity instance...
}
}
What I want to achieve is to inject into MyClass the Activity instance of the enclosing scope.
For example, if MyClass is injected into ActivityA, then the activity constructor argument of MyClass should be an instance of ActivityA.
What I tried :
#Binds annotated method to inject ActivityA and ActivityB as Activity. This doesn't work since Dagger does not allow binding multiple implementations to the same base type.
Map Multibindings doesn't suit my needs : I don't want MyClass to know about the Activity in which it is injected.
Maybe I'm missing something, as dependency injection is rather hard to setup. How can I do that with Dagger Android ?
#Binds annotated method to inject ActivityA and ActivityB as Activity. This doesn't work since Dagger does not allow binding multiple implementations to the same base type.
That's the right way to do so. You only get an error when you try to bind multiple implementations to the same type in the same component. So you should not bind both within your AppComponent, but to the #ActivityScoped subcomponents.
You have to create a module for each Activity, A and B, that binds the implementation to Activity. Then you just add that module to the Subcomponent that Dagger creates for you.
#ActivityScoped
#ContributesAndroidInjector(modules={MainActivityBindingsModule.class})
public abstract MainActivity contributeMainActivityInjector();
That way the binding only exists within the #ActivityScoped component and you should be able to repeat it.

Android Dagger 2 - how to make custom scope be a local singleton

According to tutorials like this one: in Dagger2 we are able to provide local singleton objects using a custom scope.
I already have a global appComponent and my goal is to create a activity scope that allows a subcomponent to have local singleton providers.
My issue is when i create a custom scope and inject my activity twice, i see that the object being provided is not the same one, even though i tagged it with the custom scope. for the appComponent which uses the #Singleton annotation it works fine though.
Lets take a look at how i did this:
Here is the custom activity scope:
#Scope
#Retention(RetentionPolicy.RUNTIME)
public #interface ActivityScope {
}
And here is how i define the subcomponent:
#ActivityScope
#Subcomponent(modules = {PresenterModule.class, UseCaseModule.class})
public interface ActivitySubComponent {
void inject(LoginActivity target);
void inject(LoginPresenter target);
void inject(UCGetFireBaseAccount target);
}
Notice here that i've tagged this subcomponent with #ActivityScope annotation that i custom made above.
Now lets see the appComponent which is the parent component and global:
#Singleton
#Component(modules = {AppModule.class, NetworkModule.class, RepositoryModule.class})
public interface AppComponent {
void inject(myappCloudRepo target);
void inject (FireBaseCloudRepo target);
ActivitySubComponent plus(PresenterModule presenterModule, UseCaseModule useCaseModule);
}
Notice ActivitySubComponent is defined here as a subcomponent.
Finally lets look at the presenterModule class to see what i wanted to be provided as a singleton:
#Module
public class PresenterModule {
#Provides
#ActivityScope
LoginPresenter provideLoginPresenter(Context context) {
return new LoginPresenter(context);
}
}
Now when i actually use this i set it up in my application class like this:
public class MyApplication extends Application {
private AppComponent appComponent;
#Override
public void onCreate() {
super.onCreate();
appComponent = initDagger(this);
}
public AppComponent getAppComponent() {
return appComponent;
}
protected AppComponent initDagger(MyApplication application) {
return DaggerAppComponent.builder()
.appModule(new AppModule(application))
.build();
}
public ActivitySubComponent getActivityComponent() {
return appComponent.plus(new PresenterModule(),new UseCaseModule());
}
}
Then lastly in any activity i do this:
public class LoginActivity extends AppCompatActivity{
ActivitySubComponent activityComponent;
//this loginpresenter is from the activitysubcomponent
#Inject
LoginPresenter loginPresenter;
//this retrofit object is from the appcomponent
#Inject
Retrofit retrofit;
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
activityComponent= ((MyApplication)getApplication()).getActivityComponent();
activityComponent.inject(this);
Log.d("j2emanue",loginPresenter.toString());
Log.d("j2emanue",retrofit.toString());
activityComponent= ((MyApplication)getApplication()).getActivityComponent();
activityComponent.inject(this);
Log.d("j2emanue2",loginPresenter.toString());
Log.d("j2emanue2",retrofit.toString());
}
}
When i take a look at the logs after injecting TWICE i am expecting all objects to be the same but instead the loginPresenter is another instance but the retrofit is the same so that works.
Here is the log:
05-23 23:33:35.617 31298-31298/com.myapp.mobile.myappfashion D/j2emanue: com.myapp.mobile.myappfashion.UI.Presenters.LoginPresenter#1c27a460
05-23 23:33:35.617 31298-31298/com.myapp.mobile.myappfashion D/j2emanue: retrofit2.Retrofit#13366d19
05-23 23:33:35.617 31298-31298/com.myapp.mobile.myappfashion D/j2emanue2: com.myapp.mobile.myappfashion.UI.Presenters.LoginPresenter#3dc041de
05-23 23:33:35.617 31298-31298/com.myapp.mobile.myappfashion D/j2emanue2: retrofit2.Retrofit#13366d19
Notice the loginpresenter is another object so its not making a local singleton for me that would only be destroyed when i dereference the activitysubcomponent.
what am i missing here ? The reason i want it to be a local singleton is for configuration changes. This way the presenter can be maintained during a config change.
In your application class you create and return a new subcomponent.
public ActivitySubComponent getActivityComponent() {
return appComponent.plus(new PresenterModule(),new UseCaseModule());
}
Now scopes mean that within a scope an object exists only one time. But in your case, you create 2 different components. Those 2 components will share everything in their parent component (they are subcomponents), but anything within their scope will be recreated, but unique to their scope.
Anything within your component will use the same #ActivityScope annotated objects, but if you create 2 components you will have 2 copies of everything.
If you take #Singleton as an example, it does not mean the object will be an actual Singleton. It is the name of the scope that you are supposed to have for your root component, which should only be created once and be kept during the lifetime of the application.
If you were to create 2 AppComponents that are #Singleton you could observe that same behavior—two different instances of your object.
In your example Retrofit is the same, because you use the same AppComponent both times, but LoginPresenter gets recreated along with every ActivitySubComponent that you create.
With Dagger you should try that your components follow the same lifecycle as what they scope, thus your app should hold an AppComponent, and every Activity should have their own ActivityComponent (keep the component as a member variable!). When you create a new Activity you should create a new #ActivityScope scoped component, but not more often than that.
You should remove your getActivityComponent() from the Application and keep a reference to your ActivitySubComponent, because injecting scoped dependencies with the same component will give you the same objects.
activityComponent.inject(this);
activityComponent.inject(this);
// call it as many times as you'd like.
activityComponent.inject(this);
Just don't recreate your component.

Dagger 2 How to create a Module for Base Activity Components and a separate Module for all MVP components

Hello I am new to Dagger2.
Goal. Take my Networking DI and MVP DI. MVP as in the presenter for an an activity that extends base activity. I want to combine all this into one super module and place this into my base activity. Over time add more presenters.
I do not want 30+ inject statements in my baseActivity.
I am following this example but it is too simple compared to what I am trying to do.
I think the issue is with injecting the API at the base activity. For some reason Dagger is looking for Api in my MVP class.. So that would be a dependency graph issue?
Having spent more time on this.. The issue stems from Mvp's interface of baseActivity or any sub activity that extends baseActivity. That means when it goes to inject, it sees the #inject Api call, and cannot find it. It will work if I add Api to this module, but thats upside down of what I want. I want Component / Module for Application level items. I then want a component / module that has all my different MVP component in one module.. It's like Dagger starts looking for dependencies in the leaf of a tree and getting upset when it doesn't see whats in the root. I need it to go the other way. Be satisfied that I injected the dependency in the Root activity.
Base Activity...
#inject
public ApiClient mClient;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mManager = new SharedPreferencesManager(this);
DaggerInjector.get().inject(this);
}
DaggerInjector
public class DaggerInjector {
private static AppComponent appComponent = DaggerAppComponent.builder().appModule(new AppModule()).build();
public static AppComponent get() {
return appComponent;
}
}
#Component(modules = {AppModule.class, ApiModule.class, MvpModule.class})
#Singleton
public interface AppComponent {
void inject(BaseActivity activity);
}
Api
#Singleton
#Component(modules = {ApiModule.class})
public interface ApiComponent {
void inject( BaseActivity activity);
}
#Module
public class ApiModule {
#Provides
#Singleton
public ApiClient getApiClient(){
return new ApiClient();
}
}
Mvp
#Singleton
#Component(modules = {MvpModule.class})
public interface MvpComponent {
void inject(BaseActivity activity);
}
#Module
public class MvpModule {
#Provides
#Singleton
public MvpPresenter getMvpPresenter(){ return new MvpPresenter();}
}
Error:(16, 10) error: ApiClient cannot be provided without an #Inject constructor or from an #Provides- or #Produces-annotated method. This type supports members injection but cannot be implicitly provided.
ApiClient is injected at
...BaseActivity.ApiClient
...BaseActivity is injected at
MvpComponent.inject(activity)
I found out my problem. I needed to use a subcomponent.
#Singleton
#Subcomponent(modules = {MvpModule.class})
public interface MvpComponent {
void inject(BaseActivity activity);
}
#Module
public class MvpModule {
#Provides
#Singleton
public MvpPresenter getMvpPresenter(){ return new MvpPresenter();}
}
see
Dagger- Should we create each component and module for each Activity/ Fragment
Dagger2 activity scope, how many modules/components do i need?

Dagger 2 Scopes explanation

First of all , I am newbie, just starting to explore dagger, I have some problems with understanding, so hope someone can help me.
I have read a lot about dagger, but still cannot figure out some parts.
I created my ApplicationComponent and it looks like this
#Singleton
#Component(modules = {
ApplicationModule.class,
ThreadingModule.class,
NetworkModule.class,
DatabaseModule.class,
ServiceModule.class,
ParseModule.class,
PreferencesSessionModule.class})
public interface ApplicationComponent {
void inject(BaseActivity baseActivity);
void inject(MainAppActivity mainAppActivity);
void inject(BaseFragment baseFragment);
}
And it works great everything injects correctly, but now I wanna to dive deeper into dagger API and use Custom Scope
I have module called PermissionModule it is used for Android M versions.
#PerActivity
#Module
public class PermissionModule {
#Provides
#PerActivity
PermissionController providePermissionController(Activity activity) {
return new PermissionManager(activity);
}
}
And I want to it to be injected into my activity and be in the memory only when activity is also in memory (actvity lifecycle)
#PerActivity
#Component(modules = {
ActivityModule.class,
PermissionModule.class
})
public interface ActivityComponent {
Activity activity();
void inject(BaseActivity baseActivity);
PermissionModule permissionModule();
}
My ActivityComponent
#PerActivity
#Component(modules = {
ActivityModule.class,
PermissionModule.class
})
public interface ActivityComponent {
Activity activity();
void inject(BaseActivity baseActivity);
PermissionModule permissionModule();
}
And my BaseActivity
public abstract class BaseActivity extends AppCompatActivity implements SpiceManagerProvider, NetworkRequestsExecutor {
// Dependencies are injected by ApplicationComponent
#Inject
protected ApplicationSettingsManager mApplicationSettingsManager;
#Inject
protected SpiceManager mSpiceManager;
#Inject
protected ScheduledThreadPoolExecutor mPoolExecutor;
!!!!!!
Should be injected by activity component
#Inject
protected PermissionController mPermissionController;
And in onCreate()
#Override
protected void onCreate(Bundle savedInstanceState) {
// Injecting dependencies
MyApplication application = MyApplication.get(this);
application.getApplicationComponent().inject(this);
DaggerActivityComponent.builder().activityModule(new ActivityModule(this)).build().inject(this);
mPermissionController.requestPermission(Manifest.permission.ACCESS_FINE_LOCATION);
mPermissionController.requestPermission(Manifest.permission.CAMERA);
super.onCreate(savedInstanceState);
}
I got the error
PermissionController cannot be provided without an #Provides- or
#Produces-annotated method.
.ui.activities.base.BaseActivity.mPermissionController
What is wrong in my code ?
Also not to create new question and it is related to this topic.
How does dagger2 parse Scope annotation, I cannot figure out this. As I understand dagger only recognizes Singleton annotation and all other annotations doesn't affect dagger decision, because all other annotations will have scope of activity ?
so the problem is that you call the ApplicationComponent's inject method first
application.getApplicationComponent().inject(this);
which tries to inject all the members, including the PermissionController. But the ApplicationComponent can not provide this, and that is what Dagger complains about.
The solution is to only call the ActivityComponent's inject() method.
Most probably you do need dependencies provided by the ApplicationComponent at some point. To archive that, you'd need to combine the two components. Dagger provides two ways for that,subcomponents and component dependencies
When using component dependencies, rou would end up with something like this in your Activity's onCreate() method:
DaggerActivityComponent
.builder()
.applicationComponent(application.getApplicationComponent())
.activityModule(new ActivityModule(this))
.build().inject(this);
when you change your components to look something similar to this:
#PerActivity
#Component(
dependencies = ApplicationComponent.class,
modules = {
ActivityModule.class,
PermissionModule.class
}
)
public interface ActivityComponent {
...
}
note that you need to provide dependencies explicitly in the ApplicationComponent when you need it in the ActivityComponent (or any injectors)
If there is any dependency from parent component ( for instance #Componenet (model=AppModel.class) public interface appComponent... ) that you want to use in child component (#ActivityScope #Component (DEPENDENCY=APPCOMPONENT.class, model= ActivityModel.class) public interface activityComponenet... ) you need to expose it in parent component. Only exposed dependencies are accessible downstream (in child components). You do it by writing method from appModel that need to provide dependency downstream in appComponenet interface. Name of the method do not need to match the name of method in appModel, only the return type count.
About your confusion on Dagger scopes , I am hereby specifying some conclusions regarding scopes
Any time a un-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.
If you really want to clearly understand how internally Dagger uses singleton and custom scope , follow this article Dagger 2 Scopes : How It Works Internally

Dagger 2 - modules from different components

I am not quite sure how to solve this with dagger 2.
Lets assume we have ApplicationModule that provides us ApplicationContext
then we have ApplicationComponent that uses just this one module.
Then on top of it we have ActivityModule and ActivityComponent that has dependency on ApplicationComponent.
ActivityComponent is build just like
ApplicationComponent component = ((MyApplication) getApplication()).getComponent();
mComponent = Dagger_ActivityComponent.builder()
.applicationComponent(component)
.activityModule(new ActivityModule(this))
.build();
And then I inject my activity:
mComponent.inject(this);
Now I am able to use everything that is declared inside my ActivityModule, however it is not possible for me to access ApplicationModule.
So the question is how could that be achieved? So that when I build component that depends on another component I can still access module from the first one?
EDIT
I think I have found solutions, after rewatching Devoxx talk by Jake again, I have had to miss that out, whatever I want to use from another components module I have to provide in that component, for example I want to use Context from ApplicationModule then inside ApplicationComponent I have to state Context provideContext(); and it is going to be available. Pretty cool :)
You have already answered your question, but the answer is to specify the provision methods in your "superscoped" component (ApplicationComponent).
For example,
#Module
public class ApplicationModule {
#Provides
#Singleton
public Something something() {
return new Something.Builder().configure().build();
// if Something can be made with constructor,
// use #Singleton on the class and #Inject on the constructor
// and then the module is not needed
}
}
#Singleton
#Component(modules={ApplicationModule.class})
public interface ApplicationComponent {
Something something(); //PROVISION METHOD. YOU NEED THIS.
}
#Scope
#Retention(RetentionPolicy.RUNTIME)
public #interface ActivityScope {
}
#ActivityScope
public class OtherThing {
private final Something something;
#Inject
public OtherThing(Something something) {
this.something = something;
}
}
#Component(dependencies = {ApplicationComponent.class})
#ActivityScope
public interface ActivityComponent extends ApplicationComponent { //inherit provision methods
OtherThing otherThing();
}

Categories

Resources