google / dagger

A fast dependency injector for Android and Java.
https://dagger.dev
Apache License 2.0
17.36k stars 2k forks source link

Problem with Injection of DAO #4244

Open RuralNative opened 5 months ago

RuralNative commented 5 months ago

First off, I will provide the codes for my Room Database, DAO, Repository, DI, ViewModel, and Composable

Database: `package com.ruralnative.handsy.data

import android.content.Context import androidx.room.Room import androidx.room.RoomDatabase import androidx.room.Database import com.ruralnative.handsy.data.dao.AlphabetLessonDao import com.ruralnative.handsy.data.dao.PhrasesLessonDao import com.ruralnative.handsy.data.dao.UserDao import com.ruralnative.handsy.data.entities.AlphabetLesson import com.ruralnative.handsy.data.entities.PhrasesLesson import com.ruralnative.handsy.data.entities.User import dagger.Binds import javax.inject.Inject import javax.inject.Singleton

@Database( entities = [ User::class, AlphabetLesson::class, PhrasesLesson::class ], version = 1, exportSchema = false ) abstract class AppDatabase : RoomDatabase() { abstract fun userDao(): UserDao abstract fun alphabetLessonDao(): AlphabetLessonDao abstract fun phrasesLessonDao(): PhrasesLessonDao }`

UserDAO: `` package com.ruralnative.handsy.data.dao

import androidx.room.Dao import androidx.room.Delete import androidx.room.Insert import androidx.room.OnConflictStrategy import androidx.room.Query import androidx.room.Update import com.ruralnative.handsy.data.entities.PhrasesLesson import com.ruralnative.handsy.data.entities.User import kotlinx.coroutines.flow.Flow

@Dao interface UserDao {

@Query("SELECT * from user")
fun selectAllUsers(): Flow<List<User>>

@Query("SELECT * from user WHERE id = :userID")
fun selectUserById(userID: Int): Flow<User>

@Query("SELECT COUNT(*) from user")
fun countUsers(): Flow<Int>

@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertUser(user: User)

@Update
suspend fun updateUser(user: User)

@Query("UPDATE user SET user_name = :userName WHERE id = :userID")
suspend fun updateUserName(userName: String?, userID: Int)

@Query("UPDATE user SET is_new_user = :boolValue WHERE id = :userID")
suspend fun updateUserStatus(boolValue: Int, userID: Int)

@Query("UPDATE user SET progression_level = :userLevel WHERE id = :userID")
suspend fun updateUserProgressionLevel(userLevel: Int, userID: Int)

@Delete
suspend fun deleteUser(user: User)

} ``

Repository: `package com.ruralnative.handsy.data.repository

import androidx.annotation.WorkerThread import com.ruralnative.handsy.data.dao.UserDao import com.ruralnative.handsy.data.entities.User import com.ruralnative.handsy.di.qualifiers.UserDAO import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.flow import javax.inject.Inject import javax.inject.Singleton

class UserRepository @Inject constructor( private val dao: UserDao ) { val allUsers: Flow<List> = dao.selectAllUsers()

fun getUserByID(userID: Int): Flow<User> {
    return dao.selectUserById(userID)
}

fun isThereNoUser(): Flow<Boolean> = flow {
    val numberOfUsers: Int? = dao.countUsers().firstOrNull()
    var isUserEmpty = true

    if (numberOfUsers == 0) {
        isUserEmpty = true
    } else if (numberOfUsers != 0) {
        isUserEmpty = false
    }
    emit(isUserEmpty)
}

suspend fun insertUser(user: User) {
    dao.insertUser(user)
}

suspend fun insertNewUser(id: Int, name: String) {
    val user = User(
        id,
        name,
        0,
        1
    )
    dao.insertUser(user)
}

suspend fun updateUser(user: User) {
    dao.updateUser(user)
}

suspend fun updateUserNameWithID(userName: String?, userID: Int) {
    dao.updateUserName(userName, userID)
}

suspend fun updateUserStatusWithID(boolValue: Int, userID: Int) {
    dao.updateUserStatus(boolValue, userID)
}

suspend fun updateUserLevelWithID(userLevel: Int, userID: Int) {
    dao.updateUserProgressionLevel(userLevel, userID)
}

suspend fun deleteUser(user: User) {
    dao.deleteUser(user)
}

}`

ViewModel: `package com.ruralnative.handsy.ui.entryUI

import android.util.Log import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.ruralnative.handsy.data.repository.UserRepository import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.delay import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch import javax.inject.Inject

@HiltViewModel class EntryViewModel @Inject constructor( private val repository: UserRepository ): ViewModel() {

fun checkUserCountAndNavigate(
    navigateToInitial: () -> Unit,
    navigateToMain: () -> Unit
) {
    Log.d("ENTRY_SCREEN", "checkUserCountAndNavigate() EXECUTED")
    viewModelScope.launch {
        delay(5000)
        val isThereNoUser: Boolean = repository.isThereNoUser().first()
        if (!isThereNoUser) {
            navigateToInitial()
        } else {
            navigateToMain()
        }
    }
}

}`

DatabaseModule (Hilt): `package com.ruralnative.handsy.di

import android.content.Context import androidx.room.Room import com.ruralnative.handsy.data.AppDatabase import com.ruralnative.handsy.data.dao.AlphabetLessonDao import com.ruralnative.handsy.data.dao.PhrasesLessonDao import com.ruralnative.handsy.data.dao.UserDao import com.ruralnative.handsy.data.repository.AlphabetLessonRepository import com.ruralnative.handsy.data.repository.PhrasesLessonRepository import com.ruralnative.handsy.data.repository.UserRepository import com.ruralnative.handsy.di.qualifiers.AlphabetDAO import com.ruralnative.handsy.di.qualifiers.AlphabetRepo import com.ruralnative.handsy.di.qualifiers.Database import com.ruralnative.handsy.di.qualifiers.PhrasesDAO import com.ruralnative.handsy.di.qualifiers.PhrasesRepo import com.ruralnative.handsy.di.qualifiers.UserDAO import com.ruralnative.handsy.di.qualifiers.UserRepo import dagger.Module import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent import javax.inject.Singleton

@Module @InstallIn(SingletonComponent::class) object DatabaseModule {

@Singleton
@Provides
@Database
fun provideLocalDatabase(
    @ApplicationContext context: Context
): AppDatabase {
    return Room.databaseBuilder(
        context.applicationContext,
        AppDatabase::class.java,
        "app_database.db"
    )
        .fallbackToDestructiveMigration()
        .createFromAsset("database.db")
        .build()
}

@Singleton
@Provides
@UserDAO
fun provideUserDao(
    @Database appDatabase: AppDatabase
): UserDao {
    return appDatabase.userDao()
}

@Singleton
@Provides
@AlphabetDAO
fun provideAlphabetDao(
    @Database appDatabase: AppDatabase
): AlphabetLessonDao {
    return appDatabase.alphabetLessonDao()
}

@Singleton
@Provides
@PhrasesDAO
fun providePhrasesDao(
    @Database appDatabase: AppDatabase
): PhrasesLessonDao {
    return appDatabase.phrasesLessonDao()
}

} `

ViewModelModule: `package com.ruralnative.handsy.di

import com.ruralnative.handsy.data.dao.AlphabetLessonDao import com.ruralnative.handsy.data.dao.PhrasesLessonDao import com.ruralnative.handsy.data.dao.UserDao import com.ruralnative.handsy.data.repository.AlphabetLessonRepository import com.ruralnative.handsy.data.repository.PhrasesLessonRepository import com.ruralnative.handsy.data.repository.UserRepository import com.ruralnative.handsy.di.qualifiers.AlphabetDAO import com.ruralnative.handsy.di.qualifiers.AlphabetRepo import com.ruralnative.handsy.di.qualifiers.PhrasesDAO import com.ruralnative.handsy.di.qualifiers.PhrasesRepo import com.ruralnative.handsy.di.qualifiers.UserDAO import com.ruralnative.handsy.di.qualifiers.UserRepo import dagger.Module import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.android.components.ViewModelComponent import dagger.hilt.android.scopes.ViewModelScoped

@Module @InstallIn(ViewModelComponent::class) object ViewModelModule {

@Provides
@ViewModelScoped
@UserRepo
fun provideUserRepository(
    @UserDAO dao: UserDao
): UserRepository {
    return UserRepository(dao)
}

@Provides
@ViewModelScoped
@AlphabetRepo
fun provideAlphabetRepository(
    @AlphabetDAO dao: AlphabetLessonDao
): AlphabetLessonRepository {
    return AlphabetLessonRepository(dao)
}

@Provides
@ViewModelScoped
@PhrasesRepo
fun providePhrasesRepository(
    @PhrasesDAO dao: PhrasesLessonDao
): PhrasesLessonRepository {
    return PhrasesLessonRepository(dao)
}

}`

Here is the error: `F:\Programming\Handsy\app\build\generated\hilt\component_sources\debug\com\ruralnative\handsy\Application_HiltComponents.java:141: error: [Dagger/MissingBinding] com.ruralnative.handsy.data.dao.UserDao cannot be provided without an @Provides-annotated method. public abstract static class SingletonC implements Application_GeneratedInjector, ^

Missing binding usage: com.ruralnative.handsy.data.dao.UserDao is injected at com.ruralnative.handsy.data.repository.UserRepository(dao) com.ruralnative.handsy.data.repository.UserRepository is injected at com.ruralnative.handsy.ui.initialScreens.UserIntroViewModel(repository) com.ruralnative.handsy.ui.initialScreens.UserIntroViewModel is injected at com.ruralnative.handsy.ui.initialScreens.UserIntroViewModel_HiltModules.BindsModule.binds(vm) @dagger.hilt.android.internal.lifecycle.HiltViewModelMap java.util.Map<java.lang.String,javax.inject.Provider> is requested at dagger.hilt.android.internal.lifecycle.HiltViewModelFactory.ViewModelFactoriesEntryPoint.getHiltViewModelMap() [com.ruralnative.handsy.Application_HiltComponents.SingletonC ? com.ruralnative.handsy.Application_HiltComponents.ActivityRetainedC ? com.ruralnative.handsy.Application_HiltComponents.ViewModelC]`

Already stuck with this error for 3 weeks. I tried everything, nothing at all helped. Please help

Chang-Eric commented 5 months ago

Generally questions like this are better for StackOverflow.

However, I believe the issue is you are providing your UserDAO using a qualifier here:

@Singleton
@Provides
@UserDAO
fun provideUserDao(
    @Database appDatabase: AppDatabase
): UserDao {
    return appDatabase.userDao()
}

but are injecting it without the @UserDao qualifier here:

class UserRepository @Inject constructor(
private val dao: UserDao
) {

How to fix it is a bit up to you. You could either add a qualifier where you use it in UserRepository or you could just remove the qualifier from the @Provides. The reason I mention the latter is that qualifiers are used for differentiating bindings, so it'd usually make sense to use one if you had two different UserDaos, like @Red UserDao and @Blue UserDao. Something like @UserDAO UserDao doesn't really differentiate much, so you might not need the qualifier in the first place if you only plan on having one UserDao binding.