Previously I had only one AppComponent with four modules (AppModule, NetworkModule, StorageModule, PresentersModule) and injected singletons everywhere. Recently, I decided to make small refactoring in my app and divide it into scopes. I think, presenters can live within activities only, so I created #ActivityScope and ActivityModule, but the project cannot be compiled because of my misunderstanding how to mix these scopes.
I've read a lot of articles and questions at stackoverflow, but everywhere there are simple examples where modules are independent. In my case such thing as
#Singleton
#Component(modules = { AppModule.class, StorageModule.class, NetworkModule.class })
public interface AppComponent {
ActivityComponent plus(PresentersModule module); // <-- error
}
is not working. I get this error:
Error:(19, 1) error: com.my.package.di.component.ActivityComponent scoped with #com.my.package.di.scope.ActivityScope may not reference bindings with different scopes:
#Provides #Singleton android.app.Application com.my.package.di.module.AppModule.provideApplication()
#Provides #Singleton com.my.package.network.FeedBurnerApi com.my.package.di.module.NetworkModule.provideFeedBurnerApi(android.app.Application)
#Provides #Singleton android.database.sqlite.SQLiteOpenHelper com.my.package.di.module.StorageModule.provideSQLiteOpenHelper(android.app.Application)
#Provides #Singleton com.my.package.storage.Repository com.my.package.di.module.StorageModule.provideRepository(android.database.sqlite.SQLiteOpenHelper)
#Provides #Singleton com.my.package.SharedPreferencesHelper com.my.package.di.module.StorageModule.provideSharedPreferencesHelper(android.app.Application)
So, the question is how I can get the instance of my ActivityComponent?
You can see dependencies between modules below:
Application module:
#Module
public final class AppModule {
private final MyApplication mApplication;
public AppModule(MyApplication application) { ... }
#Provides #Singleton Application provideApplication() { ... }
}
Network module:
#Module(includes = { AppModule.class })
public final class NetworkModule {
#Provides #Singleton FeedBurnerApi provideFeedBurnerApi(Application application) { ... }
#Provides #Singleton Retrofit provideRetrofit() { ... }
}
Storage module:
#Module(includes = { AppModule.class })
public final class StorageModule {
#Provides #Singleton Repository provideRepository(SQLiteOpenHelper sqLiteOpenHelper) { ... }
#Provides #Singleton SQLiteOpenHelper provideSQLiteOpenHelper(Application application) { ... }
#Provides #Singleton SharedPreferencesHelper provideSharedPreferencesHelper(Application application) { ... }
}
Presenters module:
#Module(includes = { AppModule.class, NetworkModule.class, StorageModule.class })
public final class PresentersModule {
#Provides FeedPageViewPresenter provideFeedPageViewPresenter(FeedBurnerApi api, Repository repository, SharedPreferencesHelper preferences) { ... }
#Provides #ActivityScope SlidingTabsViewPresenter provideSlidingTabsViewPresenter(Repository repository) { ... }
}
Application component:
#Singleton
#Component(modules = { AppModule.class, StorageModule.class, NetworkModule.class })
public interface AppComponent {}
Activity component:
#Subcomponent(modules = PresentersModule.class)
#ActivityScope
public interface ActivityComponent {
void inject(FeedPageView view);
void inject(SlidingTabsView view);
}
The problem was in my PresentersModule.
Since Subcomponents have access to entire objects graph from their parents, I don't need to include these dependencies in my sub-module.
So, I changed this code:
#Module(includes = { AppModule.class, NetworkModule.class, StorageModule.class })
public final class PresentersModule { ... }
with this:
#Module
public final class PresentersModule { ... }
and it solved my issue.
Related
I can't understand how properly inject Context when I have a component with multiple modules dependencies and I want use Component.Builder annotation.
I have an application module:
#Module
public class ApplicationModule {
#Provides
public AppDatabase provideDatabase(Context context){
return AppDatabase.getInstance(context);
}
}
And here is my ApplicationComponent where I used the Component.Builder in order to provide the context at the dependency graph:
#Singleton
#Component(modules = { ApplicationModule.class } )
public interface ApplicationComponent {
void inject(MainFragment mainFragment);
#Component.Builder
interface Builder {
#BindsInstance
Builder context(Context context);
ApplicationComponent build();
}
}
From my custom application I use the following code in order to provide the context:
appComponent = DaggerApplicationComponent.builder().context(getApplicationContext()).build();
Then I have another Dagger module, used for providing my ViewModelsFactory:
ViewModelModule
#Module
public class ViewModelModule {
#Singleton
#Provides
public MainFragmentViewModelFactory provideMainFragmentViewModelFactory(IVehicleProvider vehicleProvider, IPaymentProvider paymentProvider, IBackgroundOperationResponse response){
return new MainFragmentViewModelFactory(vehicleProvider, paymentProvider, response);
}
}
And the relative ViewModelComponent, where I used again the Component.Builder and as you can see I have three modules here: ViewModelModule, ApplicationModule and ProviderModule
#Singleton
#Component( modules = { ViewModelModule.class, ApplicationModule.class, ProviderModule.class })
public interface ViewModelComponent {
MainFragmentViewModelFactory getMainFragmentViewModelFactory();
#Component.Builder
interface Builder{
#BindsInstance
Builder context(Context context);
#BindsInstance
Builder response(IBackgroundOperationResponse response);
ViewModelComponent build();
}
}
Finally, since MainFragmentViewModelFactory requires IVehicleProvider and IPaymentProvider, I have another module:
ProviderModule
#Module
public abstract class ProviderModule {
#Singleton
#Binds
abstract IVehicleProvider bindVehicleProvider(VehicleProvider vehicleProvider);
#Singleton
#Binds
abstract IPaymentProvider bindPaymentProvider(PaymentProvider paymentProvider);
}
The followings are the constructors of PaymentProvider and VehicleProvider:
#Inject
public PaymentProvider(AppDatabase db){
this.db = db;
}
#Inject
public VehicleProvider(AppDatabase db){
this.db = db;
}
They require an AppDatabase which is provided on ApplicationModule class. AppDatabase requires a Context which is again provided in the ApplicationComponent using the Component.Builder
This code works properly, when I need to use mainFragmentViewModelFactory I just call
mainFragmentViewModelFactory = DaggerViewModelComponent
.builder()
.context(context)
.response(this)
.build()
.getMainFragmentViewModelFactory();
However, I'm not sure I did the correct steps since in the ViewModelModule I requested again a Context dependency, but is already provided in the ApplicationModule. Have I done the correct steps? Instead of create again a BindsInstance Context in the ViewModelComponent can I use the one privided in ApplicationModule?
I am new to dagger, I have defined my application component like this
#Singleton
#Component(modules = {ApplicationModule.class})
public interface ApplicationComponent {
void inject(BaseActivity activity);
Context context();
}
This is my ApplicationModule
#Module
public class ApplicationModule {
public TipoApplication application;
public ApplicationModule(TipoApplication application) {
this.application = application;
}
#Singleton
#Provides
public Context provideContext(){return application.getApplicationContext();}
#Singleton
#Provides
public SharedPreferences provideSharedPreferences(Context context){
return PreferenceManager.getDefaultSharedPreferences(context);
}
#Singleton
#Provides
public Gson provideGson(){
return new Gson();
}
#Singleton
#Provides
public SharedPrefsManager provideSharedPrefsManager(SharedPreferences sharedPreferences, Gson gson){
return new SharedPrefsManager(sharedPreferences, gson);
}
}
I have created a dependent Component LocationProviderComponent
#LocationScope
#Component(dependencies = {ApplicationComponent.class},modules = {LocationProviderModule.class})
public interface LocationProviderComponent {
void inject(LocationRepository locationRepository);
}
And Finally My LocationProviderModule
#Module
public class LocationProviderModule {
#Singleton
#Provides
FusedLocationProviderClient provideFusedLocationProviderClient(Context context) {
return LocationServices.getFusedLocationProviderClient(context);
}
#Singleton
#Provides
LocationRequest provideLocationRequest() {
return new LocationRequest()
.setInterval(5000)
.setFastestInterval(60000)
.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
}
#Singleton
#Provides
LocationSettingsRequest provideLocationSettingRequest(LocationRequest mLocationRequest) {
return new LocationSettingsRequest.Builder().addLocationRequest(mLocationRequest).build();
}
}
I am getting 2 errors when I build.
1st cannot find symbol class DaggerApplicationComponent
2nd
LocationProviderComponent scoped with LocationScope may not reference bindings with different scopes:
#Singleton #Provides FusedLocationProviderClient LocationProviderModule.provideFusedLocationProviderClient(android.content.Context)
#Singleton #Provides LocationRequest .module.LocationProviderModule.provideLocationRequest()
Please tell me what I am doing wrong.
Any module's #Provides method may only have the same scope as the component they are part of. Read more here.
In your case LocationProviderModule is part of the LocationProviderComponent which is scoped with #LocationScope whereas the provides methods in that module uses the #Singleton scope. This is exactly what Dagger is complaining about:
LocationProviderComponent scoped with LocationScope may not reference
bindings with different scopes
It is also pointing to where the problem is:
#Singleton #Provides FusedLocationProviderClient LocationProviderModule.provideFusedLocationProviderClient(android.content.Context)
#Singleton #Provides LocationRequest.module.LocationProviderModule.provideLocationRequest()
Instead of using #Singleton, you just need to use #LocationScope in the LocationProviderModule.
I am using dagger 2.11
Module
#Module
class MyModule {
#Provides
fun provideString() : String = "yo"
#Provides #Named("injector")
fun provideInzectorString() : String = "named_injection"
#Singleton #Provides //The error goes away if I remove #Singleton
fun provideRepository() = Repository(Interceptor(),"")
}
Activity binding module
#Module
abstract class ActivityBindingModule {
#ContributesAndroidInjector(modules = [MyModule::class])
abstract fun suggestionActivity() : SuggestionsActivity
#ContributesAndroidInjector(modules = [MyModule::class])
abstract fun editSubscriptionActivity() : SubscribeActivity
}
AppComponent
#Singleton
#Component(modules = {
AndroidInjectionModule.class,
MyModule.class
})
interface AppComponent {
#Component.Builder
interface Builder {
#BindsInstance
Builder application(MyApplication application);
AppComponent build();
}
void inject(MyApplication app);
}
I get this error on compilation
SubscribeActivitySubcomponent (unscoped) may not reference scoped bindings:
#Singleton #Provides #org.jetbrains.annotations.NotNull
I have seen these solutions 1 and 2. Both ask you to annotate your appcomponent with #Singleton which I am already doing. What is wrong with my code?
The problem is MyModule's scope(Application or singleton) is bigger than an activity scope.
#ContributesAndroidInjector(modules = [MyModule::class])
abstract fun suggestionActivity() : SuggestionsActivity
Remove the two (modules = [MyModule::class])s or define activity specific modules.
You don't need MyModule here. It's redundant because it's already included in the AppComponent.
I'm trying to use the latest version of Dagger 2 V2.11 for Android
Here is my code:
AppComponent:
#Singleton
#Component(modules = {
AndroidInjectionModule.class,
AppModule.class,
ActivityBuildersModule.class,
FragmentBuildersModule.class
})
public interface AppComponent {
void inject(MyApplication myApplication);
#Component.Builder
interface Builder {
#BindsInstance
Builder application(Application application);
AppComponent build();
}
#ExceptionRequestsQualifier
ExceptionRequestsServices exceptionRequestsServices();
}
AppModule:
#Module(includes = {ActivityModule.class, FragmentModule.class})
public class AppModule {
#Provides
CompositeDisposable provideCompositeDisposable() {
return new CompositeDisposable();
}
#Provides
#ExceptionRequestsQualifier
ExceptionRequestsServices provideExceptionRequests() {
return new Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.baseUrl(APIConstants.EXCEPTION_REQUESTS_BASE_URL)
.build()
.create(ExceptionRequestsServices.class);
}
#Singleton
#Provides
NetworkManager provideNetworkManager(Application app) {
return new NetworkManager(app);
}
}
ActivityBuildersModule:
#Module
public abstract class ActivityBuildersModule {
#ActivityScope
#ContributesAndroidInjector
abstract ExceptionRequestsActivity contributeExceptionRequestsActivity();
}
ActivityModule:
#Module()
public abstract class ActivityModule {
#Provides
#ActivityScope
static ExceptionRequestsMvpPresenter<ExceptionRequestsMvpView> bindExceptionRequestsPresenter(
ExceptionRequestsPresenter<ExceptionRequestsMvpView> presenter) {
return presenter;
}
}
FragmentBuildersModule:
#Module
public abstract class FragmentBuildersModule {
#FragmentScope
#ContributesAndroidInjector
abstract AddApplicantFragment contributeAddApplicantFragment();
#FragmentScope
#ContributesAndroidInjector
abstract PledgeFragment contributePledgeFragment();
}
FragmentModule:
#Module()
public abstract class FragmentModule {
#Provides
#FragmentScope
static AddApplicantMvpPresenter<AddApplicantMvpView> bindAddApplicantPresenter(
AddApplicantPresenter<AddApplicantMvpView> presenter) {
return presenter;
}
#Provides
#FragmentScope
static PledgeMvpPresenter<PledgeMvpView> bindPledgePresenter(
PledgePresenter<PledgeMvpView> presenter) {
return presenter;
}
}
AddApplicantPresenter:
public class AddApplicantPresenter<V extends AddApplicantMvpView> extends BasePresenter<V> implements AddApplicantMvpPresenter<V> {
#Inject
#ExceptionRequestsQualifier
ExceptionRequestsServices mExceptionRequestsServices;
#Inject
NetworkManager mNetworkManager;
#Inject
public AddApplicantPresenter(CompositeDisposable compositeDisposable) {
super(compositeDisposable);
}
}
AddApplicantMvpPresenter:
#FragmentScope
public interface AddApplicantMvpPresenter<V extends AddApplicantMvpView> extends MvpPresenter<V> {
void addApplicant(String name, String qatarId,
String date, String mobile,
ChosenImage chosenImage);
}
ActivityScope:
#Scope
#Retention(RetentionPolicy.RUNTIME)
public #interface ActivityScope {
}
FragmentScope:
#Scope
#Retention(RetentionPolicy.RUNTIME)
public #interface FragmentScope {
}
Error Log:
Error:(21, 1) error: mypackagename.di.component.AppComponent scoped with #Singleton may not reference bindings with different scopes:
#Provides #mypackagename.di.scope.ActivityScope mypackagename.ui.exceptionrequests.ExceptionRequestsMvpPresenter<mypackagename.ui.exceptionrequests.ExceptionRequestsMvpView> mypackagename.di.module.ActivityModule.bindExceptionRequestsPresenter(mypackagename.ui.exceptionrequests.ExceptionRequestsPresenter<mypackagename.ui.exceptionrequests.ExceptionRequestsMvpView>)
#Provides #mypackagename.di.scope.FragmentScope mypackagename.ui.addapplicant.AddApplicantMvpPresenter<mypackagename.ui.addapplicant.AddApplicantMvpView> mypackagename.di.module.FragmentModule.bindAddApplicantPresenter(mypackagename.ui.addapplicant.AddApplicantPresenter<mypackagename.ui.addapplicant.AddApplicantMvpView>)
#Provides #mypackagename.di.scope.FragmentScope mypackagename.ui.pledge.PledgeMvpPresenter<mypackagename.ui.pledge.PledgeMvpView> mypackagename.di.module.FragmentModule.bindPledgePresenter(mypackagename.ui.pledge.PledgePresenter<mypackagename.ui.pledge.PledgeMvpView>)
Modules & Components can't have different Scopes
You can have Components to have multiple Scopes and this can solve it.
Try to move it to different component and add it as component dependencies
I hope in future they can solve this, the way I've done it in my project.
Currently, Dagger2 allows module with NoScope & single scope. This should match with your components.
Thumb Rule:: Different scopes have different components.
For your application, you need three components,
FragmentComponent (FragmentScope) :- (Ideally this should be ActivityComponent)
ApplicationComponent (Singleton)
https://medium.com/#patrykpoborca/making-a-best-practice-app-4-dagger-2-267ec5f6c89a
Read more about scopes.
How do I make a Module which has an annotation, like #UserScope, dependent on another Module that's a #Singleton?
#Module(includes = {ApplicationModule.class})
public class JobManagerModule
{
private static final String TAG = JobManagerModule.class.getSimpleName();
#UserScope
#Provides
public JobManager providesJobManager(final Context context)
{
Log.d(TAG, "Providing JobManager");
final Configuration configuration = new Configuration.Builder(context).build();
return new JobManager(configuration);
}
}
Here, JobManagerModule provides using #UserScope but the ApplicationModule provides the Context object (which JobManagerModule needs) as a #Singleton.
This shows errors.
If you want to create a different Scope, then this scope must be a subcomponent of #Singleton.
Suppose you have ApplicationComponent annotated with #Singleton:
#Singleton
#Component(
modules = ApplicationModule.class
)
public interface ApplicationComponent {
JobManagerComponent provide(JobManagerModule jobManagerModule);
}
ApplicationModule provides Context:
#Module
public class ApplicationModule {
protected final Application mApplication;
public ApplicationModule(Application application) {
mApplication = application;
}
#Provides
#ApplicationContext
public Context provideApplicationContext() {
return mApplication;
}
}
Notice, that ApplicationComponent must provide JobManagerComponent
and Context is provided with #ApplicationContext annotation.
Now you create JobManagerComponent as a #Subcomponent of ApplicationComponent:
#UserScope
#Subcomponent(
modules = JobManagerModule.class
)
public interface JobManagerComponent{
}
JobManagerModule:
#Module
public class JobManagerModule
{
private static final String TAG = JobManagerModule.class.getSimpleName();
#UserScope
#Provides
public JobManager providesJobManager(#ApplicationContext Context context)
{
Log.d(TAG, "Providing JobManager");
final Configuration configuration = new Configuration.Builder(context).build();
return new JobManager(configuration);
}
}
Notice the removal of include from #Module annotation and Context annotated with #ApplicationContext
Creation of JobManagerComponent:
JobManagerComponent jobComponent = DaggerApplicationComponent.builder()
.applicationModule(applicationModule)
.build()
.provide(new JobManagerModule());