Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dagger2, providing Retrofit instances with different URLs

Currently I'm using Dagger 2 to inject an instance of Retrofit to use for an api call in a widget. From my understanding, Dagger searches for things to inject using the type, so declaring 2 seperate @Provides Retrofit providesRetrofit() with different names wouldn't work.

Heres my current code:

Module:

@Module
public class ApiModule {

    @Provides
    @Singleton
    GsonConverterFactory provideGson() {
        return GsonConverterFactory.create();
    }

    @Provides
    @Singleton
    RxJavaCallAdapterFactory provideRxCallAdapter() {
        return RxJavaCallAdapterFactory.create();
    }

    @Singleton
    @Provides
    Retrofit providePictureRetrofit(GsonConverterFactory gsonConverterFactory, RxJavaCallAdapterFactory rxJavaCallAdapterFactory) {
        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(MarsWeatherWidget.PICTURE_URL)
                .addConverterFactory(gsonConverterFactory)
                .addCallAdapterFactory(rxJavaCallAdapterFactory)
                .build();
        return retrofit;
    }

....
//Here is the other Retrofit instance where I was wanting to use a different URL.

//    @Singleton
//    @Provides
//    Retrofit provideWeatherRetrofit(GsonConverterFactory gsonConverterFactory, RxJavaCallAdapterFactory rxJavaCallAdapterFactory) {
//        Retrofit retrofit = new Retrofit.Builder()
//                .baseUrl(MarsWeatherWidget.WEATHER_URL)
//                .addConverterFactory(gsonConverterFactory)
//                .addCallAdapterFactory(rxJavaCallAdapterFactory)
//                .build();
//        return retrofit;
//    }
}

Component:

@Singleton
@Component(modules = ApiModule.class)
public interface ApiComponent {

    void inject (MarsWeatherWidget marsWeatherWidget);

}

class extending Application:

public class MyWidget extends Application {

    ApiComponent mApiComponent;

    @Override
    public void onCreate() {
        super.onCreate();

        mApiComponent = DaggerApiComponent.builder().apiModule(new ApiModule()).build();
    }

    public ApiComponent getApiComponent() {
        return mApiComponent;
    }
}

and finally where im actually injecting it:

@Inject Retrofit pictureRetrofit;

    @Override
    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
        // There may be multiple widgets active, so update all of them
        mAppWidgetIds = appWidgetIds;
        ((MyWidget) context.getApplicationContext()).getApiComponent().inject(this);
        final int N = appWidgetIds.length;
        for (int i = 0; i < N; i++) {
            updateAppWidget(context, appWidgetManager, appWidgetIds[i]);
        }
    }

......
//use the injected Retrofit instance to make a call

So how can I organize this to give me a seperate Retrofit instance that is built with different URLs for hitting different APIs? Let me know if more info is needed.

like image 552
Orbit Avatar asked Mar 11 '16 20:03

Orbit


2 Answers

Provide different versions of the same type

You can use @Named (or custom annotations that are annotated with @Qualifier) to distinguish between variants of the same type.

Add the annotations like the following:

@Singleton
@Provides
@Named("picture")
Retrofit providePictureRetrofit(GsonConverterFactory gsonConverterFactory, RxJavaCallAdapterFactory rxJavaCallAdapterFactory) {
    return retrofit = new Retrofit.Builder()
            .baseUrl(MarsWeatherWidget.PICTURE_URL) // one url
            .build();
}


@Singleton
@Provides
@Named("weather")
Retrofit provideWeatherRetrofit(GsonConverterFactory gsonConverterFactory, RxJavaCallAdapterFactory rxJavaCallAdapterFactory) {
    return retrofit = new Retrofit.Builder()
            .baseUrl(MarsWeatherWidget.WEATHER_URL) // other url
            .build();
}

Custom @Qualifier

You could also just create a custom annotation like the following:

@Qualifier
@Retention(RUNTIME)
public @interface Picture {}

You would just use this instead of @Named(String).

Injecting the qualified version

When you have your module providing the qualified types, you just need to also add the qualifier where you need the dependency.

MyPictureService provideService(@Named("picture") Retrofit retrofit) {
    // ...
}
like image 114
David Medenjak Avatar answered Nov 11 '22 06:11

David Medenjak


You should use a qualifier annotation to distinguish between different objects that have the same type—like between the Retrofit for pictures and the Retrofit for weather.

You apply the same qualifier to the @Provides method and to the @Inject parameter (constructor or method parameter, or field).

@Named is one qualifier annotation, but using it means you have to remember to use the exact same string at the provision point and at all injection points. (It's easy to mistype @Named("whether") somewhere.)

But it's easy to define your own qualifier annotation. Just define a custom annotation type, and annotate that with @Qualifier:

@Documented
@Qualifier
public @interface Picture {}

@Documented
@Qualifier
public @interface Weather {}

Then you can bind each Retrofit differently:

@Provides @Picture Retrofit providePictureRetrofit(…) {…}
@Provides @Weather Retrofit provideWeatherRetrofit(…) {…}

and inject each where you need it:

@Inject @Picture Retrofit pictureRetrofit;
@Inject @Weather Retrofit weatherRetrofit;
// (But constructor injection is better than field injection!)
like image 42
netdpb Avatar answered Nov 11 '22 07:11

netdpb