Dagger 2: use Component.Builder in a component with multiple modules dependencies - android

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?

Related

Error when trying to inject a named dependency

I have this AppModule class that contains a few functions:
#Singleton
#Provides
static FirebaseFirestore provideFirebaseFirestore() {
return FirebaseFirestore.getInstance();
}
#Singleton
#Provides
#Named("barsRef")
static CollectionReference provideBarsCollRef(FirebaseFirestore db) {
return db.collection("bars");
}
Now I want to inject in my repository class an instance of CollectionReference but I get the following error:
error: [Dagger/MissingBinding] com.google.firebase.firestore.CollectionReference cannot be provided without an #Inject constructor or an #Provides-annotated method.
This my repository class:
#Singleton
class BarsRepository {
private CollectionReference barsRef;
#Inject
BarsRepository(#Named("barsRef") CollectionReference barsRef) {
this.barsRef = barsRef;
}
//Method where I use barsRef
}
And this is my AppComponent class:
#Singleton
#Component(modules = {AndroidSupportInjectionModule.class, AppModule.class, ActivityBuildersModule.class, BarsViewModelModule.class})
public interface AppComponent extends AndroidInjector<BaseApplication> {
#Component.Builder
interface Builder{
#BindsInstance
Builder application(Application application);
AppComponent build();
}
}
If I remove the #Named("barsRef"), it works fine.
Here is the solution.
#Singleton
#Provides
#Named("barsRef")
static CollectionReference provideBarsCollRef(FirebaseFirestore db) {
return db.collection("bars");
}
Remove Singleton from above code. It will work.
The reason why it is happening is because of singleton Scope. Singleton is used to ensure that only one object will be shared within that component.

Subcomponent (unscoped) may not reference scoped bindings: #Singleton #Provides #org.jetbrains.annotations.NotNull

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.

Dagger 2 For Android "cannot be provided without an #Provides-annotated method"

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 can Dagger 2 be used to inject using multiple components into the same object

So I have an ApplicationComponent for injecting singletons into my fragments and presenters, but I'm trying to create a component to inject into the same presenter that the AppComponent does. Something along these lines.
#Component{modules = FileManagerModule.class}
public interface FileManagerComponet
{
public void inject(MyPresenter presenter);
}
#Component{modules = AppModule.class}
public interface AppComponent
{
public void inject(MyPresenter presenter);
}
#Module
public class AppModule
{
private Context appContext;
#Provides
#Singleton
public SharedPreferences preferences()
{
return appContext.sharedPreferences();
}
...
}
#Module
public class FileManagerModule
{
private Context activityContext;
#Provides
public FileManager FileManager()
{
return new FileManager(activityContext);
}
...
}
To anyone who can't figure this out, one component must provide all the dependencies to an object. So in my case, I'd have to make the FileManagerComponent be a Subcomponent and ".plus()" it with my AppComponent, or make it dependent on AppComponent and have AppComponent expose Context downstream by having a Context context(); Method that will let components that depend on it have access to a the context it has.
For example:
#Singleton
#Component(modules = {NetworkModule.class, AndroidModule.class})
public interface ApplicationComponent {
FileManagerComponent plus(FileManagerModule module);
}
#Subcomponent(modules = {FileManagerModule.class})
public interface FileManagerComponent {
void injectMyActivity(MyFileManagingActivity activity);
}
And you would use it like this (in MyFileManagingActivity):
FileManagerComponent fmc = applicationComponent.plus(new FileManagerModule());
fmc.injectMyActivity(this);
Or if you don't want to use subcomponents something like this:
#Singleton
#Component(modules = {NetworkModule.class, AndroidModule.class})
public interface ApplicationComponent {
Context context();
File applicationRootDirectory();
}
// Notice this is ALSO a Component
#Component(modules = {FileManagerModule.class}, dependencies = ApplicationComponent.class)
public interface FileManagerComponent {
void inject(MyFileManagerActivity activity);
}
Now you have to build your component that depends on app component.
FileManagerComponent fmc = DaggerFileManagerComponent.builder()
.applicationComponent(appComponent)
.fileManagerModule(new FileManagerModule())
.build();
fmc.inject(this);

Dagger 2 mixed scopes

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.

Categories

Resources