Search code examples
androidkotlinandroid-jetpack-composeandroid-roomdagger-hilt

App crashing when trying to retrieve a RoomDatabase object through Hilt


Kind of a follow-up from this: Trying to connect a view model to a screen using Jetpack Compose for room database

My app crashes when I tried to access a screen that has a view model attached to it, every other screen is working fine.

Here is the error:

java.lang.RuntimeException: Cannot find implementation for com.example.fyp.EntryDatabase. EntryDatabase_Impl does not exist
    at androidx.room.Room.getGeneratedImplementation(Room.kt:58)
    at androidx.room.RoomDatabase$Builder.build(RoomDatabase.kt:1351)
    at com.example.fyp.AppModule.provideDatabase(AppModule.kt:24)
    at com.example.fyp.AppModule_ProvideDatabaseFactory.provideDatabase(AppModule_ProvideDatabaseFactory.java:42)
    at com.example.fyp.DaggerDataBaseApplication_HiltComponents_SingletonC$SingletonCImpl$SwitchingProvider.get(DaggerDataBaseApplication_HiltComponents_SingletonC.java:581)
    at dagger.internal.DoubleCheck.get(DoubleCheck.java:47)
    at com.example.fyp.DaggerDataBaseApplication_HiltComponents_SingletonC$SingletonCImpl$SwitchingProvider.get(DaggerDataBaseApplication_HiltComponents_SingletonC.java:578)
    at dagger.internal.DoubleCheck.get(DoubleCheck.java:47)
    at com.example.fyp.DaggerDataBaseApplication_HiltComponents_SingletonC$SingletonCImpl$SwitchingProvider.get(DaggerDataBaseApplication_HiltComponents_SingletonC.java:575)
    at dagger.internal.DoubleCheck.get(DoubleCheck.java:47)
    at com.example.fyp.DaggerDataBaseApplication_HiltComponents_SingletonC$ViewModelCImpl$SwitchingProvider.get(DaggerDataBaseApplication_HiltComponents_SingletonC.java:441)
    at dagger.hilt.android.internal.lifecycle.HiltViewModelFactory$1.create(HiltViewModelFactory.java:102)
    at androidx.lifecycle.AbstractSavedStateViewModelFactory.create(AbstractSavedStateViewModelFactory.kt:90)
    at dagger.hilt.android.internal.lifecycle.HiltViewModelFactory.create(HiltViewModelFactory.java:114)
    at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.kt:184)
    at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.kt:150)
    at androidx.lifecycle.viewmodel.compose.ViewModelKt.get(ViewModel.kt:215)
    at androidx.lifecycle.viewmodel.compose.ViewModelKt.viewModel(ViewModel.kt:156)
    at com.example.fyp.AddEntryScreenKt.AddJournalScreen(AddEntryScreen.kt:284)
    at com.example.fyp.AppNavigationKt$AppNavigation$1$8.invoke(AppNavigation.kt:54)
    at com.example.fyp.AppNavigationKt$AppNavigation$1$8.invoke(AppNavigation.kt:53)
    at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:139)
    at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:35)
    at androidx.navigation.compose.NavHostKt$NavHost$14$1.invoke(NavHost.kt:308)
    at androidx.navigation.compose.NavHostKt$NavHost$14$1.invoke(NavHost.kt:306)
    at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:109)
    at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:35)
    at androidx.compose.runtime.CompositionLocalKt.CompositionLocalProvider(CompositionLocal.kt:248)
    at androidx.compose.runtime.saveable.SaveableStateHolderImpl.SaveableStateProvider(SaveableStateHolder.kt:84)
    at androidx.navigation.compose.NavBackStackEntryProviderKt.SaveableStateProvider(NavBackStackEntryProvider.kt:65)
    at androidx.navigation.compose.NavBackStackEntryProviderKt.access$SaveableStateProvider(NavBackStackEntryProvider.kt:1)
    at androidx.navigation.compose.NavBackStackEntryProviderKt$LocalOwnersProvider$1.invoke(NavBackStackEntryProvider.kt:52)
    at androidx.navigation.compose.NavBackStackEntryProviderKt$LocalOwnersProvider$1.invoke(NavBackStackEntryProvider.kt:51)
    at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:109)
    at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:35)
    at androidx.compose.runtime.CompositionLocalKt.CompositionLocalProvider(CompositionLocal.kt:228)
    at androidx.navigation.compose.NavBackStackEntryProviderKt.LocalOwnersProvider(NavBackStackEntryProvider.kt:47)
    at androidx.navigation.compose.NavHostKt$NavHost$14.invoke(NavHost.kt:306)
    at androidx.navigation.compose.NavHostKt$NavHost$14.invoke(NavHost.kt:295)
    at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:139)
    at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:35)
    at androidx.compose.animation.AnimatedContentKt$AnimatedContent$6$1$5.invoke(AnimatedContent.kt:755)
    at androidx.compose.animation.AnimatedContentKt$AnimatedContent$6$1$5.invoke(AnimatedContent.kt:744)
    at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:118)
    at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:35)
    at androidx.compose.animation.AnimatedVisibilityKt.AnimatedEnterExitImpl(AnimatedVisibility.kt:818)
    at androidx.compose.animation.AnimatedContentKt$AnimatedContent$6$1.invoke(AnimatedContent.kt:726)
    at androidx.compose.animation.AnimatedContentKt$AnimatedContent$6$1.invoke(AnimatedContent.kt:709)
    at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:109)
    at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:35)
    at androidx.compose.animation.AnimatedContentKt.AnimatedContent(AnimatedContent.kt:768)
    at androidx.navigation.compose.NavHostKt.NavHost(NavHost.kt:273)
    at androidx.navigation.compose.NavHostKt$NavHost$17.invoke(Unknown Source:27)
    at androidx.navigation.compose.NavHostKt$NavHost$17.invoke(Unknown Source:10)
    at androidx.compose.runtime.RecomposeScopeImpl.compose(RecomposeScopeImpl.kt:192)
    at androidx.compose.runtime.ComposerImpl.recomposeToGroupEnd(Composer.kt:2556)
    at androidx.compose.runtime.ComposerImpl.skipCurrentGroup(Composer.kt:2827)
    at androidx.compose.runtime.ComposerImpl.doCompose(Composer.kt:3314)
    at androidx.compose.runtime.ComposerImpl.recompose$runtime_release(Composer.kt:3265)
    at androidx.compose.runtime.CompositionImpl.recompose(Composition.kt:940)
    at androidx.compose.runtime.Recomposer.performRecompose(Recomposer.kt:1155)
    at androidx.compose.runtime.Recomposer.access$performRecompose(Recomposer.kt:127)
    at androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2$1.invoke(Recomposer.kt:583)
    at androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2$1.invoke(Recomposer.kt:551)
    at androidx.compose.ui.platform.AndroidUiFrameClock$withFrameNanos$2$callback$1.doFrame(AndroidUiFrameClock.android.kt:41)
    at androidx.compose.ui.platform.AndroidUiDispatcher.performFrameDispatch(AndroidUiDispatcher.android.kt:109)
    at androidx.compose.ui.platform.AndroidUiDispatcher.access$performFrameDispatch(AndroidUiDispatcher.android.kt:41)
    at androidx.compose.ui.platform.AndroidUiDispatcher$dispatchCallback$1.doFrame(AndroidUiDispatcher.android.kt:69)
    at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1229)
    at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1239)
    at android.view.Choreographer.doCallbacks(Choreographer.java:899)
    at android.view.Choreographer.doFrame(Choreographer.java:827)
    at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:1214)
    at android.os.Handler.handleCallback(Handler.java:942)
    at android.os.Handler.dispatchMessage(Handler.java:99)
    at android.os.Looper.loopOnce(Looper.java:201)
    at android.os.Looper.loop(Looper.java:288)
    at android.app.ActivityThread.main(ActivityThread.java:7872)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:936)
    Suppressed: kotlinx.coroutines.internal.DiagnosticCoroutineContextException: [androidx.compose.runtime.PausableMonotonicFrameClock@d039df3, androidx.compose.ui.platform.MotionDurationScaleImpl@19c7ab0, StandaloneCoroutine{Cancelling}@1220d29, AndroidUiDispatcher@23848ae]

Here is the project gradle:

plugins {
    id("com.android.application") version "8.2.0" apply false
    id("org.jetbrains.kotlin.android") version "1.9.0" apply false
    id("com.google.dagger.hilt.android") version "2.48" apply false
    id("com.google.devtools.ksp") version "1.9.0-1.0.12" apply false
}

module gradle:

plugins {
    id("com.android.application")
    id("org.jetbrains.kotlin.android")
    kotlin("kapt")
    id("com.google.dagger.hilt.android")
    id("com.google.devtools.ksp")
}

android {
    namespace = "com.example.fyp"
    compileSdk = 34

    defaultConfig {
        applicationId = "com.example.fyp"
        minSdk = 26 //Change from 24
        targetSdk = 34
        versionCode = 1
        versionName = "1.0"

        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
        vectorDrawables {
            useSupportLibrary = true
        }
    }

    buildTypes {
        release {
            isMinifyEnabled = false
            proguardFiles(
                getDefaultProguardFile("proguard-android-optimize.txt"),
                "proguard-rules.pro"
            )
        }
    }
    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_1_8
        targetCompatibility = JavaVersion.VERSION_1_8
    }
    kotlinOptions {
        jvmTarget = "1.8"
    }
    buildFeatures {
        compose = true
    }
    composeOptions {
        kotlinCompilerExtensionVersion = "1.5.1"
    }
    packaging {
        resources {
            excludes += "/META-INF/{AL2.0,LGPL2.1}"
        }
    }
}

dependencies {
    implementation("androidx.core:core-ktx:1.12.0")
    implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.7.0")
    implementation("androidx.activity:activity-compose:1.8.2")
    implementation(platform("androidx.compose:compose-bom:2023.08.00"))
    implementation("androidx.compose.ui:ui")
    implementation("androidx.compose.ui:ui-graphics")
    implementation("androidx.compose.ui:ui-tooling-preview")
    implementation("androidx.compose.material:material:1.6.4")
    implementation("androidx.compose.material3:material3-android:1.2.1")
    testImplementation("junit:junit:4.13.2")
    androidTestImplementation("androidx.test.ext:junit:1.1.5")
    androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
    androidTestImplementation(platform("androidx.compose:compose-bom:2023.08.00"))
    androidTestImplementation("androidx.compose.ui:ui-test-junit4")
    debugImplementation("androidx.compose.ui:ui-tooling")
    debugImplementation("androidx.compose.ui:ui-test-manifest")
    val nav_version = "2.7.6"
    implementation("androidx.navigation:navigation-compose:$nav_version")
    implementation("androidx.room:room-ktx:2.6.1") //Room
    implementation("co.yml:ycharts:2.1.0") //YChart (3rd Party)
    implementation("androidx.core:core-splashscreen:1.0.1") //Splash screen

    //Hilt Dependencies
    val daggerHiltVersion = "2.48"
    implementation("com.google.dagger:hilt-android:$daggerHiltVersion")
    ksp("com.google.dagger:hilt-android-compiler:$daggerHiltVersion")
    ksp("androidx.hilt:hilt-compiler:1.1.0")
    implementation("androidx.hilt:hilt-navigation-compose:1.1.0")
}

kapt {
    correctErrorTypes = true
}

view model

@HiltViewModel
class EntryViewModel @Inject constructor(private val repository: DataRepository) : ViewModel() {
    private val _entries = mutableStateOf<List<Entry>>(emptyList())
    val entries: State<List<Entry>> = _entries

    init {
        loadEntryList()
    }

    private fun loadEntryList() {
        viewModelScope.launch {
            _entries.value = repository.getEntryFromDate()
        }
    }

    suspend fun saveEntry(entry: Entry) {
        viewModelScope.launch {
            repository.saveEntry(entry)
        }
    }
}

screen code that has crashed:

@Composable
fun AddJournalScreen(
    navController: NavController,
    entry: Entry? = null,
    viewModel: EntryViewModel = hiltViewModel(),
) {
    //Calendar Date/Time Variables
    val currentDateAndTime = Calendar.getInstance().time //Get current date and time
    val dateFormat = DateFormat.getDateInstance().format(currentDateAndTime) //Format date for the screen
    val timeFormat = DateFormat.getTimeInstance(DateFormat.SHORT).format(currentDateAndTime) //Format time for the screen
    val changeDateToMilis = Calendar.getInstance().timeInMillis //Convert date to Long data type for storing into database

    //Drop Down Menu
    var expandedList by remember { mutableStateOf(false) }
    var selectedItem by remember { mutableStateOf("") }
    var textFiledSize by remember { mutableStateOf(Size.Zero) }
    val icon = if (expandedList) {
        Icons.Filled.KeyboardArrowUp
    } else {
        Icons.Filled.KeyboardArrowDown
    }

    //List of emotions from Plutchik's wheel of emotion
    val emotionList = listOf("ecstasy", "joy", "serenity", "admiration", "trust", "acceptance",
        "terror", "fear", "apprehension", "amazement", "surprise", "distraction", "grief",
        "sadness", "pensiveness", "loathing", "disgust", "boredom", "rage", "anger", "annoyance",
        "vigilance", "anticipation", "interest", "optimism", "love", "submission", "awe", "disapproval",
        "remorse", "contempt", "aggressiveness")

    //For journal input
    var journal by remember {
        mutableStateOf(
            if (entry?.journalEntry == null) {
                ""
            } else {
                entry.journalEntry
            }
        )
    }

    val coroutineScope = rememberCoroutineScope()

    Column(
        modifier = Modifier
            .fillMaxSize()
            .background(Color.White)
    ) {
        Spacer(modifier = Modifier.height(20.dp))
        Column(
            horizontalAlignment = Alignment.CenterHorizontally,
            modifier = Modifier
                .height(200.dp)
                .size(500.dp)
        )
        {
            Text(text = "Date: $dateFormat", fontSize = 20.sp)
            Spacer(
                modifier = Modifier
                    .size(10.dp)
                    .height(10.dp)
            )
            Spacer(
                modifier = Modifier
                    .size(10.dp)
                    .height(10.dp)
            )
            Text(text = "Time: $timeFormat", fontSize = 20.sp, fontFamily = FontFamily.Serif)
            Spacer(
                modifier = Modifier
                    .size(10.dp)
                    .height(10.dp)
            )
            Spacer(
                modifier = Modifier
                    .size(80.dp)
                    .height(80.dp)
            )
        }
        Row(modifier = Modifier
            .width(220.dp)
            .height(100.dp)
        ) {
            Spacer(
                modifier = Modifier
                    .width(10.dp)
            )
            OutlinedTextField(
                value = selectedItem,
                onValueChange = { selectedItem = it },
                modifier = Modifier
                    .width(200.dp)
                    .onGloballyPositioned { coordinates ->
                        textFiledSize = coordinates.size.toSize()
                    },
                label = { Text(text = "Select Emotion") },
                trailingIcon = {
                    Icon(icon, "",
                        Modifier.clickable { expandedList = !expandedList })
                }

            )
            DropdownMenu(
                expanded = expandedList,
                onDismissRequest = { expandedList = false },
                modifier = Modifier
                    .width(with(LocalDensity.current) { textFiledSize.width.toDp() }),
            ) {
                emotionList.forEach { label ->
                    DropdownMenuItem(onClick = {
                        selectedItem = label
                        expandedList = false
                    }) {
                        Text(text = label)
                    }
                }
            }
            Spacer(
                modifier = Modifier
                    .width(30.dp)
            )
        }
        Row(modifier = Modifier
            .width(200.dp)
            .height(20.dp)
        ) {
            Spacer(
                modifier = Modifier
                    .width(15.dp)
            )
            Text(text = "Input your thoughts...", fontStyle = FontStyle.Italic)
        }
        Box(modifier = Modifier
            .size(400.dp)
            .padding(10.dp)
            .align(Alignment.CenterHorizontally)
        ) {
            //var inputText by remember { mutableStateOf("") }
            val maxCharLimit = 200
            TextField(
                value = journal,
                onValueChange = {
                    if (it.length <= maxCharLimit)
                        journal = it
                },
                label = { Text("What makes you feel that way?") },
                singleLine = false,
                minLines = 1,
                maxLines = 15,
                modifier = Modifier.fillMaxWidth()
            )
        }
        Spacer(
            modifier = Modifier
                .size(10.dp)
        )
        Row(
            horizontalArrangement = Arrangement.SpaceEvenly,
            modifier = Modifier.fillMaxWidth(),
        ) {
            Button(
                onClick = { navController.navigate(AppScreens.CalendarScreen.route) },
                colors = ButtonDefaults.buttonColors(
                    contentColor = Color.White,
                    backgroundColor = Color.Red
                ),
            ) {
                Text("Cancel")
            }
            Spacer(
                modifier = Modifier
                    .width(15.dp)
            )
            Button(
                onClick = {
                    coroutineScope.launch {
                        viewModel.saveEntry(
                            Entry(
                                journalEntry = journal,
                                dateEntryInMillis = changeDateToMilis,
                                timeEntry = timeFormat.toString(),
                                emotionEntry = selectedItem
                            )
                        )
                    }

                    navController.navigate(AppScreens.CalendarScreen.route)
                },
                colors = ButtonDefaults.buttonColors(
                    contentColor = Color.White,
                    backgroundColor = ConfirmButtonColor
                ),
            ) {
                Text("Save")
            }
        }
    }
}

Database:

@Database(
    entities = [Entry::class],
    version = 1,
    exportSchema = false
)
abstract class EntryDatabase: RoomDatabase() {
    abstract fun entryDAO(): EntryDAO

}

appmodule

@Module
@InstallIn(SingletonComponent::class)
object AppModule {
    @Provides
    @Singleton
    fun provideDatabase(@ApplicationContext appContext: Context): EntryDatabase {
        return Room.databaseBuilder(
            appContext,
            EntryDatabase::class.java,
            "entry_db"
        ).build()
    }
    @Provides
    @Singleton
    fun provideDao(entryDatabase: EntryDatabase) : EntryDAO {
        return entryDatabase.entryDAO()
    }
    @Provides
    @Singleton
    fun provideRepository(entryDAO: EntryDAO) : DataRepository {
        return DataRepository(entryDAO)
    }
}

database application:

@HiltAndroidApp
class DataBaseApplication: Application()

database repo:

class DataRepository @Inject constructor(private val entryDAO: EntryDAO) {
    suspend fun saveEntry(entry: Entry) {
        return entryDAO.saveEntry(entry)
    }
    suspend fun deleteEntry(entry: Entry) {
        return entryDAO.deleteEntry(entry)
    }
    suspend fun getEntryFromDate(): List<Entry> {
        return entryDAO.getEntryFromDate()
    }
}

I've done some googling and most answers seems to be gradle issues but I couldn't really see much difference beyond using different 'kapt' or 'ksp' and I'm not really sure if its a problem with the gradle or the coding problem with viewmodel/screen itself.

I was hoping to that outside of the app crashing when navigating to the screen, I can save user inputs into the database. There was a prompt for the database to install if I recall correctly so I believe the database is built.


Solution

  • Although the Room documentation is a little bit outdated, it clearly states that you need to use androidx.room:room-compiler with either ksp or kapt.

    If you add this to your module level gradle's dependency block, everything should work:

    ksp("androidx.room:room-compiler:2.6.1")