I'm new to DI and Dagger.
I have this dependency graph in the Android project:
#Module(includes=[Module1, Module2, Module3]) ClassAModule
#Module(includes=[classAModule, Module4]) ClassBModule
#Module(includes=[ClassBModule]) ClassCModule
#Module(includes=[ClassBModule]) ClassDModule
Here's how Module3 looks like
#Module
class Module3 {
#Provides
fun provideUrl(): Url{
return ...
}
}
Module3's Url is required by ClassAModule,
But I want ClassCModule and ClassDModule to be able to provide different Url to ClassAModule
how should I approach this?
To get specific url for any module, you need to define annotation on provider method.
Example
#Provides
#Room
fun provideRoomWordDataSource(): WordDataSource {
return RoomWordDataSource()
}
#Provides
#Firestore
fun provideFirestoreWordDataSource(): WordDataSource {
return FirestoreWordDataSource()
}
#Singleton
class WordRepository
#Inject constructor(
#Room private val room: WordDataSource,
#Firestore private val firestore: WordDataSource
) : Repository<String, Word>(rx, rm), WordDataSource {
}
First two provider method has define WordDataSource instance of two difference class and define with two different annotation #Room and #Firestore.
To get two different WordDataSource in WordRepository, have just used #Room and #Firestore annotation in its constructor.
Enjoy the annotation power in Dagger. :)
Please feel free, if you need more details from me.
Related
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.
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
I am implementing some of the architectural designs from Google I/O's app to my own app, but I have come across something in their app that has created some confusion for me.
They have a domain layer with repository usecases, which I use myself in my apps usually. However, I do have to provide these usecases with dagger in my app. But on Google's I/O app, I cannot find any module that provides these usecases. And when used with viewmodels annotated #HiltViewModel (In my own app), it seems like it works? Somehow these get injected into my viewmodels. I do have to provide all the usecase's dependencies(repositories etc) with Hilt, but I don't have to provide any usecase via Hilt.
Here is example how it looks in my code.
Usecase:
abstract class UseCase<in P, R>(private val coroutineDispatcher: CoroutineDispatcher) {
suspend operator fun invoke(parameters: P): Resource<R> {
return try {
withContext(coroutineDispatcher) {
execute(parameters).let {
Resource.Success(it)
}
}
} catch (e: Exception) {
Timber.d(e)
Resource.Error(e.toString())
}
}
#Throws(RuntimeException::class)
protected abstract suspend fun execute(parameters: P): R
}
Concrete implementation of usecase:
class GetListUseCase #Inject constructor(
private val coroutineDispatcher: CoroutineDispatcher,
private val remoteRepository: RemoteRepository
): UseCase<ListRequest,ItemsList>(coroutineDispatcher) {
override suspend fun execute(parameters: ListRequest): ItemsList{
return remoteRepository.getList(parameters)
}
}
Viewmodel:
#HiltViewModel
class DetailViewModel #Inject constructor(
private val GetListUseCase: getListUseCase
): ViewModel() {
suspend fun getList(): Resource<ItemsList> {
getPokemonListUseCase.invoke(ListRequest(3))
}
}
Example of provided repo:
#Singleton
#Provides
fun provideRemoteRepository(
api: Api
): RemoteRepository = RemoteRepositoryImpl(api)
Remote repo:
#ActivityScoped
class RemoteRepositoryImpl #Inject constructor(
private val api: Api
): RemoteRepository {
override suspend fun getList(request: ListRequest): PokemonList {
return api.getPokemonList(request.limit, request.offset)
}
}
My Question is:
How come this works? Why don't I have to provide usecase' via Hilt? Or is my design wrong and I should provide usecases via Hilt even if this works?
EDIT:
Link to Google I/O domain layer and ui layer if it helps.
Your design is great! And it's not wrong, but yes, you can add some additional context for your use cases if you put them to a separate module and scope them per your needs. Modules exist mostly for cases when you do have your third-party dependencies (the simplest example is OkHTTPClient) or when you have interface -> impl of the interface, or you want to visibly limit/extend lifecycle/visibility of your components.
Currently you're telling Hilt how to provide instances of GetListUseCase by annotating its constructor with #Inject AND Hilt already knows what is "CoroutineDispatcher" (because it's been provided by #Provides in the coroutine module, right?) and what is RemoteRepository (because you're injecting the interface, but beneath the hood you're providing the real implementation of it in the repo module by #Provides).
So it's like saying - give me an instance of the use case class with two constructor dependencies, and both of them are known to Hilt, so it's not confusing.
And if you want to have a scoped binding/component (like explained here) or to mark your use case as a singleton, then you have to create a UseCaseModule and scope your use case components there.
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
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.