How to use Glide with dagger2 - dependency-injection

I am developing a music application. I want to load artist's image from LastFM so i do this this way
1. I created a class ArtistImageLoader extends BaseGlideUrlLoader.
2. In the getUrl method i used retrofit2 to get the artist's image url from LastFM via getArtistInfo method.
My problem is i didn't know how to inject the service of retrofit to make the request in ArtistImageLoader. I did this way but i got a NOP exception. lastFmService wasn't be not injected.
// GlideModule
glide.register(MLocalArtist.class, InputStream.class, new ArtistImageLoader.Factory());
// Use it in onCreate method of ArtistsFragment
DaggerLastFmComponent.builder().activityModule(new ActivityModule(getActivity()))
.netComponent(getNetComponent())
.build().inject(this);
// use this code in onBindViewHolder method of artists recycler adapter
Glide.with(getContext())
.from(MLocalArtist.class)
.load(localArtist)
.into(localArtistViewHolder.ivArtwork);
ArtistImageLoader
public class ArtistImageLoader extends BaseGlideUrlLoader<MLocalArtist> {
#Inject
LastfmService lastfmService;
public ArtistImageLoader(Context context) {
super(context);
}
#Override
protected String getUrl(MLocalArtist model, int width, int height) {
Call<List<MArtist>> call = lastfmService.getArtistInfo(model.artistName);
try {
List<MArtist> artists = call.execute().body();
if (artists != null && artists.size() > 0) {
Timber.e(artists.get(0).toString());
}
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
public static class Factory implements ModelLoaderFactory<MLocalArtist, InputStream> {
#Override public ModelLoader<MLocalArtist, InputStream> build(Context context, GenericLoaderFactory factories) {
return new ArtistImageLoader(context);
}
#Override public void teardown() {
}
}
}
Can you help me to do it? Thank you so much!
Glide Version: 3.7.0
Integration libraries: OkHttp3 + Dagger2
Device/Android Version: Android Emulator + Asus zenfone 5
EDIT 1
ActivityComponent.java
#PerActivity
#Component(dependencies = AppComponent.class, modules = ActivityModule.class)
public interface ActivityComponent {
Context context();
}
AppComponent.java
#Singleton
#Component(modules = AppModule.class)
public interface AppComponent {
App app();
}
NetComponent.java
#Singleton
#Component(modules = {NetModule.class, AppModule.class})
public interface NetComponent {
#Named("chartSoundCloud")
Retrofit getSoundcloudChartRetrofit();
#Named("searchSoundCloud")
Retrofit getSoundcloudSearchRetrofit();
#Named("lastFM")
Retrofit getLastFmRetrofit();
}
LastFmComponent.java
#PerActivity
#Component(dependencies = NetComponent.class, modules = {LastFmModule.class, ActivityModule.class})
public interface LastFmComponent extends ActivityComponent {
void inject(ArtistsFragment artistsFragment);
}
ActivityModule.java
#Module
public class ActivityModule {
private final Context mContext;
public ActivityModule(Context mContext) {
this.mContext = mContext;
}
#Provides
#PerActivity
Context provideActivityContext() {
return mContext;
}
}
AppModule.java
#Module
public class AppModule {
private App app;
public AppModule(App app){
this.app = app;
}
#Singleton
#Provides
App provideApplication() {
return app;
}
#Singleton
#Provides #Named("applicationContext")
Context provideApplicationContext(){
return app;
}
}
LastFmModule.java
#Module
public class LastFmModule {
#Provides
#PerActivity
LastfmService provideLastFmService(#Named("lastFM") Retrofit retrofit) {
return retrofit.create(LastfmService.class);
}
}
NetModule.java
#Module
public class NetModule {
static final int DISK_CACHE_SIZE = (int) MEGABYTES.toBytes(50);
#Provides
#Singleton
Cache provideOkHttpCache(#Named("applicationContext") Context application) {
Cache cache = new Cache(application.getCacheDir(), DISK_CACHE_SIZE);
return cache;
}
#Provides
#Singleton
ScdClientIdInterceptor provideScdClientIdInterceptor() {
return new ScdClientIdInterceptor();
}
#Provides
#Singleton
LastFMInterceptor provideLastFmInterceptor() {
return new LastFMInterceptor();
}
#Provides
#Singleton
HttpLoggingInterceptor provideHttpLoggingInterceptor() {
return new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY);
}
#Provides
#Singleton
#Named("soundcloud-Http")
OkHttpClient provideOkHttpSoundCloudClient(Cache cache, ScdClientIdInterceptor clientIdInterceptor, HttpLoggingInterceptor httpLoggingInterceptor) {
return createOkHttpClient(cache, clientIdInterceptor, httpLoggingInterceptor);
}
#Provides
#Singleton
#Named("lastFM-Http")
OkHttpClient provideOkHttpLastFmClient(Cache cache, LastFMInterceptor clientIdInterceptor, HttpLoggingInterceptor httpLoggingInterceptor) {
return createOkHttpClient(cache, clientIdInterceptor, httpLoggingInterceptor);
}
private OkHttpClient createOkHttpClient(Cache cache, Interceptor clientIdInterceptor, HttpLoggingInterceptor httpLoggingInterceptor) {
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.cache(cache)
.addInterceptor(clientIdInterceptor)
.addInterceptor(httpLoggingInterceptor)
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.writeTimeout(30, TimeUnit.SECONDS)
.build();
return okHttpClient;
}
#Provides
#Singleton
Gson provideGson() {
return GsonFactory.create();
}
#Provides
#Singleton
#Named("searchSoundCloud")
Retrofit provideSearchSoundCloudRetrofit(Gson gson, #Named("soundcloud-Http") OkHttpClient okHttpClient) {
Retrofit searchRetrofit = new Retrofit.Builder()
.baseUrl(Constants.BASE_SOUNDCLOUD_API_URL)
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create(gson))
.build();
return searchRetrofit;
}
#Provides
#Singleton
#Named("chartSoundCloud")
Retrofit provideChartSoundCloudRetrofit(Gson gson, #Named("soundcloud-Http") OkHttpClient okHttpClient) {
Retrofit chartRetrofit = new Retrofit.Builder()
.baseUrl(Constants.BASE_SOUNDCLOUD_API_V2_URL)
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create(gson))
.build();
return chartRetrofit;
}
#Provides
#Singleton
#Named("lastFM")
Retrofit provideLastFmRetrofit(Gson gson, #Named("lastFM-Http") OkHttpClient okHttpClient) {
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(Constants.LASTFM_API_URL)
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create(gson))
.build();
return retrofit;
}
}

My assumption is that your ArtistImageLoader is defined in an separate class. The reason for the your problem is the way dagger works. It only injects fields annotated with #Inject on the class you specified as parameter of the inject method. Therefore nothing inside your ArtistImageLoader annotated with #Inject will be injected, but only the annotated fields, which are defined inside your ArtistsFragment.
I would recommend to define an LastfmService field with the #Inject annotation in your fragment and pass the instance to your Glide LoaderFactory. The factory can provide it to the instances of the loader. It's not the nicest solution, but since you can't directly pass it to the instances this seems to viable workaround.
Another approach would be to build your dependency tree inside your custom Application. This allows you access the dependencies from anywhere without being dependent on the activity lifecycle.

Related

How to solve error: [Dagger/MissingBinding] while working With Retrofit with Dagger2

For learning, I want to implement Dagger2 for Dependency Injection in a simple project. I read through the google Codelab code sample to have a basic idea of Dagger2. Then I read through some medium blog and sample Github repo which has implemented Dagger2 for dependency injection in the project. Then I started a demo project and try to implement Dagger2 with Retrofit. After Implementation I got an unexpected build failed error having "error: [Dagger/MissingBinding] com.aomi.mybase.data.remote.testimonial.TestimonialRestService cannot be provided without an #Provides-annotated method." TestimonialService is the Api related service. For that reason, I can't annotate with #Provide or #Binds annotation. I really don't know what to do to solve this issue
The error log is screenshot is given bellow
https://imgur.com/a/0qQXLbN
Let me share some of my code so that you can have a look where the problem actually have
Qualifier.kt
#Qualifier
#MustBeDocumented
#Retention(RUNTIME)
annotation class Type(val type: String = "")
InterceptorModule.kt
#Module
class InterceptorModule {
#Module
companion object {
#JvmStatic
#Singleton
#Provides
fun provideLoggingInterceptor(): HttpLoggingInterceptor {
return HttpLoggingInterceptor().apply {
level = if (BuildConfig.DEBUG) BODY else NONE
}
}
#JvmStatic
#Singleton
#Type("Basic")
#Provides
fun provideBasicInterceptor(): Interceptor {
val basicAuthCredential = Credentials.basic(
"username",
"password"
)
try {
return Interceptor {
val request = it.request()
it.proceed(
request.newBuilder()
.header("Accept", "application/json")
.header("Content-Type", "application/json")
.header("Authorization", basicAuthCredential)
.build()
)
}
} catch (exception: Exception) {
throw Exception(exception.message)
}
}
#JvmStatic
#Singleton
#Type("Bearer")
#Provides
fun provideAuthInterceptor(appContext: Context): Interceptor {
val accessToken = AppPreferenceImpl(appContext).accessToken
try {
return Interceptor {
val request = it.request()
it.proceed(
request.newBuilder()
.header("Accept", "application/json")
.header("Content-Type", "application/json")
.header("Authorization", "Bearer $accessToken")
.build()
)
}
} catch (exception: Exception) {
throw Exception(exception.message)
}
}
}
}
NetworkModule.kt
#Module(
includes = [
InterceptorModule::class
]
)
abstract class NetworkModule {
#Module
companion object {
private const val BASE_URL = BuildConfig.BASE_URL
private const val TIME_OUT = 60L
#JvmStatic
#Singleton
#Type("Basic")
#Provides
fun provideBasicRetrofit(okHttpClient: OkHttpClient): Retrofit {
return Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.client(okHttpClient)
.callbackExecutor { Logger.d("returning") }
.build()
}
#JvmStatic
#Singleton
#Type("Basic")
#Provides
fun provideBasicOkHttpClient(loggingInterceptor: HttpLoggingInterceptor, basicInterceptor: Interceptor): OkHttpClient {
return OkHttpClient.Builder()
.connectTimeout(TIME_OUT, SECONDS)
.readTimeout(TIME_OUT, SECONDS)
.writeTimeout(TIME_OUT, SECONDS)
.addInterceptor(loggingInterceptor)
.addInterceptor(basicInterceptor)
.build()
}
#JvmStatic
#Singleton
#Type("Bearer")
#Provides
fun provideBearerRetrofit(okHttpClient: OkHttpClient): Retrofit {
return Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.client(okHttpClient)
.callbackExecutor { Logger.d("returning") }
.build()
}
#JvmStatic
#Singleton
#Type("Bearer")
#Provides
fun provideBearerOkHttpClient(loggingInterceptor: HttpLoggingInterceptor, authInterceptor: Interceptor): OkHttpClient {
return OkHttpClient.Builder()
.connectTimeout(TIME_OUT, SECONDS)
.readTimeout(TIME_OUT, SECONDS)
.writeTimeout(TIME_OUT, SECONDS)
.addInterceptor(loggingInterceptor)
.addInterceptor(authInterceptor)
// .authenticator(ServiceAuthenticator())
.build()
}
}
}
ServiceModule.kt
#Module
abstract class ServiceModule {
#Module
companion object {
#JvmStatic
#Singleton
#Type("Basic")
#Provides
fun provideTestimonialService(retrofit: Retrofit): TestimonialRestService {
return retrofit.create(TestimonialRestService::class.java)
}
}
}
RepositoryModule.kt
#Module
abstract class RepositoryModule {
#Binds
abstract fun provideTestimonialRepository(repo: TestimonialRepositoryImpl): TestimonialRepository
}
TestimonialRepository.kt
interface TestimonialRepository {
fun getTestimonials(): Flowable<ArrayList<Testimonial>>
}
TestimonialRepositoryImpl.kt
class TestimonialRepositoryImpl #Inject constructor(
private val testimonialDataSource: TestimonialDataSource
) : TestimonialRepository {
override fun getTestimonials(): Flowable<ArrayList<Testimonial>> {
return testimonialDataSource.getTestimonialResponse().map { it.testimonialList }
}
}
TestimonialDataSource.kt
class TestimonialDataSource #Inject constructor(
private val testimonialRestService: TestimonialRestService
) {
fun getTestimonialResponse(): Flowable<TestimonialResponse> {
return testimonialRestService.getTestimonialResponse().onResponse()
}
}
TestimonialRestService.kt
interface TestimonialRestService {
#GET("static/testimonials")
fun getTestimonialResponse(): Flowable<Response<TestimonialResponse>>
}
WelcomeViewModel.kt
class WelcomeViewModel #Inject constructor(
private val repository: TestimonialRepository
) : BaseViewModel() {
var testimonials = MutableLiveData<ArrayList<Testimonial>>()
fun getTestimonials() {
if(testimonials.value == null) {
compositeDisposable += repository.getTestimonials()
.performOnBackgroundOutputOnMain()
.doOnSubscribe { loader.value = true }
.doAfterTerminate { loader.value = false }
.subscribe({
Logger.d(it)
testimonials.value = it
}, {
handleException(it)
})
}
}
}
When using qualifiers, you need to place the qualifier annotation in two places:
Where the object is bound: a #Binds or #Provides method, a #BindsInstance method in a builder, or a #BindsInstance parameter in a factory.
Where the object is used: a parameter to a #Binds or #Provides method, a parameter to an #Inject constructor or method, or an #Inject field/property.
A dependency can only be provided if the qualifier at the use site matches the qualifier on the #Binds/#Provides method. For this purpose, "no qualifier" is a type of qualifier.
Since you want to use a #Type("Basic")-qualified Retrofit to provide an unqualified TestimonialRestService, this means the parameter should be qualified and the method itself should be unqualified:
#JvmStatic
#Singleton
#Provides
fun provideTestimonialService(#Type("Basic") retrofit: Retrofit): TestimonialRestService {
return retrofit.create(TestimonialRestService::class.java)
}
When you fix this, you'll notice that the provideXxxRetrofit methods also have errors for the same reason: they are both looking for an unqualified OkHttpClient, but the only OkHttpClient bindings in your graph have qualifiers. Those errors can be fixed in the same way:
#JvmStatic
#Singleton
#Type("Basic")
#Provides
fun provideBasicRetrofit(#Type("Basic") okHttpClient: OkHttpClient): Retrofit {
return Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.client(okHttpClient)
.callbackExecutor { Logger.d("returning") }
.build()
}
Although #Nitrodon answer is right, I made it more realistic according to his answer so that anyone falling in this problem can understand it very easily. Here is the code for better understanding
Constant.kt
object Network {
const val BASIC = "Basic"
const val BEARER = "Bearer"
}
Qualifier.kt
#Qualifier
#MustBeDocumented
#Retention(RUNTIME)
annotation class InterceptorType(val type: String = "")
#Qualifier
#MustBeDocumented
#Retention(RUNTIME)
annotation class OkHttpClientType(val type: String = "")
#Qualifier
#MustBeDocumented
#Retention(RUNTIME)
annotation class RetrofitType(val type: String = "")
InterceptorModule.kt
#Module
class InterceptorModule {
companion object {
#Singleton
#Provides
fun provideLoggingInterceptor(): HttpLoggingInterceptor {
return HttpLoggingInterceptor().apply {
level = if (BuildConfig.DEBUG) BODY else NONE
}
}
#Singleton
#InterceptorType(BASIC)
#Provides
fun provideBasicInterceptor(): Interceptor {
val basicAuthCredential = Credentials.basic(
"ct_android",
"\$2y\$12\$ej.DK5rJIZjF9FokTWErDeDylA7N.4apw0FZ2FllcK53KEYZqDryO"
)
try {
return Interceptor {
val request = it.request()
it.proceed(
request.newBuilder()
.header("Accept", "application/json")
.header("Content-Type", "application/json")
.header("Authorization", basicAuthCredential)
.build()
)
}
} catch (exception: Exception) {
throw Exception(exception.message)
}
}
#Singleton
#InterceptorType(BEARER)
#Provides
fun provideAuthInterceptor(appContext: Context): Interceptor {
val accessToken = AppPreferenceImpl(appContext).accessToken
try {
return Interceptor {
val request = it.request()
it.proceed(
request.newBuilder()
.header("Accept", "application/json")
.header("Content-Type", "application/json")
.header("Authorization", "Bearer $accessToken")
.build()
)
}
} catch (exception: Exception) {
throw Exception(exception.message)
}
}
}
}
NetworkModule.kt
#Module(
includes = [
InterceptorModule::class
]
)
abstract class NetworkModule {
companion object {
private const val BASE_URL = BuildConfig.BASE_URL
private const val TIME_OUT = 60L
#Singleton
#RetrofitType(BASIC)
#Provides
fun provideBasicRetrofit(#OkHttpClientType(BASIC) okHttpClient: OkHttpClient): Retrofit {
return Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.client(okHttpClient)
.callbackExecutor { Logger.d("returning") }
.build()
}
#Singleton
#OkHttpClientType(BASIC)
#Provides
fun provideBasicOkHttpClient(loggingInterceptor: HttpLoggingInterceptor, #InterceptorType(BASIC) basicInterceptor: Interceptor): OkHttpClient {
return OkHttpClient.Builder()
.connectTimeout(TIME_OUT, SECONDS)
.readTimeout(TIME_OUT, SECONDS)
.writeTimeout(TIME_OUT, SECONDS)
.addInterceptor(loggingInterceptor)
.addInterceptor(basicInterceptor)
.build()
}
#Singleton
#RetrofitType(BEARER)
#Provides
fun provideBearerRetrofit(#OkHttpClientType(BEARER) okHttpClient: OkHttpClient): Retrofit {
return Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.client(okHttpClient)
.callbackExecutor { Logger.d("returning") }
.build()
}
#Singleton
#OkHttpClientType(BEARER)
#Provides
fun provideBearerOkHttpClient(loggingInterceptor: HttpLoggingInterceptor, #InterceptorType(BEARER) authInterceptor: Interceptor): OkHttpClient {
return OkHttpClient.Builder()
.connectTimeout(TIME_OUT, SECONDS)
.readTimeout(TIME_OUT, SECONDS)
.writeTimeout(TIME_OUT, SECONDS)
.addInterceptor(loggingInterceptor)
.addInterceptor(authInterceptor)
// .authenticator(ServiceAuthenticator())
.build()
}
}
}
ServiceModule.kt
#Module
abstract class ServiceModule {
companion object {
#Singleton
#Provides
fun provideTestimonialService(#RetrofitType(BASIC) retrofit: Retrofit): TestimonialRestService {
return retrofit.create(TestimonialRestService::class.java)
}
}
}

Dagger generated code compilation failed when using #Singleton annotation

I am using Dagger - 2.6 and i have the following classes.
public class Trigger {
public static JSONObject triggerLambda(JSONObject jsonObject) {
DataTransformerComponent daggerDataTransformerComponent = DaggerDataTransformerComponent.create();
return daggerDataTransformerComponent.getHandler().handle(jsonObject);
}
}
Data Handler class:
public class DataHandler {
private static final Logger LOGGER = Logger.getLogger(DataHandler.class.getName());
private A a;
#Inject
public DataHandler(A a) {
this.a = a;
}
public JSONObject handle(JSONObject input) {
LOGGER.info("Json input received - " + input.toString());
return a.executeTransformation(input);
}
}
And a dependency:
public class A {
#Inject
public A() {
}
public JSONObject executeTransformation(JSONObject jsonObject) {
System.out.println("a");
return null;
}
}
My component class looks like:
#Component
public interface DataTransformerComponent {
DataHandler getHandler();
}
When i compile the above code it runs absolutely fine.
Now i want to make my A dependency #Singleton.
So i change my dependency class and component class as follows:
#Singleton
#Component
public interface DataTransformerComponent {
DataHandler getHandler();
}
Dependency class:
#Singleton
public class A {
#Inject
public A() {
}
public JSONObject executeTransformation(JSONObject jsonObject) {
System.out.println("a");
return null;
}
}
But now the generated component shows compilation errors saying:
A_Factory not found and it fails in the initialize() method.
DaggerDataTransformerComponent :
#Generated(
value = "dagger.internal.codegen.ComponentProcessor",
comments = "https://google.github.io/dagger"
)
public final class DaggerDataTransformerComponent implements DataTransformerComponent {
private Provider<A> aProvider;
private Provider<DataHandler> dataHandlerProvider;
private DaggerDataTransformerComponent(Builder builder) {
assert builder != null;
initialize(builder);
}
public static Builder builder() {
return new Builder();
}
public static DataTransformerComponent create() {
return builder().build();
}
#SuppressWarnings("unchecked")
private void initialize(final Builder builder) {
this.aProvider = DoubleCheck.provider(A_Factory.create());
this.dataHandlerProvider = DataHandler_Factory.create(aProvider);
}
#Override
public DataHandler getHandler() {
return dataHandlerProvider.get();
}
public static final class Builder {
private Builder() {}
public DataTransformerComponent build() {
return new DaggerDataTransformerComponent(this);
}
}
}
I am unable to figure out why it does not create _factory class when i use #Singleton annotation.?.
Just use regular JavaScript + node.js, its a lot simpler

Dagger 2 multiple Repositories

So i am new to Dagger 2 dependency injection. I have created a custom ViewModelFactory class which returns my ViewModel.
#Singleton
public class CustomViewModelFactory implements ViewModelProvider.Factory {
private final MyCatchesRepository repository;
#Inject
public CustomViewModelFactory(MyCatchesRepository repository) {
this.repository = repository;
}
#NonNull
#Override
#SuppressWarnings("unchecked")
public <T extends ViewModel> T create(#NonNull Class<T> modelClass) {
if (modelClass.isAssignableFrom(MyCatchViewModel.class)) {
return (T) new MyCatchViewModel(repository);
} else {
throw new IllegalArgumentException("ViewModel Not Found");
}
}
}
The CustomViewModel takes a MyCatchesRepository in the constructor and then creates the MyCatchViewModel.
How could i change this class so that i can use this ViewModelFactory to create different ViewModels with different constructor arguments (repositories)
This is the Module where the CustomViewModelFactory is created
#Module
public class RoomModule {
private final MyDatabase myDatabase;
public RoomModule(Application application) {
this.myDatabase = Room.databaseBuilder(application,
MyDatabase.class, AppConstants.DATABASE_NAME)
.build();
}
#Provides
#Singleton
MyCatchesRepository provideCatchesRepository(MyCatchDao myCatchDao) {
return new MyCatchesRepository(myCatchDao);
}
#Provides
#Singleton
MyCatchDao providesCatchDao(MyDatabase myDatabase) {
return myDatabase.myCatchDao();
}
#Provides
#Singleton
LuresRepository provideLureRepository(LureDao lureDao) {
return new LuresRepository(lureDao);
}
#Provides
#Singleton
LureDao provideLureDao(MyDatabase myDatabase) {
return myDatabase.lureDao();
}
#Provides
#Singleton
MyDatabase provideDatabase(Application application) {
return myDatabase;
}
#Provides
#Singleton
ViewModelProvider.Factory provideCatchesViewModelFactory(MyCatchesRepository catchesRepository) {
return new CustomViewModelFactory(catchesRepository);
}
}
ViewModelModule
#Module
public abstract class ViewModelModule {
#Binds
#IntoMap
#ViewModelKey(MyCatchViewModel.class)
abstract ViewModel myCatchViewModel(MyCatchViewModel myCatchViewModel);
#Binds
#IntoMap
#ViewModelKey(FishingSpotViewModel.class)
abstract ViewModel fishingSpotViewModel(FishingSpotViewModel fishingSpotViewModel);
#Binds
abstract ViewModelProvider.Factory bindCustomViewModelFactory(CustomViewModelFactory customViewModelFactory);
}
The approach the Google team came up with in the architecture components samples is to use a custom annotation in order to provide ViewModel classes through dagger.
In Java the Annotation looks as follows.
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import androidx.lifecycle.ViewModel;
import dagger.MapKey;
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
#MapKey
#interface ViewModelKey {
Class<? extends ViewModel> value();
}
This uses MapKey from Dagger, where any annotated ViewModel will be composed into a Map which can then be used in your ViewModelFactory.
In the Google samples the ViewModelFactory looks as follows, where using constructor injection you can access the map of ViewModel providers.
public class ViewModelFactory implements ViewModelProvider.Factory {
private final Map<Class<? extends ViewModel>, Provider<ViewModel>> viewModels;
#Inject
public ViewModelFactory(Map<Class<? extends ViewModel>, Provider<ViewModel>> viewModels) {
this.viewModels = viewModels;
}
#NonNull
#Override
public <T extends ViewModel> T create(#NonNull Class<T> modelClass) {
Provider<ViewModel> viewModelProvider = viewModels.get(modelClass);
if (viewModelProvider == null) {
throw new IllegalArgumentException("model class " + modelClass + " not found");
}
//noinspection unchecked
return (T) viewModelProvider.get();
}
}
In your example your would end up with the following in order to provide the MyCatchViewModel. Other ViewModels could then be provided by following the same pattern.
#Module
public abstract class ViewModelModule {
#Binds
#IntoMap
#ViewModelKey(MyCatchViewModel.class)
abstract ViewModel myCatchViewModel(MyCatchViewModel myCatchViewModel);
}
For a complete example you can check out the GithubBrowserSample sample from Google. https://github.com/googlesamples/android-architecture-components/blob/master/GithubBrowserSample/app/src/main/java/com/android/example/github/di/ViewModelModule.kt

cannot be provided without an #Provides-annotated method -- - Confusedddd

Error:(39, 10) error: [Dagger/MissingBinding] kz.production.kuanysh.sellings.ui.content_owner.fragments.order.orders.OrdersFragmentPresenter cannot be provided without an #Provides-annotated method.
kz.production.kuanysh.sellings.ui.content_owner.fragments.order.orders.OrdersFragmentPresenter is injected at
kz.production.kuanysh.sellings.ui.content_owner.fragments.order.orders.OrdersFragment.mPresenter
kz.production.kuanysh.sellings.ui.content_owner.fragments.order.orders.OrdersFragment is injected at
kz.production.kuanysh.sellings.di.component.ActivityComponent.inject(kz.production.kuanysh.sellings.ui.content_owner.fragments.order.orders.OrdersFragment)
Component :
#PerActivity
#Component(dependencies = ApplicationComponent.class, modules =
ActivityModule.class)
public interface ActivityComponent {
void inject(MainActivity activity);
void inject(LoginActivity activity);
void inject(OwnerSupplierItemFragment ownerSupplierItemFragment);
void inject(OrdersFragment ordersFragment);
}
In module class :
#Module
public class ActivityModule {
private AppCompatActivity mActivity;
public ActivityModule(AppCompatActivity activity) {
this.mActivity = activity;
}
#Provides
#ActivityContext
Context provideContext() {
return mActivity;
}
#Provides
AppCompatActivity provideActivity() {
return mActivity;
}
#Provides
CompositeDisposable provideCompositeDisposable() {
return new CompositeDisposable();
}
#Provides
SchedulerProvider provideSchedulerProvider() {
return new AppSchedulerProvider();
}
#Provides
#PerActivity
LoginMvpPresenter<LoginMvpView> provideLoginPresenter(
LoginPresenter<LoginMvpView> presenter) {
return presenter;
}
#Provides
#PerActivity
MainMvpPresenter<MainMvpView> provideMainPresenter(
MainPresenter<MainMvpView> presenter) {
return presenter;
}
/*#Provides
OrdersFragmentMvpPresenter<OrdersFragmentMvpView> provideOrdersFragmentPresenter(
OrdersFragmentPresenter<OrdersFragmentMvpView> presenter) {
return presenter;
}*/
#Provides
#PerActivity
OrdersFragmentMvpPresenter<OrdersFragmentMvpView> provideOrdersFragmentPresenter(
OrdersFragmentPresenter<OrdersFragmentMvpView> presenter){
return presenter;
}
#Provides
#PerActivity
OwnerSupplierItemMvpPresenter<OwnerSupplierItemMvpView> provideOwnerSupplierItemPresenter(
OwnerSupplierItemPresenter<OwnerSupplierItemMvpView> presenter) {
return presenter;
}
#Provides
LinearLayoutManager provideLinearLayoutManager(AppCompatActivity activity) {
return new LinearLayoutManager(activity);
}
}
My presenter
public class OrdersFragmentPresenter<V extends OrdersFragmentMvpView> extends
BasePresenter<V>
implements OrdersFragmentMvpPresenter<V> {
#Inject
public OrdersFragmentPresenter(DataManager dataManager, SchedulerProvider schedulerProvider, CompositeDisposable compositeDisposable) {
super(dataManager, schedulerProvider, compositeDisposable);
}
#Override
public void onViewPrepared() {
//getMvpView().updateOrders();
}
#Override
public void onDetailClick(int position) {
getMvpView().openOrderDetailFragment();
}
}
So the issue is that you are using
OrdersFragmentPresenter
But you are never providing it, so dagger will not know how to get an instance of this.
#Provides
OrdersFragmentMvpPresenter<OrdersFragmentMvpView> provideOrdersFragmentPresenter(
OrdersFragmentPresenter<OrdersFragmentMvpView> presenter) {
return presenter;
}
So you need to add something like
#Provides
OrdersFragmentPresenter<OrdersFragmentMvpView> provideOrdersFragmentPresenter(
) {
return new OrdersFragmentPresenter(); //something like this;
}
Also, you should be careful using custom scopes in the module. As a good practice, each module should be providing elements for only one scope.

Dagger - Is it possible to select a Provider based on inheritance?

At the moment I have a Base class that contains a member I would like to inject. However, I would like the concrete type of this member to depend on the Subclass being instantiated. What I am aiming for is something along these lines:
public interface StringInterface {
public String getString();
}
public class HelloStringConcrete implements StringInterface {
public String getString() {
return "Hello";
}
}
public class WorldStringConcrete implements StringInterface {
public String getString() {
return "World";
}
}
public abstract class Base {
#Inject StringInterface member;
public Base() {
// Assume access to object graph
MyObjectGraph.get().inject(this);
}
public void printSomething() {
System.out.println(member.getString());
}
}
public class SubclassHello extends Base {}
public class SubclassWorld extends Base {}
#Module(injects = {SubclassHello.class})
public class HelloModule {
#Provides StringInterface provideStringInterface() {
return new HelloStringConcrete();
}
}
#Module(injects = {SubclassWorld.class})
public class WorldModule {
#Provides StringInterface provideStringInterface() {
return new WorldStringConcrete();
}
}
So now what I would like to do is something along the lines of:
#Module(
includes = {
HelloModule.class,
WorldModule.class
}
)
public class BigModule {}
// Somewhere in another piece of code...
objectGraph = ObjectGraph.create(new BigModule());
// In yet another piece of code...
SubclassHello hello = new SubclassHello();
SubclassWorld world = new SubclassWorld();
hello.printSomething();
world.printSomething();
// Hopefully would result in :
// Hello
// World
This type of setup won't work though, because including two modules with the same provider will result in a duplicate provider error at compile time. It would be cool to see a solution to this problem without introducing #Named or #Qualifer annotations, or using scoped graph extensions via graph.plus() because these strategies necessarily introduce coupling to the subclasses
This is possible but I think the code I've attached below is more coupled than using scoped graphs or annotations. Basically you can use constructor injection to inject concrete dependencies to your
SubclassHello and SubclassWorld.
public abstract class Base {
private final StringInterface member;
public Base(StringInterface member) {
this.member = member;
}
...
}
#Module(injects = {SubclassWorld.class})
public class WorldModule {
#Provides
WorldStringConcrete provideStringInterface() {
return new WorldStringConcrete();
}
}
public class SubclassWorld extends Base {
#Inject
public SubclassWorld(WorldStringConcrete worldStringConcrete) {
super(worldStringConcrete);
}
}
#Module(injects = {SubclassHello.class})
public class HelloModule {
#Provides
HelloStringConcrete provideStringInterface() {
return new HelloStringConcrete();
}
}
public class SubclassHello extends Base {
#Inject
public SubclassHello(HelloStringConcrete helloStringConcrete) {
super(helloStringConcrete);
}
}
// Somewhere in another piece of code...
ObjectGraph objectGraph = ObjectGraph.create(new BigModule());
// In yet another piece of code...
SubclassHello hello = objectGraph.get(SubclassHello.class);
SubclassWorld world = objectGraph.get(SubclassWorld.class);
I don't think there are other solutions. How could Dagger find out which StringInterface implementations should be injected to the concrete classes?

Resources