Modules with different Scopes - android

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

Related

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

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?

CustomScope may not reference bindings with different scopes

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.

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

Retrofit cannot be provided without an #Inject constructor or from an #Provides- or #Produces-annotated method

I am learning Dagger 2 now and it's such a pain for me to explain the question without codes , so let me list all my modules, components and etc first :
App.class
public class App extends Application {
private ApiComponent mApiComponent = null;
private AppComponent mAppComponent = null;
public ApiComponent getApiComponent() {
if (mApiComponent == null) {
// Dagger%COMPONENT_NAME%
mApiComponent = DaggerApiComponent.builder()
// list of modules that are part of this component need to be created here too
.appModule(new AppModule(this)) // This also corresponds to the name of your module: %component_name%Module
.apiModule(new ApiModule(this))
.build();
}
return mApiComponent;
}
public AppComponent getAppComponent() {
if (mAppComponent == null) {
// If a Dagger 2 component does not have any constructor arguments for any of its modules,
// then we can use .create() as a shortcut instead:
mAppComponent = DaggerAppComponent.builder()
.appModule(new AppModule(this))
.build();
}
return mAppComponent;
}
}
AppComponent
#Singleton
#Component(modules = AppModule.class)
public interface AppComponent {
void inject(RetrofitDemo target);
}
AppModule
private final Application mContext;
AppModule(Application context) {
mContext = context;
}
#Singleton
#ForApplication
#Provides
Application provideApplication() {
return mContext;
}
#Singleton
#ForApplication
#Provides
Context provideContext() {
return mContext;
}
ApiComponent
#Singleton
#Component(dependencies = {AppModule.class},modules = {ApiModule.class})
public interface ApiComponent {
void inject(RetrofitDemo target);
}
APIModule
#Inject
Context application;
#Inject
public ApiModule(Context context){
this.application = context;
}
#Provides
#Singleton
Gson provideGson() {
return new GsonBuilder()
// All timestamps are returned in ISO 8601 format:
.setDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'")
// Blank fields are included as null instead of being omitted.
.serializeNulls()
.create();
}
#Provides
#Singleton
OkHttpClient provideOkHttpClient() {
...
}
#Provides
#Singleton
public Retrofit provideRetrofit(Gson gson,OkHttpClient okHttpClient){
return new Retrofit.Builder()
.baseUrl(DribbleApi.END_POINT)
.addConverterFactory(GsonConverterFactory.create(gson))
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.client(okHttpClient)
.build();
}
And my activity will be like this:
#Inject
Retrofit mRetrofit;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_retrofit_demo);
((App) getApplication()).getApiComponent().inject(this);
...
Here is the error message:
Error:(18, 10) : retrofit2.Retrofit cannot be provided without an #Inject constructor or from an #Provides- or #Produces-annotated method.
retrofit2.Retrofit is injected at com.sinyuk.yuk.RetrofitDemo.mRetrofit
com.sinyuk.yuk.RetrofitDemo is injected at com.sinyuk.yuk.AppComponent.inject(target)
What makes me confused is the retrofit instance is provided by the ApiModule, but why the error massage said it's injected at appComponent?And I can't find any place wrong in my code. T_T,it's too heavy going to learn Dagger for me...I think.
Besides, in my case I wrote dependencies = AppModule.class module = ApiModule.class in the AppComponent , and it seems to be right I think,but if I wrote module = ({AppComponent.class,ApiComponent.class}),it also works fine.Anybody can explain me why?
Kindly review my code and give me some advice. Thx in advance!
#Sinyuk There's a lot to unpack here, and Dagger is a little complicated at first blush, but I think I can help. First, you have a conceptual misunderstanding regarding the #Component annotation. A Component is an interface which you define, and which Dagger implements through code generation. You will define the interface, and annotate it with #Component and then you will provide a set of Modules to Dagger to use in the generation process. The modules which you use are passed in through the modules element of the #Component annotation. If you want to have one Component permit another Component to support the injection process, then any Component interfaces which you need to have Dagger use while injecting your code, will be passed in through the dependencies element of the #Component annotation.
--
As a result, The following is incorrect
#Component(dependencies = AppModule.class module = ApiModule.class`)
instead, to have the one Component use two Modules write:
#Component(modules = {ApiModule.class, AppModule.class})
or, to have one Component use one Module and depend upon the other Component
#Component(modules = {AppModule.class}, dependencies = {ApiComponent.class})
I hope that that helps you get onto the right path. Let me know if you have any follow up questions.
Okay so your configuration should be this
public class App extends Application {
private AppComponent mAppComponent = null;
public AppComponent getAppComponent() {
if (mAppComponent == null) {
// If a Dagger 2 component does not have any constructor arguments for any of its modules,
// then we can use .create() as a shortcut instead:
mAppComponent = DaggerAppComponent.builder()
.appModule(new AppModule(this))
.build();
}
return mAppComponent;
}
}
And
#Singleton
#Component(modules = {AppModule.class, ApiModule.class})
public interface AppComponent {
void inject(RetrofitDemo target);
}
And
#Module
public class AppModule {
private final Application mContext;
AppModule(Application context) {
mContext = context;
}
#Provides
Application provideApplication() {
return mContext;
}
#Provides
Context provideContext() {
return mContext;
}
}
And
#Module
public class ApiModule {
#Provides
#Singleton
Gson provideGson() {
return new GsonBuilder()
// All timestamps are returned in ISO 8601 format:
.setDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'")
// Blank fields are included as null instead of being omitted.
.serializeNulls()
.create();
}
#Provides
#Singleton
OkHttpClient provideOkHttpClient() {
...
}
#Provides
#Singleton
public Retrofit provideRetrofit(Gson gson, OkHttpClient okHttpClient){
return new Retrofit.Builder()
.baseUrl(DribbleApi.END_POINT)
.addConverterFactory(GsonConverterFactory.create(gson))
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.client(okHttpClient)
.build();
}
}
And
//...Activity
#Inject
Retrofit mRetrofit;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_retrofit_demo);
((App) getApplication()).getAppComponent().inject(this);
...

Categories

Resources