Anton Makov
Anton Makov

Reputation: 845

Dagger 2 injecting two retrofit objects

I'm using Dagger 2 with retrofit2 library while using MVP. Everything went well till I tried to integrate another service (basically I tried to initialize another retrofit object to another service). I followed this answer but without any success.

Every time I'm getting an errors that each of my fragments and application classes don't seem to recognize the component classes.

error: cannot find symbol class DaggerApplicationComponent error: cannot find symbol class DaggerEpisodeComponent

Code

ApplicationComponent

@Singleton
@Component(modules = ApplicationModule.class)
public interface ApplicationComponent {

    Retrofit exposeStreamingRetrofit();

    Retrofit exposeRetrofit();

    Context exposeContext();

    AppPreferenceHelper exposePrefs();

}

Application Module

   @Module
public class ApplicationModule
{
    private String mBaseUrl;
    private Context mContext;
    private AppPreferenceHelper mPrefsHelper;

    public ApplicationModule(Context context,String baseUrl)
    {
        mContext = context;
        mBaseUrl = baseUrl;
        mPrefsHelper = new AppPreferenceHelper(context, Consts.PREF_NAME);
    }


    @Singleton
    @Provides
    GsonConverterFactory provideGsonConverterFactory()
    {
        GsonConverterFactory gsonConverterFactory = GsonConverterFactory.create();
        return gsonConverterFactory;
    }

    @Singleton
    @Provides
    @Named("ok-1")
    OkHttpClient provideOkHttpClient()
    {

        HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
        logging.setLevel(HttpLoggingInterceptor.Level.BODY);

        return new OkHttpClient().newBuilder()
                .connectTimeout(500, TimeUnit.MILLISECONDS)
                .readTimeout(500,TimeUnit.MILLISECONDS)
                .addInterceptor(logging)
                .build();
    }

    @Singleton
    @Provides
    RxJava2CallAdapterFactory provideRxJava2CallAdapterFactory()
    {
        return RxJava2CallAdapterFactory.create();
    }

    @Provides
    @Singleton
    Retrofit provideRetrofit(@Named("ok-1") OkHttpClient client, GsonConverterFactory convectorFactory, RxJava2CallAdapterFactory adapterFactory)
    {
        return new Retrofit.Builder()
                .baseUrl(mBaseUrl)
                .addConverterFactory(convectorFactory)
                .addCallAdapterFactory(adapterFactory)
                .client(client)
                .build();
    }

    @Provides
    @Singleton
    Retrofit provideStreamingRetrofit(@Named("ok-1") OkHttpClient client, GsonConverterFactory convectorFactory, RxJava2CallAdapterFactory adapterFactory) {
        return new Retrofit.Builder()
                .baseUrl(Consts.STREAMING_BASE_PATH)
                .addConverterFactory(convectorFactory)
                .addCallAdapterFactory(adapterFactory)
                .client(client)
                .build();
    }

    @Singleton
    @Provides
    Context provideContext()
    {
        return mContext;
    }

    @Singleton
    @Provides
    AppPreferenceHelper provideAppPreferenceHelper(){
        return mPrefsHelper;
    }

}

StreamingService

public interface StreamingService
{
    @GET("search")
    Observable<StreamingItems> getStreamingItems(@Query("keyword") String query);
}

Streaming Module @Module

public class StreamingModule
{
    private StreamingView mView;

    public StreamingModule(StreamingView view)
    {
        mView = view;
    }

    @PerFragment
    @Provides
    StreamingService provideStreamingService(Retrofit retrofit)
    {
        return retrofit.create(StreamingService.class);
    }

    @PerFragment
    @Provides
    StreamingView provideView()
    {
        return mView;
    }

    public void disposeView()
    {
        mView = null;
    }
}

Streaming component

@PerFragment
@Component(modules = StreamingModule.class, dependencies = ApplicationComponent.class)
public interface StreamingComponent {
    void inject(StreamingFragment streamingFragment);
}

Streaming Presenter

public class StreamingPresenter extends BasePresenter<StreamingView>
{
    private long                    mMaxPagesOfTopSeries;

    @Inject
    protected StreamingService mApiService;

    @Inject
    protected Mapper                mTopSeriesMapper;

    @Inject
    protected AppPreferenceHelper   mPrefsHelper;

    @Inject
    public StreamingPresenter()
    {
        mMaxPagesOfTopSeries = 1;

    }
}

The problem might be connected to the exposing another instance of Retrofit in the component application class, but I'm not sure.

Update 1

EpisodeModule

@PerFragment
@Component (modules = EpisodeModule.class, dependencies = ApplicationComponent.class)
public interface EpisodeComponent
{
    void inject(EpisodeFragment episodeFragment);
}

After I created the streaming classes (service, presenter, module, component) I didn't change anything in other classes so I think the problem is somewhere in Application module/component or streaming classes but I'm not sure because I might change the retrofit object for the other fragments because I added a new instance of retrofit for streaming.

Upvotes: 6

Views: 4487

Answers (2)

Justin Fiedler
Justin Fiedler

Reputation: 6576

You can use the @Named annotation as described by @ilosqu but I could not get over the use of magic strings and the resulting code smell. In a large project with many Retrofit instances developers would need to remember, or worse lookup, the Name values each time the instance is required. This seems tedious, unmanageable at scale and difficult to refactor. I am not judging here, I just want to state my reasoning for another approach.

My solution (Kotlin) was to create unique Classes for each Retrofit instance similar to the idea of marker interfaces in Java. Since Retrofit is a final class and does not provide any interfaces to allow for easy extension was forced to create an abstract wrapper class instead which accomplishes the same result.

/**
 * This abstract class allows for extension and can expose 
 * convenience functions to Retrofit functionality by composition
 */

abstract class RetrofitContainer(private val retrofit: Retrofit) {
    fun <T> create(service: Class<T>): T {
        return retrofit.create(service)
    }

    fun getRetrofit(): Retrofit {
        return retrofit
    }
}

For each unique instance of Retrofit you will need to create a concrete class extending RetrofitContainer.

/**
 * This class can be injected anywhere you want the Streaming Retrofit instance
 */
class StreamingRetrofit(retrofit: Retrofit): RetrofitContainer(retrofit)

Now in your Modules you can create @Provides methods for each "marker" class and inject via Class without the need for @Named

@Provides
@Singleton
fun provideStreamingService(retrofit: StreamingRetrofit): StreamingService{
    return retrofit.create(StreamingService::class.java)
}

@Provides
@Singleton
fun provideStreamingRetrofit(
        client: OkHttpClient,
        adapterFactory: RxJava2CallAdapterFactory,
        converterFactory: GsonConverterFactory
): StreamingRetrofit {
    val retrofit = Retrofit.Builder()
            .baseUrl(Consts.STREAMING_BASE_PATH)
            .addConverterFactory(convectorFactory)
            .addCallAdapterFactory(adapterFactory)
            .client(client)
            .build();

    return StreamingRetrofit(retrofit)
}

This technique allows concise lookup and refactoring without needing to remember any magic. It is probably possible to use the same approach in most cases that you would otherwise need to use @Named instances.

Upvotes: 0

Ilosqu
Ilosqu

Reputation: 336

You are right the problem is the second exposed instance of Retrofit in application module.The solution is to use dagger qualifiers. Just replace appropriate code blocks with:

Define retrofit provider with qualifier @Named("streaming") in Application Module

@Provides
@Singleton
@Named("streaming")
Retrofit provideStreamingRetrofit(@Named("ok-1") OkHttpClient client, GsonConverterFactory convectorFactory, RxJava2CallAdapterFactory adapterFactory) {
    return new Retrofit.Builder()
            .baseUrl(Consts.STREAMING_BASE_PATH)
            .addConverterFactory(convectorFactory)
            .addCallAdapterFactory(adapterFactory)
            .client(client)
            .build();
}

Don't foreget to expose retrofit instance with exact same qualifier in Application Component

@Singleton
@Component(modules = ApplicationModule.class)
public interface ApplicationComponent {

    @Named("streaming") Retrofit exposeStreamingRetrofit();

    Retrofit exposeRetrofit();

    Context exposeContext();

    AppPreferenceHelper exposePrefs();

}

Whenever you need the streaming service retrofit instance - don't forget to set qualifier. Example in Streaming Module

@PerFragment
@Provides
StreamingService provideStreamingService(@Named("streaming") Retrofit retrofit) {
    return retrofit.create(StreamingService.class);
}

Upvotes: 16

Related Questions