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)
}
}
}
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
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
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.
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?