I'm following the new Dagger2 support for android to implement a movies list sample application and below is my use case.
Activity Holds a fragment used to load list of movies
Fragment uses a presenter to hit an api using retrofit
Presenter has a dependency to the API interface class which contains Observale for the movies
I'm using #Inject inside the presenter for the ApiService interface but i got an error that i cannot use #Inject field without declaring provide annotation and below is my code
Main App component
My Movies module
My Movies Contract
My Presenter
and finally the api service interface
So how can i provide the service interface to MoviesModule in order to work properly inside the presenter
The error is
Error:(22, 8) error: [dagger.android.AndroidInjector.inject(T)] sampler.dagger.com.movieslist.data.MoviesApiService cannot be provided without an #Provides-annotated method.
sampler.dagger.com.movieslist.data.MoviesApiService is injected at
sampler.dagger.com.movieslist.movies.MoviePresenter.mApiService
sampler.dagger.com.movieslist.movies.MoviePresenter is injected at
sampler.dagger.com.movieslist.movies.MoviesModule.moviesPresenter(presenter)
sampler.dagger.com.movieslist.movies.MoviesContract.Presenter is injected at
sampler.dagger.com.movieslist.movies.MoviesFragment.mPresenter
dagger.Lazy<sampler.dagger.com.movieslist.movies.MoviesFragment> is injected at
sampler.dagger.com.movieslist.movies.MainActivity.mMoviesFragmentsProvider
sampler.dagger.com.movieslist.movies.MainActivity is injected at
dagger.android.AndroidInjector.inject(arg0)
One solution could be:
#Module
public class APIModule {
#Provides
#Singleton
Retrofit provideRetrofit(Gson gson) {
OkHttpClient client = new OkHttpClient.Builder().build();
return new Retrofit.Builder()
.baseUrl("https://stackoverflow.com/")
.addConverterFactory(GsonConverterFactory.create(gson))
.client(client)
.build();
}
#Provides
#Singleton
Gson provideGson() {
return new GsonBuilder().create();
}
#Provides
#Singleton
MoviesApiService provideMoviesApiService(Retrofit retrofit) {
return retrofit.create(MoviesApiService.class);
}
}
In your MoviePresenter its better to use constructor injection than field injection:
private MoviesApiService mApiService;
#Inject
public MoviePresenter(MoviesApiService apiService) {
mApiService = apiService;
}
MoviApiService is an interface, you cannot inject an interface. You need to create a provides method to provide the retrofit service.
MoviApiService providesMoviApiService(Retrofit retrofit) {
retrofit.create(MoviApiService.class);
}
Related
I have Hilt DI framework in my Android project. Also I have retrofit, and ApiNetworkModule for getting singleton retrofit object:
#Module
#InstallIn(SingletonComponent.class)
public class ApiNetworkModule {
...
#Singleton
#Provides
public Retrofit provideRetrofit(
OkHttpClient okHttpClient,
Gson gson,
SharedPrefManager sharedPrefManager
) {
return new Retrofit.Builder()
.client(okHttpClient)
.baseUrl(sharedPrefManager.getUrl())
.addConverterFactory(GsonConverterFactory.create(gson))
.build();
}
...
}
In general it works fine, but I have noticed in the case when url is updated in sharedPrefManager, retrofit object does not know about it and uses old url. It will be fully updated only after closing-opening application. Is there any way how to reinit Retrofit singleton programmatically? Or how to handle it correctly?
I want to inject a Retrofit object directly into my MyRepository class but I always get a NullPointerException. This is what I have tried.
This is my AppModule class:
#Module
public class AppModule {
#Singleton
#Provides
static Retrofit provideRetrofitInstance(){
return new Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build();
}
}
And this is my view model class:
public class MyViewModel extends AndroidViewModel {
LiveData<Data> myLiveData;
MyViewModel(Application application, City city) {
super(application);
myLiveData = myRepository.addDataToLiveData(city);
}
LiveData<Data> getLiveData() {
return myLiveData;
}
}
And this is my repository class where I want to inject Retofit:
public class MyRepository {
private String myTex;
#Inject
private Retrofit retrofit;
public MyRepository(String myText) {
this.myText = myText;
}
LiveData<Data> addDataToLiveData(City city) {
//Make api call using retrofit
}
}
Edit:
This is how I instantiate my ViewModel in my activity class:
MyRepository repository = new MyRepository("MyText");
Application application = activity.getApplication();
MyViewModelFactory factory = new MyViewModelFactory(application, repository);
MyViewModel viewModel = ViewModelProviders.of(this, factory).get(MyViewModel.class);
Making your Repository injectable is the simplest solution, which also allows you to inject it where it's used, in your ViewModels or Interactors:
#Singleton
public class MyRepository {
private Retrofit retrofit;
#Inject
public MyRepository(Retrofit retrofit) {
this.retrofit = retrofit;
}
LiveData<Data> addDataToLiveData(City city) {
//Make api call using retrofit
}
}
Edit: you can either provide the text via Dagger and inject that in your constructor, like this
#Inject
public MyRepository(String myText, Retrofit retrofit)
Note that you'd need to use #Named or #Qualifier for your string.
Alternatively, you can inject your repository calling inject(this), the syntax depends on how you setup Dagger
somehowGetDaggerComponent().inject(this)
I strongly suggest you go with the 1st solution.
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
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
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.
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) {
// ...
}
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!)