I am trying two separate path collection query but hilt is throwing up a error of:
[Dagger/DuplicateBindings] com.google.firebase.firestore.CollectionReference is bound multiple times
How should i use two Collection Reference from my Repository to my AppModule?
#Module
#InstallIn(SingletonComponent::class)
object AppModule {
#Provides
fun provideFirebaseAuth(): FirebaseAuth = Firebase.auth
#Provides
fun provideFirebaseFirestore() = Firebase.firestore
#Provides
#Singleton
fun provideProductRef(db: FirebaseFirestore) = db.collection(Products)
#Provides
#Singleton
fun provideCategoriesRef(catDb: FirebaseFirestore) = catDb.collection(Categories)
#Provides
#Singleton
fun provideProductRepository(
productRef: CollectionReference,
cartRef: CollectionReference,
catRef: CollectionReference
): ProductRepository = ProductRepositoryImpl(productRef, cartRef,catRef)
#Provides
#Singleton
fun provideUseCases(productRepo: ProductRepository) = UseCases(
getProducts = GetProducts(productRepo),
addToCart = AddToCart(productRepo),
getCategories = GetCategories(productRepo)
)
My Repository
typealias Products = List<Product>
typealias Categories = List<Category>
typealias ProductResponse = Response<Products>
typealias CategoryResponse = Response<Categories>
typealias AddProductToCartResponse = Response<Boolean>
interface ProductRepository {
fun getProductsFromFirestore(): Flow<ProductResponse>
fun getCategoriesFromFirestore(): Flow<CategoryResponse>
suspend fun addProductToCartCollection(product: Product): AddProductToCartResponse
}
Error
/home/shiva/AndroidStudioProjects/ZuZu/app/build/generated/hilt/component_sources/debug/com/shivaconsulting/zuzu/ZuzuApp_HiltComponents.java:131: error: [Dagger/DuplicateBindings] com.google.firebase.firestore.CollectionReference is bound multiple times: It is also requested at:
com.shivaconsulting.zuzu.di.AppModule.provideProductRepository(productRef, …)
com.shivaconsulting.zuzu.di.AppModule.provideProductRepository(…, catRef)
You are getting the following error:
[Dagger/DuplicateBindings] com.google.firebase.firestore.CollectionReference is bound multiple times
Because of the presence of the following lines of code inside your AppModule class:
#Provides
#Singleton
fun provideProductRef(db: FirebaseFirestore) = db.collection(Products)
#Provides
#Singleton
fun provideCategoriesRef(catDb: FirebaseFirestore) = catDb.collection(Categories)
This means that you trying to create two objects of type CollectionReference, which is actually not possible because Hilt won't know which one to inject. To solve this, you have to differentiate them by naming them differently:
#Provides
#Singleton
#Named("products") //👈
fun provideProductsRef(db: FirebaseFirestore) = db.collection("products")
#Provides
#Singleton
#Named("categories") //👈
fun provideCategoriesRef(db: FirebaseFirestore) = db.collection("categories")
#Provides
#Singleton
#Named("cart") //👈
fun provideCartRef(db: FirebaseFirestore) = db.collection("cart")
Going forward, when you need to create a repository object you have to explicitly specify the names:
#Provides
#Singleton
fun provideProductRepository(
#Named("products") //👈
productsRef: CollectionReference,
#Named("categories") //👈
categoriesRef: CollectionReference,
#Named("cart") //👈
cartRef: CollectionReference
): ProductRepository = ProductRepositoryImpl(productsRef, categoriesRef, cartRef)
Later, when you want to use these three objects inside your repository implementation class you have to use:
#Singleton
class ProductRepositoryImpl #Inject constructor(
#Named("products") //👈
private val productsRef: CollectionReference,
#Named("categories") //👈
private val categoriesRef: CollectionReference,
#Named("cart") //👈
private val cartRef: CollectionReference
): ProductRepository {
//...
}
Related
Base app architecture for Android client-server app is:
UI (Activity + Fragment or Jetpack Compose)
ViewModel (also is part of UI actually)
Data (repositories to get data from database (Room) and API (Retrofit))
Hilt has different Components and Scopes for Android https://dagger.dev/hilt/components.html
For my base app architecture I have set DI using Hilt in the following way:
Database
#InstallIn(SingletonComponent::class)
#Module
object DatabaseModule {
#Provides
#Singleton // only this one is marked as #Singleton to get the same instance all the time (logically for Database...)
fun provideDataBase(#ApplicationContext context: Context): AppDataBase {
return AppDataBase.getInstance(context)
}
#Provides
fun provideUsersDao(dataBase: AppDataBase): UsersDao = dataBase.getUsersDao()
}
Network (Datasource)
#InstallIn(SingletonComponent::class)
#Module
object NetworkModule {
#Provides
#Singleton // also as Room instance, it should return the same instance of http client
fun providesOkHttpClient(
httpLoggingInterceptor: HttpLoggingInterceptor,
...
): OkHttpClient = OkHttpClient.Builder()
.addInterceptor(headerInterceptor)
...
.build()
#Provides
#Singleton
fun provideRetrofit(
gson: Gson,
client: OkHttpClient,
queryConverterFactory: Converter.Factory
): Retrofit = Retrofit.Builder()
...
.client(client)
.build()
//... some other stuff needed to setup OkHttp and Retrofit
#Provides
fun provideUserApi(retrofit: Retrofit): UserApi = retrofit.create(UserApi::class.java)
#Provides
fun provideUserDataSource(
userApi: UserApi,
networkErrorConverterHelper: NetworkErrorConverterHelper
): UserDataSource = UserDataSourceImpl(userApi, networkErrorConverterHelper)
}
Repository
#InstallIn(SingletonComponent::class)
#Module
interface RepositoryModule {
#Binds
fun bindUserRepositoryImpl(impl: UserRepositoryImpl): UserRepository
}
class UserRepositoryImpl #Inject constructor(
private val userDataSource: UserDataSource,
private val userDbManager: UserDbManager
) : UserRepository {
override fun observeCachedUsers(): Flow<List<User>> ...
override fun fetchUsers(): Flow<Resource<Unit>> = ...
}
ViewModel
#HiltViewModel
class UsersViewModel #Inject constructor(
private val userRepository: UserRepository,
) : ViewModel() {
...
}
Activity
#AndroidEntryPoint
class UsersActivity : AppCompatActivity() {
val userViewModel: UserViewModel by viewModels()
...
}
So basically everywhere I have used SingletonComponent for Hilt modules and #Singleton annotation for some providers so it would return the same instance (of Database, Http client)
I'm trying to understand how good (correct) all of that and in which cases I could use other components/scopes based on this sample.
When app has only one activity and uses Fragments or Jetpack Compose to implement all screens then I guess I can use ActivityRetainedComponent instead of SingletonComponent for data modules, in this case Hilt releases all instances in memory when a user presses the back button to exit the app (activity is destroyed), in case of SingletonComponent these instances live until process of the app dies, so basically difference in this case is not big but still...
I'm trying to inject the viewmodel to the Fragment using hilt, for that I created two modules one for my network component and another for the characters viewmodel.
The network module is installed in SingletonComponent and I need that the characters module installs in the FragmentComponent to get the Viewmodel through "by viewmodels()"
My activity and my fragment are annotated with "#AndroidEntryPoint" and my Application is annotated with "#HiltAndroidApp" my modules are:
#Module
#InstallIn(SingletonComponent::class)
class NetworkModule {
#Singleton
#Provides
fun provideLoggingInterceptor(): HttpLoggingInterceptor {
val loggingInterceptor = HttpLoggingInterceptor()
loggingInterceptor.level = HttpLoggingInterceptor.Level.BODY
return loggingInterceptor
}
#Singleton
#Provides
fun provideAuthorizationInterceptor(): AuthorizationInterceptor =
AuthorizationInterceptor()
#Singleton
#Provides
fun provideInterceptors(
loggingInterceptor: HttpLoggingInterceptor,
authorizationInterceptor: AuthorizationInterceptor
): HttpInterceptors = HttpInterceptors(
listOf(
loggingInterceptor,
authorizationInterceptor
)
)
#Singleton
#Provides
fun provideTimeouts(): HttpTimeouts = HttpTimeouts()
#Singleton
#Provides
fun provideHttpClient(
timeouts: HttpTimeouts,
interceptors: HttpInterceptors
): HttpBuilderFactory = HttpClientBuilderFactory(timeouts, interceptors)
#Singleton
#Provides
fun provideGsonConverter(): GsonFactory = GsonBuilderFactory()
#Singleton
#Provides
fun provideHttpServiceProvider(
httpBuilderFactory: HttpBuilderFactory,
gsonFactory: GsonFactory
): HttpProvider = HttpServiceProvider(httpBuilderFactory, gsonFactory)
}
#Module
#InstallIn(FragmentComponent::class)
class CharacterListModule {
#Singleton
#Provides
fun provideCharacterMapper(): Mapper<CharacterDataContainer, PaginatedCharacters> =
PaginatedCharactersMapper()
#Singleton
#Provides
fun provideCharacterServices(
httpServiceProvider: HttpProvider
): CharacterServices = httpServiceProvider.createService(CharacterServices::class.java)
#Singleton
#Provides
fun provideCharacterListRemoteSource(
characterServices: CharacterServices,
characterMapper: Mapper<CharacterDataContainer, PaginatedCharacters>
): CharacterListSource = CharacterListRemoteSourceImpl(
characterServices,
characterMapper
)
#Singleton
#Provides
fun provideCharacterListRepository(
characterListRemoteSource: CharacterListSource
): CharacterListRepository = CharacterListRepositoryImpl(
characterListRemoteSource
)
#Singleton
#Provides
fun provideCoroutineDispatcher() = Dispatchers.IO
#Singleton
#Provides
fun provideCharacterListUseCase(
coroutineDispatcher: CoroutineDispatcher,
characterListRepository: CharacterListRepository
) = CharacterListUseCase(
coroutineDispatcher,
characterListRepository
)
#Singleton
#Provides
fun provideCharacterUIMapper(): Mapper<Character, CharacterUI> = CharacterUIMapper()
}
and my viewmodel is:
#HiltViewModel
class CharacterListViewModel #Inject constructor(
private val characterListUseCase: CharacterListUseCase,
private val characterUIMapper: Mapper<Character, CharacterUI>
) : ViewModel() {
Also when I use SingletonComponent in both cases the application runs fine, but when try to use FragmentComponent fails:
Finally my dependencies are:
object Hilt {
internal object Versions {
const val hilt = "2.33-beta"
const val hiltViewModel = "1.0.0-alpha01"
}
const val hilt = "com.google.dagger:hilt-android:${Versions.hilt}"
const val hiltCompiler = "com.google.dagger:hilt-android-compiler:${Versions.hilt}"
const val hiltViewModel = "androidx.hilt:hilt-lifecycle-viewmodel:${Versions.hiltViewModel}"
const val hiltViewModelCompiler = "androidx.hilt:hilt-compiler:${Versions.hiltViewModel}"
}
As it names says "FragmentComponent"(s) only live as long as it fragments does. You can't inject something that is fragment scoped into a view model, because viewmodels outlive its fragments lifecycle.. Change "FragmentScoped" with "SingletonScoped".
Please read the official documentation first before working with scopes. 99% of the time, using a "SingletonComponent" is more than enough
I'm migrating from Dagger2 to Hilt however I'm unable to solve this part:
#Module
class HistoryPurchaseModule(private val historyPurchaseFragment: HistoryPurchaseFragment) {
#Provides
fun provideHistoryPurchaseFragment(): HistoryPurchaseFragment = historyPurchaseFragment
#Provides
fun provideUseCase() = HistoryPurchaseUseCase()
#Provides
fun provideSession() = HistoryPurchaseSession(
isHistoryLoading = MutableLiveData(true),
isThisTheFirstTime = true,
isHistoryEmpty = MutableLiveData(false)
)
#Provides
fun provideViewModel(session: HistoryPurchaseSession): HistoryPurchaseViewModel {
return historyPurchaseFragment.createViewModel {
HistoryPurchaseViewModel(session)
}
}
#Provides
fun provideLogic(
useCases: HistoryPurchaseUseCase,
viewModel: HistoryPurchaseViewModel
) = HistoryPurchaseLogic(viewModel.viewModelScope.coroutineContext, useCases, historyPurchaseFragment, viewModel)
}
My logic class takes two interfaces in the constructor that the viewModel(HistoryPurchaseContract.ViewModel) and fragment(HistoryPurchaseContract.View) implement.
Fragment:
class HistoryPurchaseFragment : BaseFragment<HistoryPurchaseEvent>(), HistoryPurchaseContract.View
ViewModel:
class HistoryPurchaseViewModel #Inject constructor(override var session: HistoryPurchaseSession) : BaseViewModel<HistoryPurchaseEvent>(),
HistoryPurchaseContract.ViewModel
I managed to pass the viewModel however I'm unable to pass the fragment.
Is there a way I can do it?
I have 3 repositories:
interface MainRepository {
...
}
interface LocalRepository {
...
}
interface WebRepository {
...
}
Each Repository has it's own implementation:
#Singleton
class MainRepositoryImpl #Inject constructor(
private val localRepository: LocalRepository,
private val webRepository: WebRepository
) : MainRepository {
...
}
#Singleton
class LocalRepositoryImpl #Inject constructor(
private val localMapper: LocalMapper
private val popularMovieDao: PopularMovieDao
) : LocalRepository {
...
}
#Singleton
class WebRepositoryImpl #Inject constructor(
private val webMapper: WebMapper,
private val popularMovieApi: PopularMovieApi
) : WebRepository {
...
}
As you can see, MainRepository needs to have both other repositories injected into it, however,I can't really figure out how to do it.
Of course I can inject it with type of LocalRepositoryImpl or WebRepositoryImpl but I want to inject it with type of LocalRepository or WebRepository for more generalized approach.
Here is the module that I tried writing:
#InstallIn(ApplicationComponent::class)
#Module
object Module {
#Singleton
#Provides
fun provideWebRepository(): WebRepository {
return WebRepositoryImpl(mapper = WebMapper(), popularMovieApi = PopularMovieApi.getInstance())
}
#Singleton
#Provides
fun provideLocalRepository(): LocalRepository {
return LocalRepositoryImpl(mapper = LocalMapper(), // Here I can't really
// figure out how to get #Dao since it requires DB
// which requires context and etc
// which makes me think that I've got completely wrong approach to this)
}
}
My Module of LocalData:
#InstallIn(ApplicationComponent::class)
#Module
object LocalDataSourceModule {
#Singleton
#Provides
fun provideMainDatabase(#ApplicationContext context: Context): MainDatabase = MainDatabase.getInstance(context)
#Provides
fun providePopularMovieDao(mainDatabase: MainDatabase): PopularMovieDao = mainDatabase.popularMovieDao()
}
My Module of WebData:
#InstallIn(ApplicationComponent::class)
#Module
object RemoteDataSourceModule {
#Singleton
#Provides
fun providePopularMovieApi(): PopularMovieApi = PopularMovieApi.getInstance()
}
How do I properly Inject Implementations that I have (LocalRepositoryImpl & WebRepositoryImpl) while maintaining types of interfaces(LocalRepository & `WebRepository)??
Use #Binds. Instead of your object Module use following module:
#InstallIn(ApplicationComponent::class)
#Module
interface Module {
#Binds
fun bindWebRepository(repository: WebRepositoryImpl): WebRepository
#Binds
fun bindLocalRepository(repository: LocalRepositoryImpl): LocalRepository
}
It tells Dagger that if you need WebRepository dependency then it must provide WebRepositoryImpl and the same for LocalRepository and LocalRepositoryImpl.
What is the use case for #Binds vs #Provides annotation in Dagger2
Your Repositories
interface MainRepository {
...
}
interface LocalRepository {
...
}
interface WebRepository {
...
}
Implementation (No #Inject or #Singleton here!)
class MainRepositoryImpl constructor(
private val localRepository: LocalRepository,
private val webRepository: WebRepository
) : MainRepository {
...
}
class LocalRepositoryImpl constructor(
private val localMapper: LocalMapper
private val popularMovieDao: PopularMovieDao
) : LocalRepository {
...
}
class WebRepositoryImpl constructor(
private val webMapper: WebMapper,
private val popularMovieApi: PopularMovieApi
) : WebRepository {
...
}
Di.Module (Repository Module)
#Module
#InstallIn(ApplicationComponent::class)
object RepositoryModule {
#Singleton
#Provides
fun provideMainRepository(
localRepository: LocalRepository,
webRepository: WebRepository
): MainRepository = MainRepositoryImpl(localRepository, webRepository)
#Singleton
#Provides
fun provideLocalRepository(
localMapper: LocalMapper,
popularMovieDao: PopularMovieDao
): LocalRepository = LocalRepositoryImpl(localMapper, popularMovieDao)
#Singleton
#Provides
fun provideWebRepository(
webMapper: WebMapper,
popularMovieApi: PopularMovieApi
): WebRepository = WebRepositoryImpl(webMapper, popularMovieApi)
Try this and tell me if it worked. Since you have provided all your Repositories with #Provides, Dagger-Hilt knows how to create them. Are you using Room for your localDatabase? If yes, then creating the Database like you did might not be right. If you're not using Room that you should start to, as it makes your life easier.
Here is the right way to create a room database with dagger hilt:
Entity Module
#Entity(tableName = "exampleTableName")
data class ExampleEntity(
#PrimaryKey(autoGenerate = true)
val id: Int,
// ... whatever you need
val header: String = "",
val title: String = "",
val description: String = "",
)
ExampleDatabase
#Database(entities = [ExampleEntity::class], version = 1)
abstract class ExampleDatabase : RoomDatabase() {
abstract fun exampleDao(): ExampleDao
companion object {
const val DATABASE_NAME = "example_db"
}
}
DAO
#Dao
interface DocumentDao {
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(exampleEntity: List<ExampleEntity>)
#Query("SELECT * FROM exampleTableName")
suspend fun getList(): List<ExampleEntity>
}
Room Di.Module
#Module
#InstallIn(ApplicationComponent::class)
object RoomModule {
#Singleton
#Provides
fun provideExampleDB(#ApplicationContext context: Context): ExampleDatabase = Room.databaseBuilder(
context,
ExampleDatabase::class.java,
ExampleDatabase.DATABASE_NAME,
).fallbackToDestructiveMigration().build()
#Singleton
#Provides
fun provideExampleDAO(exampleDatabase: ExampleDatabase): ExampleDao = exampleDatabase.exampleDao()
}
I been refactoring an app to Kotlin and currently I have been facing a weird error from Dagger. Im trying to implement a MVVM design but im hard stuck with the dagger error.
AppModule
#Module
class AppModule(val app: App) {
companion object {
private var INSTANCE: RecorderisDB? = null
private fun getInstance(context: Context): RecorderisDB?{
if (INSTANCE == null) {
synchronized(RecorderisDB::class){
INSTANCE = Room.databaseBuilder(context.applicationContext,
RecorderisDB::class.java,
"recorderis.db")
.build()
}
}
return INSTANCE
}
fun destroyInstance(){
INSTANCE = null
}
}
#Provides #Singleton
fun provideApp() = app
#Provides #Singleton #Nullable
fun getDB(context: Context): RecorderisDB? = getInstance(context)
#Provides #Singleton
fun provideDateVM(db: RecorderisDB): DateViewModel {
return DateViewModel(db)
}
AppComponent
#Singleton
#Component(modules = [(AppModule::class)])
interface AppComponent {
fun inject(app: App)
fun inject(form: Form)
}
DateViewModel
class DateViewModel #Inject constructor(val dB: RecorderisDB){
fun createDate(name: String, symbol: String, date: String): Completable {
return Completable.fromAction{ dB.getDateDao().newDate(Date(name, symbol, date))}
}
Form.kt
class Form : AppCompatActivity() {
#Inject
lateinit var dateVM: DateViewModel
public override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_form)
App.graph.inject(this)
initDialog()
setUpRecyclerView()
}
Stacktrace Log
English is not my first language but this error i think is being contradictory? Is telling me that my DB is not nullable BUT is being provided? Basically what i Have i my companion object inside the AppModule.
15:27:21.882 [ERROR] [org.gradle.api.Task] e: C:\Users\diego\Apps\Recorderis\app\build\tmp\kapt3\stubs\debug\tech\destinum\recorderis\DI\AppComponent.java:13:
error: [Dagger/Nullable] tech.destinum.recorderis.Data.RecorderisDB is not nullable,
but is being provided by #org.jetbrains.annotations.Nullable #Singleton
#Provides tech.destinum.recorderis.Data.RecorderisDB
tech.destinum.recorderis.DI.AppModule.getDB(android.content.Context)
public abstract void inject(#org.jetbrains.annotations.NotNull()
^
tech.destinum.recorderis.Data.RecorderisDB is injected at
tech.destinum.recorderis.DI.AppModule.provideDateVM(db)
tech.destinum.recorderis.Data.ViewModels.DateViewModel is injected at
tech.destinum.recorderis.activities.Form.dateVM
tech.destinum.recorderis.activities.Form is injected at
tech.destinum.recorderis.DI.AppComponent.inject(tech.destinum.recorderis.activities.Form)
Well it specifically says that the problem is that you are injecting RecorderisDB, even though you are providing RecorderisDB?.
The solution? Dagger already handles the double-checked locking for you just by using #Singleton #Provides. There is no need for that code at all.
#Module
class AppModule(val app: App) {
#Provides
fun provideApp() = app
#Provides #Singleton
fun getDB(context: App): RecorderisDB = Room.databaseBuilder(context.applicationContext,
RecorderisDB::class.java,
"recorderis.db")
.build()
#Provides
// #Singleton // are you CERTAIN this is singleton?
fun provideDateVM(db: RecorderisDB): DateViewModel {
return DateViewModel(db)
}
}