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.
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")