androidkotlinandroid-roomdagger-hiltandroid-mvvm

How to implement RoomDataBase in An Activity using Dagger Hilt Injection in View Model


I'm trying to make an app that can enter and store Bible Verses. I'm using Dagger Hilt to inject my Daos into my View Model . My App keeps crashing whenever I try to use viewModel to get my state and onEvent objects to pass to my "verseScreen() composable" in my MainActivity as shown below but I've looked at the code and everything seems fine but it keeps crashing, Error in Logcat is "Cannot create instance of viewModel ---> In this case versesViewModel" but shouldn't @AndroidEntryPoint take care of that or what I'm I missing or doing wrong?


@AndroidEntryPoint
class MainActivity : ComponentActivity() {


  private val viewModel: versesViewModel by viewModels()


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            SwordTheme {



                 Log.d("VIEW THIS","${viewModel.hashCode()}")

                 val state by viewModel.state.collectAsState()

                Log.d("STATE HERE","${state.hashCode()}")



                //verseScreen depends on states and onEvent to display dta
                verseScreen(state = state, onEvents = viewModel::onEvent)

            }
        }
    }
}

My ViewModel class:


@HiltViewModel
class versesViewModel @Inject constructor(

     val thisRepository: dataBaseRepository

): ViewModel() {

 // sortType is an enum containing 3 constants "byBOOK, byDATE, byTHEME"

    private val _sortType = MutableStateFlow(sortType.byBOOK)

     
     //SELECTING A DAO BASED ON THE SORTTYPE
    private val _verses = _sortType.flatMapLatest { sorttype ->

         when(sorttype){

             sortType.byBOOK -> thisRepository.orderByBook()
             sortType.byDATE -> thisRepository.orderByDate()
             sortType.byTHEME -> thisRepository.orderByTheme()
         }
     }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), emptyList())

 //verseState is a Data class to keep my states
    private val _state = MutableStateFlow(verseState())

 //COMBINING THE ABOVE FLOWS, To be used outside this View Model
    val  state = combine(_state,_sortType,_verses){ state, sortType, myverses ->

        state.copy(
            sortType = sortType,  
            verses = myverses

        )


    }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), verseState())

 

  //DEFINING THE EVENTS
   //appEvents is a sealed interface containing all my possible Events

       fun onEvent(event: appEvents){
          when(event) {

      

              is appEvents.deleteVerse -> {
                  viewModelScope.launch { thisRepository.deleteVerse(event.verse) }

              }

              is appEvents.enterVerse -> { _state.update { it.copy( verse = event.verse) } }

              appEvents.hideDialog -> { _state.update { it.copy( isAddingVerse = false) }  }

              appEvents.saveVerse -> {

                  val verseNum = state.value.verseNum
                  val chapter = state.value.chapter
                  val bookNum = state.value.bookNum
                  val bookName = state.value.bookName
                  val verse = state.value.verse
                  val theme = state.value.theme
                  val date: Long = System.currentTimeMillis()


                  if (bookName.isBlank() || verse.isBlank())
                      return

                  val thisVerse = myVerses(
                      verseNum = verseNum,
                      bookName = bookName,
                      bookNum = bookNum,
                      theme = theme,
                      date = date,
                      verse = verse,
                      chapter = chapter)

                  viewModelScope.launch { thisRepository.addVerse(thisVerse) }

                  _state.update { it.copy(
                      isAddingVerse = false,
                      verse = "",
                      theme = myThemes.NO_THEME,
                      bookName = "",
                      chapter = "",
                      verseNum = "",
                      bookNum = ""
                      
                  ) }
              }

              is appEvents.setBookNum -> { _state.update {it.copy( bookNum = event.bookNum)} }

              is appEvents.setBookName -> { _state.update {it.copy( bookName = event.bookName)} }

              is appEvents.setChapter -> { _state.update {it.copy( chapter = event.chapter)} }

              is appEvents.setVerseNum -> { _state.update {it.copy( verseNum = event.verseNum)} }

              appEvents.showDialog -> { _state.update { it.copy( isAddingVerse = true) } }

              is appEvents.sortVerses -> { _sortType.value = event.sortType}

              is appEvents.enterTheme -> { _state.update { it.copy( theme = event.theme) } }
          }
      }


}

MY APP MODULE:


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


    @Provides
    @Singleton
    fun mydatabase(appContext: myApp) =
            Room.databaseBuilder(
            appContext,
            myDataBase::class.java,
            "myVerses.DB"
        ).build()


    @Singleton
    @Provides
    fun provideDao(db: myDataBase) = db.myDao()

}

MY DATABASE CLASS:


@Database( entities = [myVerses::class],
           version = 1)

abstract class myDataBase:RoomDatabase() {

    abstract fun myDao(): myFunctions //myFunctions is an Interface containing my Room Daos
}

MY DATABASE REPOSITORY CLASS:


class dataBaseRepository @Inject constructor(

    val myfuns : myFunctions
) {

    suspend fun addVerse(verse: myVerses) = myfuns.addVerse(verse)

    suspend fun deleteVerse(verse: myVerses) = myfuns.deleteVerse(verse)

    fun orderByBook() = myfuns.orderByBook()
    fun orderByDate() = myfuns.orderByDate()
    fun orderByTheme() = myfuns.orderByTheme()


}



LOGCAT ERRORS(SCROLL RIGHT):


FATAL EXCEPTION: main
                                                                                                    Process: com.example.Sword, PID: 27103
                                                                                                    java.lang.RuntimeException: Cannot create an instance of class com.example.Sword.versesViewModel
                                                                                                        at androidx.lifecycle.ViewModelProvider$NewInstanceFactory.create(ViewModelProvider.kt:204)
                                                                                                        at androidx.lifecycle.ViewModelProvider$AndroidViewModelFactory.create(ViewModelProvider.kt:322)
                                                                                                        at androidx.lifecycle.ViewModelProvider$AndroidViewModelFactory.create(ViewModelProvider.kt:304)
                                                                                                        at androidx.lifecycle.ViewModelProvider$AndroidViewModelFactory.create(ViewModelProvider.kt:278)
                                                                                                        at androidx.lifecycle.SavedStateViewModelFactory.create(SavedStateViewModelFactory.kt:128)
                                                                                                        at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.kt:187)
                                                                                                        at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.kt:153)
                                                                                                        at androidx.lifecycle.ViewModelLazy.getValue(ViewModelLazy.kt:53)
                                                                                                        at androidx.lifecycle.ViewModelLazy.getValue(ViewModelLazy.kt:35)
                                                                                                        at com.example.Sword.MainActivity.getViewModel(MainActivity.kt:32)
                                                                                                        at com.example.Sword.MainActivity.access$getViewModel(MainActivity.kt:29)
                                                                                                        at com.example.Sword.MainActivity$onCreate$1$1.invoke(MainActivity.kt:44)
                                                                                                        at com.example.Sword.MainActivity$onCreate$1$1.invoke(MainActivity.kt:38)
                                                                                                        at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:107)
                                                                                                        at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:34)
                                                                                                        at androidx.compose.runtime.CompositionLocalKt.CompositionLocalProvider(CompositionLocal.kt:228)
                                                                                                        at androidx.compose.material3.TextKt.ProvideTextStyle(Text.kt:261)
                                                                                                        at androidx.compose.material3.MaterialThemeKt$MaterialTheme$1.invoke(MaterialTheme.kt:81)
                                                                                                        at androidx.compose.material3.MaterialThemeKt$MaterialTheme$1.invoke(MaterialTheme.kt:80)
                                                                                                        at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:107)
                                                                                                        at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:34)
                                                                                                        at androidx.compose.runtime.CompositionLocalKt.CompositionLocalProvider(CompositionLocal.kt:228)
                                                                                                        at androidx.compose.material3.MaterialThemeKt.MaterialTheme(MaterialTheme.kt:73)
                                                                                                        at com.example.Sword.ui.theme.ThemeKt.SwordTheme(Theme.kt:65)
                                                                                                        at com.example.Sword.MainActivity$onCreate$1.invoke(MainActivity.kt:38)
                                                                                                        at com.example.Sword.MainActivity$onCreate$1.invoke(MainActivity.kt:37)

DEPENDECIES:


   //FOR ROOM
    implementation "androidx.room:room-ktx:2.5.2"
    kapt "androidx.room:room-compiler:2.5.2"

    //Dagger Hilt
    implementation 'com.google.dagger:dagger:2.48.1'
    kapt 'com.google.dagger:dagger-compiler:2.48.1'
    implementation "androidx.hilt:hilt-lifecycle-viewmodel:1.0.0-alpha03"
    kapt "androidx.hilt:hilt-compiler:1.0.0"

I started Learning about injection using Dagger Hilt , 2 days ago so I haven't tried a'lot of options just a few edits here and there but nothing Worked.


Solution

  • After a'lot of suffering and praying, Turns out it was an easy fix:

    MY APPLICATION CLASS BEFORE FIX:

    
    @HiltAndroidApp
    class myApp: Application() {
    }
    
    

    AFTER FIX:

    
    
    @Module
    @InstallIn(SingletonComponent::class)
    @HiltAndroidApp
    class myApp: Application() {
    }
    
    /*Note! it must be registered in manifest (<application 
    
                                                    name = ".myApp" ) */
    
    

    USING myApp() CONTEXT TO BUILD ROOM IN HILT MODULE:

    @Module
    @InstallIn(SingletonComponent::class)
    object appModule {
    
    
        
        @Provides
        @Singleton
        fun mydatabase(@ApplicationContext context: Context) =
    
    /* For some reason @ApplicationContext worked and not passing an instance of  
           myApp() e.g fun mydatabase(context: myApp) */
    
                Room.databaseBuilder(
                context,
                myDataBase::class.java,
                "myVerses.DB"
            ).build()
    
    
        @Singleton
        @Provides
        fun provideDao(db: myDataBase) = db.myDao()
    
    
    
    }
    
    
    

    MY MAINACTIVITY:

    
    @AndroidEntryPoint
    class MainActivity : ComponentActivity() {
    
    
        private val viewModel: versesViewModel by viewModels()
    
        override fun onCreate(savedInstanceState: Bundle?) {
    
            super.onCreate(savedInstanceState)
            setContent {
                SwordTheme {
    
                    Log.d("VIEW THIS","${viewModel.hashCode()}")
    
                     val state by viewModel.state.collectAsState()
    
                    Log.d("STATE HERE","${state.hashCode()}")
    
    
    
                    // A surface container using the 'background' color from the theme
                    verseScreen(state = state, onEvents = viewModel::onEvent)
    
                }
            }
        }
    }
    
    

    IN build.gradle(app) :

    
    plugins {
        id 'com.android.application'
        id 'org.jetbrains.kotlin.android'
        id 'kotlin-kapt'
        id 'dagger.hilt.android.plugin'
    }
    
    
    dependencies {
    
        // ViewModel Compose
        implementation "androidx.lifecycle:lifecycle-viewmodel-compose:2.4.1"
    
         //FOR ROOM
        implementation "androidx.room:room-ktx:2.5.2"
        kapt "androidx.room:room-compiler:2.5.2"
    
        //Dagger - Hilt
        implementation "com.google.dagger:hilt-android:2.40.5"
        kapt "com.google.dagger:hilt-android-compiler:2.40.5"
        implementation "androidx.hilt:hilt-lifecycle-viewmodel:1.0.0-alpha03"
        kapt "androidx.hilt:hilt-compiler:1.0.0"
        implementation 'androidx.hilt:hilt-navigation-compose:1.0.0'
        //
    
    
    }
    
    hilt {
        enableAggregatingTask = false
    }
    
    kapt {
        correctErrorTypes true
    }
    
    

    IN build.gradle(Project) :

    
    buildscript {
       
        dependencies {
            classpath "com.google.dagger:hilt-android-gradle-plugin:2.40.5"
        }
    }