Dagger multibinding with a custom qualifier - android

I have the following interface called SettingHandler that is responsible for handling events related to a particular setting inside the Android app.
interface SettingHandler {
fun onHandleEvent(event: SettingEvent)
fun candleHandleEvent(event: SettingEvent): Boolean
fun getSettingId(): SettingId
}
Then let's say I have the following Dagger module:
#Module
object SettingsModule {
#Provides
fun provideSettingPresenter(
view: SettingsContract.View,
settingHandlers: Set<#JvmSuppressWildcards SettingHandler>
): SettingsPresenter {
//
}
#Provides
#IntoSet
fun provideSettingHandlerA(
dependencyA: A,
dependencyB: B
): SettingHandler {
//
}
#Provides
#IntoSet
fun provideSettingHandlerB(
settingHandlerC: Provider<SettingHandler>
): SettingHandler {
//
}
#Provides
#IntoSet
fun provideSettingHandlerC(
settingHandlerB: Provider<SettingHandler>
): SettingHandler {
//
}
}
As you can see, nothing too special here, expect for the SettingHandlerB and SettingHandlerC provider methods. They both depend on each other, so I've decided to resolve this circular dependency using the Dagger supplied Provider class. Since I require a concrete implementation of a setting handler (SettingHandlerC for the SettingHandlerB and SettingHandlerB for the SettingHandlerC), I now need to differentiate between them. That's where a #Qualifier annotation comes into play. I created the following qualifier annotation to differentiate among different implementations.
#Qualifier
#Retention(AnnotationRetention.RUNTIME)
#Target(AnnotationTarget.FIELD, AnnotationTarget.FUNCTION, AnnotationTarget.VALUE_PARAMETER)
annotation class SettingHandlerType(val id: SettingId)
SettingId is basically an enumeration that contains all possible setting constants. So now my SettingHandlers module looks as follows:
#Module
object SettingsModule {
#Provides
fun provideSettingPresenter(
view: SettingsContract.View,
settingHandlers: Set<#JvmSuppressWildcards SettingHandler>
): SettingsPresenter {
//
}
#Provides
#SettingHandlerType(SettingId.A)
fun provideSettingHandlerA(
dependencyA: A,
dependencyB: B
): SettingHandler {
//
}
#Provides
#SettingHandlerType(SettingId.B)
fun provideSettingHandlerB(
#SettingHandlerType(SettingId.C)
settingHandlerC: Provider<SettingHandler>
): SettingHandler {
//
}
#Provides
#SettingHandlerType(SettingId.C)
fun provideSettingHandlerC(
#SettingHandlerType(SettingId.B)
settingHandlerB: Provider<SettingHandler>
): SettingHandler {
//
}
}
And here comes the problem. Mutlibinding now does not work, because all my SettingHandlers are annotated with a #SettingHandlerType annotation and a Set that I'm injecting into the SettingsPresenter also needs to be annotated. However, annotating it with, for example, #SettingHandlerType(SettingId.A) won't work because in that case the Set will contain only setting handlers with that particular qualifier (SettingHandlerA, in this case). How can I construct a Set data structure using multibinding because I really don't want to provide another provider method where I'll be constructing it myself?
Any help would be much appreciated. Thanks in advance.

You want two different types of data: Individual handlers of type #SettingHandlerType(/*...*/) SettingHandler and a set of type Set<SettingHandler>. I think the best way would be to have each definition come with a #Binds #IntoSet complement.
#Binds #IntoSet abstract fun bindSettingHandlerA(
#SettingHandlerType(SettingId.A) handler: SettingHandler): SettingHandler
Unfortunately, since you've defined this as an object in Kotlin, it's slightly more complicated to put the necessarily-abstract #Binds methods on the same object. You may need to pursue one of these other solutions to make that topology work with your case, including the use of a companion object to capture the otherwise-static #Provides methods:
Dagger 2 static provider methods in kotlin
#Provides and #Binds methods in same class Kotlin
Dagger2 : How to use #Provides and #Binds in same module (java)

Related

How to Isolate Hilt Modules

I'm using in my app hilt for di. I have a BasePresenter for a custom usage which takes as a parameter a List
abstract class BasePresenter(private val rules :MutableList<Rule>){...}
All of the concrete implementation Presenters should extend this one and provide the list like so
class FirstPresenter(rules: MutableList<Rule>) :BasePresenter<IrisView> (rules) {...}
class SecondPresenter(rules: MutableList<Rule>) :BasePresenter<IrisView> (rules) {...}
and each respective module is like so
#Module
#InstallIn(ActivityComponent::class)
object FirstModule {
#Provides
#ActivityScoped
fun provideFirstPresenter(rules: MutableList<Rule>) = FirstPresenter(rules)
#Provides
#ActivityScoped
fun provideRules(): MutableList<Rule> {
val rules = mutableListOf<Rule>()
rules.add(SpecificRule())
return rules
}
This works as expected, however when I try to add a second module for a different usecase like so
#Module
#InstallIn(ActivityComponent::class)
object SecondModule {
#Provides
#ActivityScoped
fun provideSecondPresenter(rules: MutableList<Rule>) = SecondPresenter(rules)
#Provides
#ActivityScoped
fun provideRules(): MutableList<Rule> {
val rules = mutableListOf<Rule>()
rules.add(OtherSpecificRule())
return rules
}
Hilt breaks because It seems that these two module have a different #provides method for
MutableList of rules. As I understand this happens because these two Modules exist in the same Scope ( for all Activities, as they are InstalledIn Activity component). So how Can I isolate those two Modules? In Dagger I assigned modules to components which were specific to the corresponding Feature, is that thing possible in Hilt, or should I address this in a different way?
Because Hilt returns the same type MutableList<Rule> in the same scope, I feel like Hilt is confusing which one to implement, because if a module provides something, it can be injected in a different module that is in equal scope. So, you can actually inject the returned list from fun provideRules() in the FirstModule and in the SecondModule, while providing the first or second presenter. That is why Hilt is generating errors, because it is not determined which one to implement.
If you want different objects, you can use the #Named annotation and pass the parameter to the function that you target. The code will turn into something like this:
First module:
#Module
#InstallIn(ActivityComponent::class)
object FirstModule {
#Provides
#ActivityScoped
fun provideFirstPresenter(#Named("firstPresenterRules") rules: MutableList<Rule>)
= FirstPresenter(rules)
#Provides
#ActivityScoped
#Named("firstPresenterRules")
fun provideRules(): MutableList<Rule> {
val rules = mutableListOf<Rule>()
rules.add(SpecificRule())
return rules
}
Second module:
#Module
#InstallIn(ActivityComponent::class)
object SecondModule {
#Provides
#ActivityScoped
fun provideSecondPresenter(#Named("secondPresenterRules") rules: MutableList<Rule>)
= SecondPresenter(rules)
#Provides
#ActivityScoped
#Named("secondPresenterRules")
fun provideRules(): MutableList<Rule> {
val rules = mutableListOf<Rule>()
rules.add(OtherSpecificRule())
return rules
}
Using the #Named annotation with different names allows you to have methods that return the same type, and also provide different ones as you desire.

Kotlin + Hilt: Class cannot be provided without an #Provides-annotated method when injecting into an object

I was trying to look for similar posts in SO before posting, but most of them talk about retrofit, and my question is about injecting a dependency (Service, Repository or whatever) into an object using #EntryPoint.
I have an object like this:
object FreddieMercuryYouAreTheOne {
lateinit var exception: ExceptionHandler
fun init(appContext: Context) {
setDependencies(appContext)
DoOtherInitStuff...
}
private fun setDependencies(appContext: Context){
val exh = EntryPointAccessors.fromApplication(appContext, Dependencies.ProvideExceptionHandler::class.java)
this.exception = exh.exceptionHandler()
}
/*
* THIS IS JUST AN ABSURD EXAMPLE
* */
private fun DoWhatever(cryptKey16CharStr: String, cryptInitializationVector16CharStr: String) {
try {
doWhatever
}catch(ex: Exception){
exception.logException(ex)
}
}
}
And then I have the class where I set the dependencies:
#Module
#InstallIn(SingletonComponent::class)
class Dependencies {
#EntryPoint
#InstallIn(SingletonComponent::class)
interface ProvideExceptionHandler {
fun exceptionHandler(): ExceptionHandler
}
}
And when building, what I get is the following error:
error: [Dagger/MissingBinding] exception.ExceptionHandler cannot be provided without an #Provides-annotated method.
Well, if I modify my dependencies module as follows:
#Module
#InstallIn(SingletonComponent::class)
class Dependencies {
#Provides
#Singleton
fun bindsExceptionHandler(): ExceptionHandler {
return ExceptionHandler
}
#EntryPoint
#InstallIn(SingletonComponent::class)
interface ProvideExceptionHandler {
fun exceptionHandler(): ExceptionHandler
}
}
Not only build, but it works, and ExceptionHandler is correctly injected in FreddieMercuryYouAreTheOne object, so, as you see, what I have is not exactly an issue, but wondering to know why I need two "providers" to be able to inject a dependency into an object, lets say, why is not enough with interface ProvideExceptionHandler (as Google documentation mentions).
I ask this because I have many class objects across my app, and most of them have dependencies, and so this way I'll have to create two providers for each dependency. Am I doing something wrong?
Entry Points used for field injection for un-supported classes by Hilt like a custom class or content provider.
in your case since you have object FreddieMercuryYouAreTheOne thats can't has a constructer . yeah you need :
1- to Provide the object(instance) you want
in your case:
#Provides
#Singleton
fun bindsExceptionHandler(): ExceptionHandler {
return ExceptionHandler
}
2- and then say hey!! ,i need field injection in my custom class
then you should use :
#EntryPoint
#InstallIn(SingletonComponent::class)
interface ProvideExceptionHandler {
fun exceptionHandler(): ExceptionHandler
}
if you have a normal class you just need to provide the object(just point #1). and then inject it in the constructer.
as i say #EntryPoint for un-supported classes field injection just.
Hint the recommended is constructer-injection over field injection
PLUS: ExceptionHandler and most of dependencies should be injected into ViewModel

Dagger2, adding a binding for ViewModelProvider.Factory in a dependant component

The Problem
When attempting to add a ViewModel bind into the multibinding for an inherited ViewModelFactory (created with no scope) within a lower scope (#FragmentScope), I keep running into this error:
java.lang.IllegalArgumentException: unknown model class com.example.app.MyFragmentVM
What I've read or tried
(note: the below is not by any means an exhaustive list, but are two good examples of resources and the kinds of advice I've perused)
[1] dagger2 and android: load module which injects viewmodel on a map (and other variants / similar Q and As)
[2] https://medium.com/tompee/dagger-2-scopes-and-subcomponents-d54d58511781
I'm relatively new to working with Dagger so I had to do a lot of Googling to try and understand what has been going on, but I've reached a point where, to my understanding, something should be working(?)...
From sources similar to [1], I removed the #Singleton scope on ViewModelFactory, but I still get the aforementioned crash saying there is no model class found in the mapping.
From sources similar to [2] I tried to reinforce my understanding of how dependencies worked and how items are exposed to dependant components. I know and understand how ViewModelProvider.Factory is available to my MyFragmentComponent and it's related Modules.
However I do not understand why the #Binds #IntoMap isn't working for the MyFragmentVM.
The Code
Let me first go through the setup of the stuff that already exists in the application -- almost none of it was scoped for specific cases
// AppComponent
#Component(modules=[AppModule::class, ViewModelModule::class])
interface AppComponent {
fun viewModelFactory(): ViewModelProvider.Factory
fun inject(activity: MainActivity)
// ... and other injections
}
// AppModule
#Module
class AppModule {
#Provides
#Singleton
fun providesSomething(): Something
// a bunch of other providers for the various injection sites, all #Singleton scoped
}
// ViewModelModule
#Module
abstract class ViewModelModule {
#Binds
abstract fun bindsViewModelFactory(factory: ViewModelFactory): ViewModelProvider.Factory
#Binds
#IntoMap
#ViewModelKey(MainActivityVM::class)
abstract fun bindsMainActivityVM(vm: MainActivityVM): ViewModel
}
// VMFactory
class ViewModelFactory #Inject constructor(
private val creators: Map<Class<out ViewModel>, #JvmSuppressWildcards Provider<ViewModel>>
): ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
val creator = creators[modelClass] ?: creators.entries.firstOrNull {
modelClass.isAssignableFrom(it.key)
}?.value ?: throw IllegalArgumentException("unknown model class $modelClass")
try {
#Suppress("UNCHECKED_CAST")
return creator.get() as T
} catch (e: Exception) {
throw RuntimeException(e)
}
}
}
And the following is how I am trying to add and utilize my #FragmentScope:
// MyFragmentComponent
#FragmentScope
#Component(
dependencies = [AppComponent::class],
modules = [MyFragmentModule::class, MyFragmentVMModule::class]
)
interface MyFragmentComponent {
fun inject(fragment: MyFragment)
}
// MyFragmentModule
#Module
class MyFragmentModule {
#Provides
#FragmentScope
fun providesVMDependency(): VMDependency {
// ...
}
}
// MyFragmentVMModule
#Module
abstract class MyFragmentVMModule {
#Binds
#IntoMap
#ViewModelKey(MyFragmentVM::class)
abstract fun bindsMyFragmentVM(vm: MyFragmentVM): ViewModel
}
// MyFragment
class MyFragment : Fragment() {
#set:Inject
internal lateinit var vmFactory: ViewModelProvider.Factory
private lateinit var viewModel: MyFragmentVM
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
DaggerMyFragmentComponent.builder()
.appComponent(MyApplication.instance.component)
.build()
.inject(this)
viewModel = ViewModelProvider(this, vmFactory).get(MyFragmentVM::class.java)
}
}
What's interesting here to note is that MyFragmentModule itself does NOT end up providing any unique injections for MyFragment (those all come from AppComponent as it is right now). It DOES however, provide unique injections for the ViewModel that MyFragment uses.
The root of this problem is the difference between subcomponents and component dependencies.
Subcomponents
When working with subcomponents, the parent component knows everything about its subcomponents. As such, when a subcomponent requests a multibinding, the parent component can combine its contributions with those of the subcomponent. This even works transitively: if the subcomponent requests an unscoped ViewModelProvider.Factory, the injected map will include bindings from the subcomponent. (The same is true of a #Reusable binding, but not a #Singleton.)
If you change your components with dependencies into subcomponents, everything will just work. However, this might not fit your desired architecture. In particular, this is impossible if MyFragmentComponent is in an Instant App module.
Component dependencies
When working with component dependencies, the main component merely exposes objects through provision methods, and it does not know about any components that might depend on it. This time, when asked for a ViewModelProvider.Factory, the main component does not have access to any #ViewModelKey bindings except its own, and so the Factory it returns will not include the MyFragmentVM binding.
If MyFragmentComponent does not require any ViewModel bindings from AppComponent, you can extract bindsViewModelFactory into its own module and include it in both components. That way, both components can create their own Factory independently.
If you do need some ViewModel bindings from AppComponent, hopefully you can add those binding modules to MyFragmentComponent as well. If not, you would have to expose the map in AppComponent, and then somehow combine those entries with your new bindings. Dagger does not provide a good way to do this.

Dagger: How to provide specific implementation of a parametrized base type?

I'm new to Dagger and have the following setup:
// data models
open class BaseEntity (open val id: Long)
data class UserEntity (override val id: Long, val name: String) : BaseEntity(id)
data class FruitEntity (override val id: Long, val status: String) : BaseEntity(id)
// interface to spec common API response operations
interface Repo<T> {
fun getEntities(): List<T>
}
// entity specific implementation of the repo interface
class RepoImpl<T: BaseEntity> #Inject constructor(apiService: ApiService) : Repo<T> {
override fun getEntities(): List<T> {
val entities = apiService.getEntities(...)// get result from respective entity network service, e.g users, fruits etc
return entities
}
}
// DI: provide entity-specific implementations of the interface
#Singleton
#Provides
fun provideUserRepoImpl(userService: UserApiService): RepoImpl<BaseEntity> {
return RepoImpl(userService)
}
#Singleton
#Provides
fun provideFruitRepoImpl(fruitService: FruitApiService): RepoImpl<BaseEntity> {
return RepoImpl(fruitService)
}
When I build the project, this error comes up:
error: RepoImpl<com.example.data.model.BaseEntity> is bound multiple times
...
#org.jetbrains.annotations.NotNull #Provides #Singleton com.example.data.source.remote.RepoImpl<com.example.data.model.BaseEntity> com.example.app.di.AppModule.provideUserRepoImpl()
#org.jetbrains.annotations.NotNull #Provides #Singleton com.example.data.source.remote.RepoImpl<com.example.data.model.BaseEntity> com.example.app.di.AppModule.provideFruitRepoImpl()
And when I try to provide the entity-specific instances, like this:
#Singleton
#Provides
fun provideUserRepoImpl(userService: UserApiService): RepoImpl<UserEntity> {
return RepoImpl(userService)
}
#Singleton
#Provides
fun provideFruitRepoImpl(fruitService: FruitApiService): RepoImpl<FruitEntity> {
return RepoImpl(fruitService)
}
I get the following:
error: RepoImpl<com.example.data.model.BaseEntity> cannot be provided without an #Inject constructor or from an #Provides-annotated method
Also I have tried separating the provider methods of fruits & users into their respective modules but latter error also comes up, maybe I'm not hooking up the FruitModule, UserModule and AppModule correctly. I'm having similar problems providing implementations of interfaces.
What would be a correct approach here, or is it not at all possible to inject parametrized classes with Dagger?
UPDATE
I have managed to solve the dependency issue on RepoImpl by explicitly providing each object to be injected (the latter option where we provide RepoImpl<UserEntity> and RepoImpl<FruitEntity>). I think the #Inject annotation on the constructor was somehow not being registered,
so had to refresh the project.
However, I cannot figure out how to provide implementations (or sub-types) of parametrized interfaces just yet.
Consider for example the ApiService parameter of RepoImpl defined as follows:
// base service for all types of items
interface ApiService<T: BaseAttributes> {
fun getEntities(): T
}
// service impl for fruits
class FruitService : ApiService<FruitAttributes> {
override fun getEntities(): FruitResponses {...}
}
// service impl for users
class UserService : ApiService<UserAttributes> {
override fun getEntities(): UserResponses {...}
}
// AppModule.kt:
#Singleton
#Provides
fun provideUserService() : ApiService<UserAttributes> {
return UserService()
}
#Singleton
#Provides
fun provideFruitService() : ApiService<FruitAttributes> {
return FruitService()
}
error: "... ApiService<BaseAttributes> cannot be provided without an #Provides-annotated method ..."
whereas
...
fun provideUserService() : ApiService<BaseAttributes> {
return UserService() as ApiService<BaseAttributes>
}
...
fun provideFruitService() : ApiService<FruitAttributes> {
return FruitService() as ApiService<BaseAttributes>
}
error: "... ApiService<BaseAttributes> is bound multiple times: ..."
How else can I inject these implementations of the interface?
Turns out there's a well-discussed wildcard issue on dagger/kotlin generics. Particularly, the "cannot be provided without an #Provides-annotated method" error on parametrized classes requires suppressing generation of wildcard types (covariants or contravariants) with the #JvmSuppressWildcard annotation at the injection site. So I would have done:
class RepoImpl<T: BaseEntity> #Inject constructor(apiService: #kotlin.jvm.JvmSuppressWildcards ApiService) : Repo<T> { ... }
Though I actually ended up converting RepoImpl into an abstract class and creating concrete ones for Fruit & User types and providing them at the module-level instead:
class UserRepoImpl #Inject constructor(apiService: UserService) : RemoteRepoImpl(apiService)
class FruitRepoImpl #Inject constructor(apiService: FruitService) : RemoteRepoImpl(apiService)
This related SO question contains another example.
Finally, this Kotlin+Dagger thread contains some nice gotchas and tips relevant to this problem

Override dependencies in Dagger Module

There's a base module with common dependencies:
#Module
object CommonActivityModule {
#JvmStatic
#Provides
fun baseNavigator(activity: AppCompatActivity): Navigator = BaseNavigator(activity, SOME_STUFF)
// other common deps
}
I include it in every Activity module to obtain those common deps. But in some modules I want to shadow a few base interface implementations with another:
#Module(includes = [CommonActivityModule::class])
interface SomeActivityModule {
#Binds
fun anotherNavigator(anotherNavigator: AnotherNavigator): Navigator
// other module's binds
}
And it thows ..Navigator is bound multiple times-exception. Is there a way how I can replace those interface implementations without dropping the whole CommonActivityModule?
You're binding each as Navigator. I believe you need to use a different return type on your shadowed binding.
Alternatively, you could try something with Qualifiers. Defining a custom qualifier is easy; you should be able to find examples online. I'd share one, but I'm on my phone right now.
This answer has been accepted, so I'd like to add some code to make it more "complete". Here's an example of a custom "Qualifier" (Kotlin)
import javax.inject.Qualifier
#Qualifier
#Retention(AnnotationRetention.RUNTIME)
annotation class DelayQualifier
Usage:
#Module object {
#Provides #DelayQualifier #JvmStatic
fun provideDelay(): Long = if (BuildConfig.DEBUG) 1L else 3L
}
#ActivityScoped
class SignupViewModelFactory #Inject constructor(
#param:DelayQualifier private val delay: Long
) : ViewModelProvider.Factory { ... }
This is the only Long I'm currently injecting in my project, so I don't need the qualifier. But if I decide I want more Longs, I'll regret not qualifying this one.

Categories

Resources