Dagger 2 - modules from different components - android

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();
}

Related

dagger-android custom scopes

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.

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

How inject the Activity to object that is being injected to the Activity?

I have this module:
#Module
public class UserProfileModule {
#Provides
#Singleton
UserProfileController providesUserProfileController() {
return new UserProfileController();
}
}
and this component:
#Component(modules = {UserProfileModule.class})
#Singleton
public interface AppComponent {
void inject(UserProfileActivity activity);
}
So far, in My UserProfileActivity I can #Injectan UserProfileController. But now, I need to inject the UserProfileActivity to the controller. I mean, inject each other.
I could do it by calling a UserProfileController setter in UserProfileActivity: setActivity(this);, but it would be nice if can be automatic.
How can achieve that?
Thanks.
For starters: add it to the constructor. Then declare that dependency.
#Provides
#Singleton
UserProfileController providesUserProfileController(UserProfileActivity activity) {
return new UserProfileController(activity);
}
After doing so dagger will complain about not being able to provide UserProfileActivity unless you already do so. If you don't, add another module, or just provide the dependency from that same module. The actual implementation follows, first we need to fix your code.
#Singleton is a dependency on top of the hierarchy. You can't—or at least should not—have an activity dependency for a #Singleton annotated object, since this will probably cause bad smells and/or memory leaks. Introduce a custom scope #PerActivity to use for dependencies within your activities lifespan.
#Scope
#Retention(RUNTIME)
public #interface PerActivity {}
This will allow for correct scoping of the object. Please also refer to some tutorials about dagger, since this is a really important issue and covering everything in a single answer would be too much. e.g. Tasting dagger 2 on android
The following uses the latter approach of the aforementioned 2 options by expanding your module:
#Module
public class UserProfileModule {
private final UserProfileActivity mActivity;
public UserProfileModule(UserProfileActivity activity) {
mActivity = activity;
}
#Provides
#PerActivity
UserProfileActivity provideActivity() {
return mActivity;
}
#Provides // as before
#PerActivity
UserProfileController providesUserProfileController(UserProfileActivity activity) {
return new UserProfileController(activity);
}
}
If you now use your component Builder you can create a new instance of your module with the activity as an argument. The dependency will then correctly be supplied.

Injecting from many scopes at Dagger 2

I want to inject objects from two scopes at the same class.
e-g. I am implementing MVP. As a view I have a fragment. And in those fragment I want to inject:
presenter from ApplicationScope (I use Singleton)
derivatives of RecyclerView.Adapter and LayoutManager from ActivityScope.
Is it possible? What are the best practise to achieve this?
Yes, it's definitely possible. There are probably different ways to accomplish that.
It's straightforward as long as each component specifies a scope. For example, if your application-level dependencies are #Singleton then your activity-level component also needs a #Scope like #ActivityScope...
For example: application/singleton scope component:
#Singleton
#Component
public interface ApplicationComponent {
MainPresenter getMainPresenter();
}
Activity module:
#Module
public class MainModule {
private final Activity mActivity;
public MainModule(Activity activity) {
mActivity = activity;
}
#Provides
Context provideContext() {
return mActivity;
}
// and other activity-specific dependencies here...
}
and matching activity-level component:
#ActivityScope
#Component(dependencies = ApplicationComponent.class, modules = MainModule.class)
public interface MainComponent {
void initialize(MainActivityFragment mainActivityFragment);
}
then it all just works... for example you can inject both the singletons and activity-scope dependencies into the fragment:
final MainComponent mainComponent = DaggerMainComponent.builder()
.applicationComponent(((App) getActivity().getApplication()).getComponent())
.mainModule(new MainModule(getActivity()))
.build();
...
mainComponent.initialize(this);
See the full working code here.
PS. I'm not so sure it's best to make the presenters singletons though :-) For one reason, I'd expect to inject activity-level dependencies into the presenter, but this way it won't be possible... Let me know how this works for you and if you find another approach to retain the presenters.

Dagger 2 error: dependency "cannot be provided without an #Inject constructor" while it actually annotated with #Inject

I've started using Dagger 2 and faced strange issue that looks like a bug to me.
I have 3 modules, that are composed into one subcomponent, which in turn extends/pluses higher level component.
Subcomponent is pretty simple: just combination of modules and a single injection point:
#Singleton
#Subcomponent(
modules = {
NavigationDrawerModule.class,
NavigationListModule.class,
SwitcherModule.class
}
)
public interface NavigationDrawerComponent {
NavigationDrawerFragment inject(NavigationDrawerFragment object);
}
First modules looks like this - it provides general fragment-level dependencies:
#Module
public class NavigationDrawerModule {
private final Activity activity;
private final View rootView;
private final LoaderManager loaderManager;
public NavigationDrawerModule(Activity activity, View rootView, LoaderManager loaderManager) {
this.activity = activity;
this.rootView = rootView;
this.loaderManager = loaderManager;
}
#Provides #Singleton EventBus provideLocalBus() {
return EventBus.builder().build();
}
#Provides #Singleton View provideViewRoot() {
return rootView;
}
#Provides #Singleton LoaderManager provideLoaderManager() {
return loaderManager;
}
#Provides #Singleton Context provideContext() {
return activity;
}
}
Second module looks like this - it provides presenter/controller and their dependencies for a subset of UI on screen:
#Module
public class SwitcherModule {
#Provides SwitchController provideSwitcherController(SwitchControllerImpl impl) {
return impl;
}
#Provides SwitcherView provideSwitcherView(SwitcherViewImpl impl) {
return impl;
}
}
Third module - another presenter/controller for a subset of UI:
#Module
public class NavigationListModule {
#Provides #Singleton NavigationListController provideNavigationListController(NavigationListControllerImpl impl) {
return impl;
}
#Provides #Singleton NavigationListView provideNavigationListView(NavigationListViewImpl impl) {
return impl;
}
}
Relevant part of the fragment that is being injected:
#Inject SwitchController identitySwitchController;
#Inject SwitcherView identitySwitcherView;
#Inject NavigationListController navigationListController;
#Inject NavigationListView navigationListView;
NavigationListControllerImpl implements the following constructor:
#Inject
public NavigationListControllerImpl(Context ctx, EventBus bus) {
this.ctx = ctx;
this.bus = bus;
}
Error I'm getting from the Dagger 2 compiler is the following:
error: ...sidenavigation.navigationlist.NavigationListControllerImpl cannot be provided without an #Inject constructor or from an #Provides-annotated method.
...sidenavigation.NavigationDrawerFragment.navigationListController
[injected field of type: ...sidenavigation.navigationlist.NavigationListController navigationListController]
...sidenavigation.navigationlist.NavigationListModule.provideNavigationListController(...sidenavigation.navigationlist.NavigationListControllerImpl impl)
[parameter: ...sidenavigation.navigationlist.NavigationListControllerImpl impl]
Error complains about missing #Inject-annotated constructor, but it exists! If I replace implicit NavigationListControllerImpl instance creation (passing via #Provides-method parameter) with explicit (with new), dagger starts complaining about the same error but now for the presenter object which is the second entry in the same module, and so on.
All this situation looks very strange, and I'd like to hear some input from more experienced Dagger 2 users (and developers?).
Thank you in advance!
I got this same error because I forgot to expose the objects provided by the modules in the parent component to the other components that are depend on it.
Parent component example:
#Singleton
#Component(modules = {AppModule.class})
public interface AppComponent {
AppPref exposeAppPref(); /* my issue was caused by forgot this line,
the method name doesn't matter, what matters is the object type AppPref provided in the AppModule
that you want it to be available in the component that declares this component as one of its dependencies*/
}
Sample component that makes the above component as a dependency
#UserScope
#Component (dependencies = {AppComponent.class})
public interface ActivityComponent {
void inject(MainActivity activity);
}
Update:
AppModule:
...
#Provides
#Singleton
AppPref provideAppPref() {
return appPref;
}
...
The GlobalComponent and the subcomponent NavigationDrawerComponent must have different scopes. Use #Singleton for your GlobalComponent and some another scope for the subcomponent.
Otherwise, if you apply the same scope to the GlobalComponent and to the subcomponent, you must declare the modules of your subcomponent in your global component as well:
#Component(
// modules from subcomponent must be declared here also
modules = {NavigationListModule.class,
SwitcherModule.class,
NavigationDrawerModule.class,
...}
)
#Singleton
public interface GlobalComponent {
NavigationDrawerComponent plus(NavigationDrawerModule module);
}
For your use case, you can also use component dependencies. For instance:
#Component(
dependencies = GlobalComponent.class,
modules = {NavigationListModule.class,
SwitcherModule.class,
NavigationDrawerModule.class}
)
#YourOtherDaggerScope // #Singleton scope won't work here, it must be a different scope
public interface NavigationDrawerComponent extends GlobalComponent { // extend the parent component if you wish to get access to parent dependencies
NavigationDrawerFragment inject(NavigationDrawerFragment object);
}
Seems like I've figured out what was wrong with my Dagger 2 setup. It's not possible to use the same scope in both component and subcomponents. It's required to define a new scope for subcomponent. In my case I've ended up creating #Screen scope for me subcomponent.
I'd say that this is a small but very annoying defect in Dagger 2. Apparently dagger-compiler reports nice and understandable error about the same scopes in a parent component and child component if child component is extended with a parent component as dependency. But completely misleading error is reported by the compiler if parent component and child subcomponent share the same scope.
Thank you, #lukas, for giving me a hint here https://stackoverflow.com/a/30383088/808313 that led to a problem resolution.
Came accross this issue today too. For me there was a problem with the Annotation processing (on Android Studio 2.2 with gradle 2.x).
Instead of ~~apt~~ I used annotationProcessor
I used
annotationProcessor 'com.google.dagger:dagger-compiler:2.6'
and now It's working.
Came across the same issue while trying to create Subcomponents, but it seems to be fixed in Dagger 2.0.1.
Seems it is the same kinda error dagger reports for many mistakes. In my case, my target injection was expecting concrete class (Presenter) where as the module that provides presenter was returning only the interface (DemoContract.Presenter)
So changed from
#Inject
public Presenter mDemoPresenter;
to
#Inject
public DemoContract.Presenter mDemoPresenter;
and module that provides presenter looks like this:
#Module
public class DiDemoPresenterModule {
private final DemoContract.View mView;
DiDemoPresenterModule(MainActivity mView) {
this.mView = mView;
}
#Provides
public DemoContract.Presenter providesDemoPresenter(Repository repository) {
return new DemoPresenter(repository, mView);
}
}

Categories

Resources