Dagger 2 error Subcomponent may not reference scoped bindings - android

I am using Dagger2 in my app to provide dependencies. I get this following error when I build my app.
e: /Users/sriramr/Desktop/android/Movie/MovieInfo/app/build/generated/source/kapt/debug/in/sriram/movieinfo/di/ActivityBuilder_BindMoviesListActivity.java:22: error: in.sriram.movieinfo.di.ActivityBuilder_BindMoviesListActivity.MoviesListActivitySubcomponent (unscoped) may not reference scoped bindings:
#Subcomponent(modules = MoviesListActivityModule.class)
^
#Provides #Singleton in.sriram.movieinfo.network.TmdbService in.sriram.movieinfo.di.MoviesListActivityModule.getTmdbService(retrofit2.Retrofit)
#Provides #Singleton retrofit2.Retrofit in.sriram.movieinfo.di.NetworkModule.getRetrofit(okhttp3.OkHttpClient, retrofit2.converter.gson.GsonConverterFactory, retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory)
#Provides #Singleton okhttp3.OkHttpClient in.sriram.movieinfo.di.NetworkModule.getOkHttpClient(okhttp3.logging.HttpLoggingInterceptor, okhttp3.Cache)
#Provides #Singleton okhttp3.logging.HttpLoggingInterceptor in.sriram.movieinfo.di.NetworkModule.getHttpLoggingInterceptor()
#Provides #Singleton okhttp3.Cache in.sriram.movieinfo.di.NetworkModule.getCacheFile(#Named("application-context") android.content.Context)
#Provides #Singleton retrofit2.converter.gson.GsonConverterFactory in.sriram.movieinfo.di.NetworkModule.getGsonConverterFactory()
#Provides #Singleton retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory in.sriram.movieinfo.di.NetworkModule.getRxJavaFactory()
#Provides #Singleton in.sriram.movieinfo.cache.AppDatabase in.sriram.movieinfo.di.ContextModule.getAppDatabase(#Named("application-context") android.content.Context)
#Provides #Singleton com.squareup.picasso.Picasso in.sriram.movieinfo.di.MoviesListActivityModule.getPicasso(#Named("application-context") android.content.Context)
This is my ContextModule
#Module
public class ContextModule {
#Provides
#Named("application-context")
Context getContext(Application app) {
return app;
}
#Provides
#Singleton
AppDatabase getAppDatabase(#Named("application-context") Context context) {
return Room.databaseBuilder(context,
AppDatabase.class, "database-name").build();
}
}
And this is my NetworkModule
#Module(includes = ContextModule.class)
public class NetworkModule {
#Provides
#Singleton
Cache getCacheFile(#Named("application-context") Context context) {
File cacheFile = new File(context.getCacheDir(), "moviedb-cache");
return new Cache(cacheFile, 10 * 1000 * 1000);
}
#Provides
#Singleton
OkHttp3Downloader getOkHttp3Downloader(OkHttpClient okHttpClient) {
return new OkHttp3Downloader(okHttpClient);
}
#Provides
#Singleton
OkHttpClient getOkHttpClient(HttpLoggingInterceptor loggingInterceptor, Cache cache) {
return new OkHttpClient.Builder()
.addInterceptor(loggingInterceptor)
.cache(cache)
.build();
}
#Provides
#Singleton
Retrofit getRetrofit(OkHttpClient client, GsonConverterFactory gsonConverterFactory, RxJava2CallAdapterFactory callAdapter) {
return new Retrofit.Builder()
.addConverterFactory(gsonConverterFactory)
.addCallAdapterFactory(callAdapter)
.baseUrl("https://api.themoviedb.org/3/")
.client(client)
.build();
}
#Provides
#Singleton
HttpLoggingInterceptor getHttpLoggingInterceptor() {
HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor(message -> Timber.tag("OkHttp").d(message));
loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
return loggingInterceptor;
}
#Provides
#Singleton
GsonConverterFactory getGsonConverterFactory() {
return GsonConverterFactory.create();
}
#Provides
#Singleton
RxJava2CallAdapterFactory getRxJavaFactory() {
return RxJava2CallAdapterFactory.create();
}
}
And finally the MovieListActivityModule
#Module(includes = NetworkModule.class)
public class MoviesListActivityModule {
#Provides
#Singleton
TmdbService getTmdbService(Retrofit retrofit) {
return retrofit.create(TmdbService.class);
}
#Provides
#Singleton
Picasso getPicasso(#Named("application-context") Context context) {
return new Picasso.Builder(context)
.loggingEnabled(true)
.build();
}
}
And this is the ActivityBuilder class
#Module
public abstract class ActivityBuilder {
#ContributesAndroidInjector(modules = MoviesListActivityModule.class)
abstract MoviesListActivity bindMoviesListActivity();
}
And finally the AppComponent
#Singleton
#Component(modules = {AndroidInjectionModule.class, ContextModule.class, ActivityBuilder.class})
public interface AppComponent {
#Component.Builder
interface Builder {
#BindsInstance
Builder application(Application application);
AppComponent build();
}
void inject(MovieInfoApp app);
}
I am new to Dagger 2 and I followed this from a random tutorial of Medium.
I don't see any SubComponent here. I understand that the subcomponent is generated.
This error just occurs for the #Singleton scoped dependencies.
I followed some stack overflow links and added #Singleton to the AppComponent interface.
How do I fix this?

Rename MoviesListActivityModule to MoviesListApplicationModule, take it off of your #ContributesAndroidInjector, and put it onto your AppComponent's modules list instead.
Behind the scenes, #ContributesAndroidInjector generates a #Subcomponent specific to your MoviesListActivity. When you try to inject your MoviesListActivity using AndroidInjection.inject(Activity), Dagger creates a subcomponent instance that holds MoviesListActivity's MembersInjector (the generated code that knows how to populate all the #Inject fields in your MoviesListActivity) and any scoped bindings that your Activity (or its dependencies) may need. That's the component you're missing, and it's named after the Module and #ContributesAndroidInjector method name, ActivityBuilder_BindMoviesListActivity.MoviesListActivitySubcomponent.
You can add scopes (like an #ActivityScope you create) to #ContributesAndroidInjector, and dagger.android will add them onto the generated subcomponent, just like setting modules = {/*...*/} will add modules onto it. However, that isn't your problem.
First of all, you were right to add #Singleton to your AppComponent; that's a very common thing, and it's right to do here. Adding #Singleton there means that Dagger will automatically watch out for bindings that are marked with #Singleton and keep copies of them in the component you annotate the same way. If you were to create an #ActivityScope annotation and add it to your subcomponent, Dagger would watch out for bindings that were annotated with #ActivityScope and return the same instance from within the same Activity instance but a different instance compared to other instances of the same Activity or other Activity types.
Your problem is that you are adding #Singleton-scoped bindings Picasso and TmdbService into your unscoped subcomponent graph. What you're telling Dagger is that across the lifetime of your application component (not your Activity, your application) you should always return the same Picasso and TmdbService instances. However, by making that binding on the module list of your subcomponent rather than your top-level #Singleton #Component, you tell Dagger about these application-level objects when it's trying to configure activity-level bindings. The same applies to the bindings in your NetworkModule, which are also listed in your error message.
After you move the modules, you will always receive the same instance of Picasso, TmdbService, OkHttpClient, and all of those others—which should allow those objects to help manage caches, batches, and requests in flight without you having to worry about which instance you're interacting with. It'll always be the same instance across the lifetime of your app.

Related

Dagger-2: Field not injected

My field for retrofit in this class is never injected into, it is still null when i run my code.
Here is my ServiceClass where I inject retrofit, have my api calls etc. I stripped it down for simplicity:
public class ServiceClass{
#Inject
Retrofit retrofit;
public ServiceClass(){
}
}
My module class for all network related dependencies:
#Module
public class NetworkModule {
#Provides
#ApplicationScope
Retrofit getRetrofit(OkHttpClient okHttpClient, Gson gson){
return new Retrofit.Builder()
.baseUrl(URL.BASE_URL)
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create(gson))
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build();
}
#Provides
#ApplicationScope
OkHttpClient getOkHttpClient(Gson gson, HttpLoggingInterceptor httpLoggingInterceptor){
OkHttpClient okHttpClient = new OkHttpClient();
okHttpClient.newBuilder().addInterceptor(httpLoggingInterceptor);
return okHttpClient;
}
#Provides
#ApplicationScope
HttpLoggingInterceptor getHttpLoggingInterceptor(){
return new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BASIC);
}
#Provides
#ApplicationScope
Gson getGson(){
return new Gson();
}
}
My AppComponent this is my only component class:
#ApplicationScope
#Component(modules = {NetworkModule.class})
public interface AppComponent {
#Component.Builder
interface Builder {
#BindsInstance
Builder application(MyApplication myApplication);
AppComponent build();
}
void inject(MyApplication myApplication);
Retrofit getRetrofit();
}
My Application class:
public class MyApplication extends Application{
private AppComponent appComponent;
#Override
public void onCreate() {
super.onCreate();
DaggerAppComponent
.builder()
.application(this)
.build()
.inject(this);
}
public AppComponent getAppComponent(){
return appComponent;
}
}
I tried to fiddle around the code, I don't seem to manage to get it working properly. What am I missing here?
Update (previous information still valid) :
I have noticed you incorrectly build your component: you must add .networkModule(new NetworkModule()) after DaggerAppComponent.builder()
Make sure your private AppComponent appComponent is initialized too!
For field injection (I believe that's what you're after), you can write your constructor like this:
public ServiceClass(){
MyApplication.getInstance().getAppComponent().inject(this)
}
Naturally, you should expose your appComponent entity somehow - the above is my guess (to expose appComponent entity via application entity).
PS.: better approach (and more readable too) is to avoid field injection at all and parametrize constructor (however it's not always possible, like for example if you inject into activity).
PSS.: your AppComponent should also have void inject(ServiceClass value);
There are multiple ways of injecting retrofit in ServiceClass
You have to make a separate Component for ServiceClass like :-
#Component(dependencies = AppComponent.class)
interface ServiceClassComponent {
void injectServiceClass(ServiceClass serviceClass);
}
Or
you can just inject ServiceClass into your application component:-
void injectServiceClass(ServiceClass serviceClass);
into your AppComponent
The dependencies keyword would include all the dependent components into your particular component that you would build.
Then in the constructor of ServiceClass you need to build the Component and inject it

Why we need to provide Repository singleton as injected params are already singleton?

I am new on project I have this line of code in AppModule
#Singleton
#Provides
fun articleRepository(apiSeResource: ApiResourceArticles, preferences:SharedPreferences): ArticlesRepository {
return ArticlesRepository(apiResource, preferences)
}
#Singleton
#Provides
fun apiResourceArticle(retrofit: Retrofit): ApiResourceArticles{
return retrofit.create(ApiResourceArticles::class.java)
}
I am wondering why we need to have ArticlesRepository in AppModule.
I see we are injecting the constructor, but why do we need to provide the instance of ArticleRepository as Singleton? What is the need for it?

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.

Dynamic settings in retrofit2 when it created with Dagger2

I create dagger2 module for retrofit2
#Module
public class NetworkModule {
private Context context;
public NetworkModule(Application app) {
this.context = app;
}
#Singleton
#Provides
Context providesContext() {
return context;
}
#Singleton
#Provides
OkHttpClient providesOkHttpClient(Utils utils) {
User user = utils.getSettings();
return new OkHttpClient.Builder()
.connectTimeout(Long.valueOf(user.getTimeOut()), TimeUnit.SECONDS)
.writeTimeout(Long.valueOf(user.getTimeOut()), TimeUnit.SECONDS)
.readTimeout(Long.valueOf(user.getTimeOut()), TimeUnit.SECONDS)
.build();
}
#Singleton
#Provides
Retrofit providesRetrofit(OkHttpClient okHttpClient, Utils utils) {
User user = utils.getSettings();
String host = user.getHost();
if (!host.endsWith("/")) host += "/";
return new Retrofit.Builder()
.baseUrl(host)
.addConverterFactory(JacksonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.client(okHttpClient).build();
}
#Singleton
#Provides
RestApiFactory providesRestApiFactory(Retrofit retrofit) {
return new RestApiFactory(retrofit);
}
}
I have settings activity where user can change baseUrl, timeOut. If I create
providesRetrofit
providesRestApiFactory
providesOkHttpClient
like #Singleton - after change settings it not change. If I remove #Singleton annotation - all work. My questions - how can I update dagger #Singleton when user change data?
Place NetworkModule in separate component(likely Subcomponent of your main Component) and recreate it when user changes baseUrl, timeout etc.
You can do that by creating interface annotated with #Subcomponent and adding your network module there. You can instantiate this Subcomponent by invoking method from your main component that you also need to add.
Detailed tutorial
https://google.github.io/dagger/subcomponents.html

Dagger ApplicationComponent must be set

I need to expose my OkHttpClient from ApplicationModule so I added to ApplicationComponent. Something like this :
#Module
public class ApplicationModule {
#Provides #Singleton
public OkHttpClient provideOkHttpClient() {
final OkHttpClient.Builder client = new OkHttpClient.Builder();
return client.build();
}
#Singleton
#Component( modules = {ApplicationModule.class} )
public interface ApplicationComponent {
OkHttpClient okHttpClient();
}
so I added the OkHttpClient okHttpClient(); in the ApplicationComponent as you can see right above.
Now in my NetworkModule I use it like :
#Module
public class NetworkModule {
#Provides #ActivityScope
public ProjectService provideProjectService(OkHttpClient client) {
return new ProjectService(client);
}
#Component( dependencies = {ApplicationComponent.class}, modules = {NetworkModule.class} )
#ActivityScope
public interface NetworkComponent {
void inject(#NonNull MyActivity myActivity);
}
but now when I get a runtime error :
Caused by: java.lang.IllegalStateException: css.test.demo.ApplicationComponent must be set
at css.test.demo.main.projects.network.DaggerNetworkComponent$Builder.build(DaggerNetworkComponent.java:102)
at css.test.demo.main.projects.MyActivity.onCreate(MyActivity.java:159)
at android.app.Activity.performCreate(Activity.java:6237)
and here is how I build it in MyActivity :
NetworkComponent = DaggerNetworkComponent.builder()
.NetworkModule(new NetworkModule(this))
.build();
NetworkComponent.inject(this);
I like to emphasize that dagger contains no magic—its is just plain java. If you don't give it the information it needs, the compiler will complain.
If you have a look at your DaggerNetworkComponent.Builder you will notice that it has a method called appComponent(AppComponent component). This is where dagger expects you to add the appcomponent that your NetworkComponent depends on.
NetworkComponent = DaggerNetworkComponent.builder()
.NetworkModule(new NetworkModule(this))
.appComponent(((App)getApplication()).getAppComponent()) // add your appComponent
.build();
And it should work.

Categories

Resources