Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dagger 2 error Subcomponent may not reference scoped bindings

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?

like image 501
Sriram R Avatar asked Apr 11 '18 19:04

Sriram R


1 Answers

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.

like image 199
Jeff Bowman Avatar answered Oct 21 '22 01:10

Jeff Bowman