I'm migrating DI from Koin to Dagger Hilt. I have a custom interface with many implementations, and I want to inject all the instances in a useCase as a list.
For example:
#Singleton
class MyUseCaseImpl #Inject constructor(
private val myInterfaces: List<MyInterface>,
) : MyUseCase {
...
}
When I used Koin, I would do:
single<MyUseCase> {
MyUseCaseImpl(
myInterfaces = getKoin().getAll(),
)
}
How can I do the same with Hilt?
I've already binded each implementation with my interface like:
#Binds
abstract fun bindFirstMyInterfaceImpl(
firstMyInterfaceImpl: FirstMyInterfaceImpl,
): MyInterface
You need multibindings. Provide your dependencies with #IntoSet annotation
#Binds
#IntoSet
abstract fun bindFirstMyInterfaceImpl(
impl: FirstMyInterfaceImpl1,
): MyInterface
#Binds
#IntoSet
abstract fun bindSecondMyInterfaceImpl(
impl: SecondMyInterfaceImpl,
): MyInterface
But instead of List multibindings uses Set (or Map)
#Singleton
class MyUseCaseImpl #Inject constructor(
private val myInterfaces: Set<#JvmSuppressWildcards MyInterface>,
) : MyUseCase {
...
}
Related
I have these Repositories dependent on DataSources.
class LocationRepository: Repository<String>(LocationDataSource())
class ItemRepository: Repository<String>(ItemDataSource())
I would like to inject the Repository class with Hilt like this to prevent code duplication.
abstract class Repository<T> {
#Inject lateinit var dataSource: DataSource<T>
...
}
I have tried this, but am not sure how to get Hilt to use the right ones.
#Qualifier
#Retention(AnnotationRetention.BINARY)
annotation class ItemDataSourceAnnotation
#Qualifier
#Retention(AnnotationRetention.BINARY)
annotation class LocationDataSourceAnnotation
#Module
#InstallIn(SingletonComponent::class)
object DataSourceModule {
#ItemDataSourceAnnotation
#Provides
#Singleton
fun provideItemDataSource(): DataSource{
return ItemDataSource()
}
#LocationDataSourceAnnotation
#Provides
#Singleton
fun provideLocationDataSource(): DataSource{
return LocationDataSource()
}
}
#Module
#InstallIn(SingletonComponent::class)
object RepositoryModule {
#Provides
#Singleton
fun providesItemRepository(
#ItemDataSourceAnnotation itemDataSource: ItemDataSource
): ItemRepository {
return ItemRepository()
}
#Provides
#Singleton
fun providesLocationRepository(
#LocationDataSourceAnnotation locationDataSource: LocationDataSource
): LocationRepository {
return LocationRepository()
}
}
If what you want is to avoid having the data source field in each repository subclass, you could add a data source type parameter and val to Repository:
class Repository<ValueT, DataSourceT>(val dataSource: DataSourceT) {
...
}
class LocationRepository: Repository<Location, LocationDataSource> #Inject constructor(dataSource: LocationDataSource): super(dataSource) {
...
}
It's better to use constructor injection than field injection, and using constructor injection, you have to pass the constructor parameters through the subclasses -- you can't do it only in the base class.
Hilt points out that this interface cannot be provided without an #Provides annotation:
interface PlannedListRepository {
fun getAllLists(): LiveData<List<PlannedList>>
suspend fun addList(plannedList: PlannedList)
suspend fun updateList(plannedList: PlannedList)
suspend fun deleteList(plannedList: PlannedList)
}
Implementation of the interface:
class PlannedListRepositoryImpl #Inject constructor(private val plannedListDao: PlannedListDao) :
PlannedListRepository {
...
}
As far as I know, if I want to get an interface, I can use #Binds to say to Dagger which implementation should be received. So I did this:
#Module
#InstallIn(ActivityComponent::class)
abstract class RepositoryModule {
#Binds
abstract fun providePlannedListRepository(impl: PlannedListRepositoryImpl) : PlannedListRepository
}
My ViewModel if it has something to deal with the error:
#HiltViewModel
class PlannedListViewModel #Inject constructor(
private val repository: PlannedListRepository
) : ViewModel() {
...
}
So what should I do to fix the error? The full error message:
AndroidStudioProjects\PlanShopping\app\build\generated\hilt\component_sources\debug\com\tetsoft\planshopping\PlannerApplication_HiltComponents.java:129: error: [Dagger/MissingBinding] com.tetsoft.planshopping.db.planned.PlannedListRepository cannot be provided without an #Provides-annotated method.
public abstract static class SingletonC implements PlannerApplication_GeneratedInjector,
^
com.tetsoft.planshopping.db.planned.PlannedListRepository is injected at
com.tetsoft.planshopping.ui.planned.PlannedListViewModel(repository)
com.tetsoft.planshopping.ui.planned.PlannedListViewModel is injected at
com.tetsoft.planshopping.ui.planned.PlannedListViewModel_HiltModules.BindsModule.binds(vm)
#dagger.hilt.android.internal.lifecycle.HiltViewModelMap java.util.Map<java.lang.String,javax.inject.Provider<androidx.lifecycle.ViewModel>> is requested at
dagger.hilt.android.internal.lifecycle.HiltViewModelFactory.ViewModelFactoriesEntryPoint.getHiltViewModelMap() [com.tetsoft.planshopping.PlannerApplication_HiltComponents.SingletonC ? com.tetsoft.planshopping.PlannerApplication_HiltComponents.ActivityRetainedC ? com.tetsoft.planshopping.PlannerApplication_HiltComponents.ViewModelC]
Here is the database module:
#Module
#InstallIn(SingletonComponent::class)
class DatabaseModule {
#Provides
#Singleton
fun provideDatabase(#ApplicationContext appContext: Context) : PlannerDatabase {
return Room.databaseBuilder(
appContext,
PlannerDatabase::class.java,
"PlannerDB"
)
.fallbackToDestructiveMigration()
.build()
}
#Provides
fun providePlannedListDao(plannerDatabase: PlannerDatabase) : PlannedListDao {
return plannerDatabase.plannedListDao()
}
#Provides
fun provideProductDao(plannerDatabase: PlannerDatabase) : ProductDao {
return plannerDatabase.productDao()
}
#Provides
fun provideSelectedProductDao(plannerDatabase: PlannerDatabase) : SelectedProductDao {
return plannerDatabase.selectedProductDao()
}
}
use #InstallIn(ViewModelComponent::class) in your repository module since you are injection repository in your view model
Is your application multi modules? If your answer is yes check your imports from your :app, in my case I facing the same error and I forgot to add my other modules where he DI was defined!
Better late than never.
Besides you need do the thing mentioned in the accepted answer.If you are working with compose or the latest Hilt version, init your ViewModel by the way of:
val viewModel: YourViewModel by viewModels()
do not use the way of:
#Inject lateinit var viewModel: YourViewModel
I'm getting this error error: [Dagger / MissingBinding] com.eduramza.domain.repositories.RemoteRepository cannot be provided without an # Provides-annotated method. when implementing my repository interface with android hilt.
That's because my useCase implements my repository interface. What may be wrong with my implementation, below is the code:
app.Viewmodel:
#HiltViewModel
class RemoteListViewModel #Inject constructor(
private val useCase: GetTickersUseCase
): ViewModel() {
}
domain.usecase:
class GetTickersUseCase #Inject constructor(
private val remoteRepository: RemoteRepository)
: SingleUseCase<MainCoins> {
override suspend fun executeCall(): Flow<Result<MainCoins>> = remoteRepository.readAllTickers()
}
domain.repository:
interface RemoteRepository {
suspend fun readAllTickers(): Flow<Result<MainCoins>>
}
core.repositoryImpl:
class RemoteRepositoryImpl #Inject constructor(
private val apiService: BraziliexService,
private val tickersMapper: TickersMapper
) : RemoteRepository{
override suspend fun readAllTickers(): Flow<Result<MainCoins>> {
TODO("Not yet implemented")
}
}
core.module:
#Module
#InstallIn(ActivityComponent::class)
abstract class RemoteModule {
#Binds
abstract fun bindRemoteRepository(
remoteRepositoryImpl: RemoteRepositoryImpl
): RemoteRepository
}
My multimodule app in this structure
where core implement domain, and app implement both.
why is the bind method not being initialized?
You using the ActivityComponent but the RemoteRepository is the indirect dependency of ViewModel so it should be tied with the ViewModel Lifecycle
so instead of ActivityComponent
#Module
#InstallIn(ActivityComponent::class)
abstract class RemoteModule {
#Binds
abstract fun bindRemoteRepository(
remoteRepositoryImpl: RemoteRepositoryImpl
): RemoteRepository
}
Use this ViewModelComponent
#Module
#InstallIn(ViewModelComponent::class)
abstract class RemoteModule {
#Binds
abstract fun bindRemoteRepository(
remoteRepositoryImpl: RemoteRepositoryImpl
): RemoteRepository
}
I am trying to add dagger-android to a Kotlin project and got confused when it is requied to create a Module and when it is sufficient to just declare an Inject contructor.
Assume there is the following dependency graph:
Activity
-> ViewModel
-> Repository
-> Webservice
-> Dao
-> Database
-> Application
To provide ViewModel for the Activity we create respective modules for the activity and ViewModel factory, and then create the ViewModel in the Activity manually like so:
#Module
abstract class ActivityModule {
#ContributesAndroidInjector
abstract fun mainActivity(): MainActivity
}
// Skiping ViewModelKey and ViewModelFactory code for brevity
#Module
abstract class ViewModelModule {
#Binds
internal abstract fun bindViewModelFactory(
factory: ViewModelFactory
): ViewModelProvider.Factory
#Binds
#IntoMap
#ViewModelKey(MainViewModel::class)
internal abstract fun mainViewModel(viewModel: MainViewModel): ViewModel
}
class MainActivity : DaggerAppCompatActivity() {
#Inject
lateinit var viewModelFactory: ViewModelProvider.Factory
private lateinit var viewModel: MainViewModel
override fun onCreate(savedInstanceState: Bundle?) {
...
viewModel = ViewModelProviders.of(this, viewModelFactory)
.get(HomeViewModel::class.java)
}
}
To provide Repository for the ViewModel we just declare #Inject constructor like so:
class MainViewModel #Inject constructor(private val repository: Repository): ViewModel() {}
To provide Webservice and Dao for the Repository as well as Database for the Dao we create respective Modules like so:
#Module
class NetworkModule {
#Provides
#Singleton
fun provideWebservice() = Webservice.create()
}
interface Webservice {
...
companion object Factory {
fun create(): Webservice {
...
return retrofit
}
}
}
#Module
class DataModule {
#Provides
#Singleton
fun provideApplicationDatabase(app: Application) =
AppDatabase.getDatabase(app)
#Provides
#Singleton
fun provideUserDao(db: AppDatabase) = db.userDao()
}
#Database(...)
abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
companion object {
fun getDatabase(context: Context): AppDatabase {
...
return instance
}
}
}
And the Application is provided for the Dabatabase by some magic in the AppComponent and the Application class
#Singleton
#Component(modules = [
AndroidSupportInjectionModule::class,
NetworkModule::class,
DataModule::class,
ViewModelModule::class,
ActivityModule::class
])
interface AppComponent: AndroidInjector<App> {
#Component.Builder
interface Builder {
#BindsInstance
fun create(application: Application): Builder
fun build(): AppComponent
}
}
class App : DaggerApplication() {
override fun applicationInjector(): AndroidInjector<out DaggerApplication> =
DaggerAppComponent.builder().create(this).build()
}
The questions are:
How does the Database get the Application instance? Is it AndroidSupportInjectionModule which does the magic?
Why do we need to create Modules for the Webservice and the Database but not the Repository?
Is it possible to annotate the Webservice interface and the Database class themselves to skip creating separate dagger modules for them?
Question 1 How does the Database get the Application instance? Is it AndroidSupportInjectionModule which does the magic?
Answer: No it is not the work of AndroidSupportInjectionModule. AndroidSupportInjectionModule is included in Dagger Android Support which helps in
"Configures bindings to ensure the usability of dagger.android and dagger.android.support framework classes." Found Here
So, basically you just pass the Application context when you're creating Dagger Builder you just pass it from Application Class now Once you got it in the Main Component you have the Application context in your all Modules and we need context when we initialize Room Database.
Question 2 Why do we need to create Modules for the Webservice and the Database but not the Repository? Is it possible to annotate the Webservice interface and the Database class themselves to skip creating separate dagger modules for them?
Answer:
First, always try to achieve constructor Injection. The general idea of Modules is "If we don't own the class we can't #annotate its constructor so we make Modules to provide their implementation". also if we want to inject Interface we can achieve it via its implementation class by its constructor Injection.
So we don't own the initialization of WebService and Database that is why we create their Modules and provide them so we can get their instances in our repositories. We own our repository class so we can Inject them via constructor Injection.
Why can't I inject interface types in ViewModel constructors when using Dagger Android?
Here's my AppComponent
#Singleton
#Component(
modules = [
AndroidInjectionModule::class,
AppModule::class,
ActivityBuilder::class,
ViewModelModule::class
]
)
interface AppComponent {
fun inject(app: App)
}
Here's the module for my activities:
#Module
abstract class ActivityBuilder {
#ActivityScope
#ContributesAndroidInjector(modules = [UserDetailsModule::class])
abstract fun userDetailsActivity(): UserDetailsActivity
}
Here's the UserDetailsModule
#Module
abstract class UserDetailsModule {
#Binds
#ActivityScope
abstract fun providesUserRepository(repository: UserRepositoryImpl): UserRepository
}
Here's the ViewModelModule where I follow the dynamic view model factory solution.
#Module
abstract class ViewModelModule {
#Binds
#IntoMap
#ViewModelKey(UserDetailsViewModel::class)
abstract fun userDetailsViewModel(viewModel: UserDetailsViewModel): ViewModel
#Binds
abstract fun bindViewModelFactory(factory: ViewModelFactory): ViewModelProvider.Factory
}
Here's the concrete class of UserRepository
class UserRepositoryImpl #Inject constructor(private val api: Api) : UserRepository { ... }
Here's the UserDetailsViewModel
class UserDetailsViewModel #Inject constructor(private val userRepository: UserRepository) : ViewModel() { ... }
When I compile, it will error
UserRepository cannot be provided without an #Provides-annotated method.
However, the confusing part is when I change UserDetailsViewModel's constructor to receive UserRepositoryImpl instead of type UserRepository, it compiles successfully and it works.
Anyone knows what the problem might be?
Solved the issue. Using a generic view model factory found in this Github issue:
https://github.com/google/dagger/issues/1273#issuecomment-447997439