Reputation: 845
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
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
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