I found a solution please do not read on
I am creating an app with Hilt which stores some data using Room.
My ViewModel:
@HiltViewModel
class NewScreenViewModel @Inject constructor(private val repository: QuickPresetsRepository) : ViewModel() {
private val _state = mutableStateOf(
NewScreenState(
quickPresets = emptyList()
)
)
private val textValidator = TextValidator()
val state: State<NewScreenState>
get() = _state
init {
getQuickPresets()
}
private fun getQuickPresets() {
viewModelScope.launch {
val quickPresets = repository.getQuickPresets()
_state.value = _state.value.copy(
quickPresets = quickPresets
)
}
}
}
Repo:
@Singleton
class QuickPresetsRepository @Inject constructor(private val dao: QuickPresetsDao) {
suspend fun getQuickPresets(): List<QuickPreset> {
return withContext(Dispatchers.IO) {
return@withContext dao.getAll()
}
}
}
Module:
@Module
@InstallIn(SingletonComponent::class)
object QuickPresetsModule {
@Provides
fun provideRoomDao(database: QuickPresetsDb): QuickPresetsDao {
return database.dao
}
@Singleton
@Provides
fun provideRoomDatabase(app: Application): QuickPresetsDb {
return QuickPresetsDb.getInstance(app)
}
}
Dao:
@Dao
interface QuickPresetsDao {
@Query("SELECT * FROM quickpresets")
suspend fun getAll(): List<QuickPreset>
@Insert
suspend fun insert(quickPreset: QuickPreset)
}
Db:
@Database(
entities = [QuickPreset::class],
version = 1,
)
abstract class QuickPresetsDb : RoomDatabase() {
abstract val dao: QuickPresetsDao
companion object {
@Volatile
private var INSTANCE: QuickPresetsDb? = null
fun getInstance(app: Application): QuickPresetsDb {
return INSTANCE ?: synchronized(this) {
INSTANCE ?: buildDatabase(app).also {
INSTANCE = it
}
}
}
private fun buildDatabase(app: Application) =
Room.databaseBuilder(app, QuickPresetsDb::class.java, "quickpresets_db")
.addCallback(
object : Callback() {
override fun onCreate(db: SupportSQLiteDatabase) {
super.onCreate(db)
CoroutineScope(Dispatchers.IO).launch {
prePopulateDb(getInstance(app).dao)
}
}
}
)
.build()
private suspend fun prePopulateDb(dao: QuickPresetsDao) {
dao.insert(
QuickPreset(5, 5)
)
}
}
}
Model:
@Entity(tableName = "quickpresets")
data class QuickPreset(
@ColumnInfo(name = "qp_width")
val width: Int,
@ColumnInfo(name = "qp_height")
val height: Int
) {
@PrimaryKey(autoGenerate = true) var id: Int = 0
}
MainActivity:
composable(route = "new") {
val viewModel: NewScreenViewModel = hiltViewModel()
val state = viewModel.state.value
NewScreen(
viewModel,
state
) { width, height ->
navController.currentBackStackEntry?.arguments?.apply {
putInt("width", width)
putInt("height", height)
}
navController.navigate("draw")
}
}
The problem is, whenever I run the app, I keep getting this exception:
java.lang.RuntimeException: Cannot create an instance of class com.therealbluepandabear.pixapencil.screens.new.NewScreenViewModel
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 dagger.hilt.android.internal.lifecycle.HiltViewModelFactory.create(HiltViewModelFactory.java:118)
at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.kt:187)
at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.kt:153)
at androidx.lifecycle.viewmodel.compose.ViewModelKt.get(ViewModel.kt:215)
at androidx.lifecycle.viewmodel.compose.ViewModelKt.get$default(ViewModel.kt:195)
at androidx.lifecycle.viewmodel.compose.ViewModelKt.viewModel(ViewModel.kt:120)
at com.therealbluepandabear.pixapencil.activities.main.MainActivity$PixaPencil$1$2.invoke(MainActivity.kt:208)
at com.therealbluepandabear.pixapencil.activities.main.MainActivity$PixaPencil$1$2.invoke(MainActivity.kt:109)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:116)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:34)
at androidx.navigation.compose.NavHostKt$NavHost$4$2.invoke(NavHost.kt:163)
at androidx.navigation.compose.NavHostKt$NavHost$4$2.invoke(NavHost.kt:162)
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.runtime.saveable.SaveableStateHolderImpl.SaveableStateProvider(SaveableStateHolder.kt:84)
at androidx.navigation.compose.NavBackStackEntryProviderKt.SaveableStateProvider(NavBackStackEntryProvider.kt:60)
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:107)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:34)
at androidx.compose.runtime.CompositionLocalKt.CompositionLocalProvider(CompositionLocal.kt:228)
at androidx.navigation.compose.NavBackStackEntryProviderKt.LocalOwnersProvider(NavBackStackEntryProvider.kt:47)
at androidx.navigation.compose.NavHostKt$NavHost$4.invoke(NavHost.kt:162)
at androidx.navigation.compose.NavHostKt$NavHost$4.invoke(NavHost.kt:141)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:116)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:34)
at androidx.compose.animation.CrossfadeKt$Crossfade$5$1.invoke(Crossfade.kt:133)
at androidx.compose.animation.CrossfadeKt$Crossfade$5$1.invoke(Crossfade.kt:128)
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.animation.CrossfadeKt.Crossfade(Crossfade.kt:142)
at androidx.compose.animation.CrossfadeKt.Crossfade(Crossfade.kt:73)
at androidx.navigation.compose.NavHostKt.NavHost(NavHost.kt:141)
at androidx.navigation.compose.NavHostKt$NavHost$5.invoke(Unknown Source:13)
2022-10-16 18:18:55.632 3957-3957 AndroidRuntime com.therealbluepandabear.pixapencil E at androidx.navigation.compose.NavHostKt$NavHost$5.invoke(Unknown Source:10)
at androidx.compose.runtime.RecomposeScopeImpl.compose(RecomposeScopeImpl.kt:145)
at androidx.compose.runtime.ComposerImpl.recomposeToGroupEnd(Composer.kt:2375)
at androidx.compose.runtime.ComposerImpl.skipCurrentGroup(Composer.kt:2643)
at androidx.compose.runtime.ComposerImpl$doCompose$2$5.invoke(Composer.kt:3260)
at androidx.compose.runtime.ComposerImpl$doCompose$2$5.invoke(Composer.kt:3238)
at androidx.compose.runtime.SnapshotStateKt__DerivedStateKt.observeDerivedStateRecalculations(DerivedState.kt:341)
at androidx.compose.runtime.SnapshotStateKt.observeDerivedStateRecalculations(Unknown Source:1)
at androidx.compose.runtime.ComposerImpl.doCompose(Composer.kt:3238)
at androidx.compose.runtime.ComposerImpl.recompose$runtime_release(Composer.kt:3203)
at androidx.compose.runtime.CompositionImpl.recompose(Composition.kt:771)
at androidx.compose.runtime.Recomposer.performRecompose(Recomposer.kt:1031)
at androidx.compose.runtime.Recomposer.access$performRecompose(Recomposer.kt:125)
at androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2$2.invoke(Recomposer.kt:534)
at androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2$2.invoke(Recomposer.kt:503)
at androidx.compose.ui.platform.AndroidUiFrameClock$withFrameNanos$2$callback$1.doFrame(AndroidUiFrameClock.android.kt:34)
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:1035)
at android.view.Choreographer.doCallbacks(Choreographer.java:845)
at android.view.Choreographer.doFrame(Choreographer.java:775)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:1022)
at android.os.Handler.handleCallback(Handler.java:938)
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:7870)
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:1003)
Suppressed: kotlinx.coroutines.DiagnosticCoroutineContextException: [androidx.compose.runtime.PausableMonotonicFrameClock@7dee98d, androidx.compose.ui.platform.MotionDurationScaleImpl@5209742, StandaloneCoroutine{Cancelling}@c02b353, AndroidUiDispatcher@4fbd490]
Caused by: java.lang.InstantiationException: java.lang.Class<com.therealbluepandabear.pixapencil.screens.new.NewScreenViewModel> has no zero argument constructor
at java.lang.Class.newInstance(Native Method)
at androidx.lifecycle.ViewModelProvider$NewInstanceFactory.create(ViewModelProvider.kt:202)
... 71 more
Build.gradle (root):
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.3.0'
classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.10'
classpath 'com.google.dagger:hilt-android-gradle-plugin:2.42'
}
ext {
compose_version = '1.3.0-rc01'
}
}
plugins {
id 'com.autonomousapps.dependency-analysis' version "1.4.0"
id 'org.jetbrains.kotlin.android' version '1.6.10' apply false
}
task clean(type: Delete) {
delete rootProject.buildDir
}
Build.gradle (module):
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'kotlin-kapt'
id 'dagger.hilt.android.plugin'
}
android {
compileSdk 33
testOptions {
execution 'ANDROIDX_TEST_ORCHESTRATOR'
}
defaultConfig {
applicationId "com.therealbluepandabear.pixapencil"
minSdk 26
targetSdk 33
versionCode 26
versionName "0.1.8"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
javaCompileOptions {
annotationProcessorOptions {
arguments = ["room.schemaLocation": "$projectDir/schemas".toString()]
}
}
}
composeOptions {
kotlinCompilerExtensionVersion '1.1.1'
}
testOptions {
execution 'ANDROID_TEST_ORCHESTRATOR'
}
configurations {
androidTestImplementation {
exclude group: 'io.mockk', module: 'mockk-agent-jvm'
}
}
buildTypes {
release {
minifyEnabled 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 {
viewBinding true
compose true
}
namespace 'com.therealbluepandabear.pixapencil'
testOptions {
unitTests.returnDefaultValues = true
}
kotlin.sourceSets.all {
languageSettings.optIn("kotlin.RequiresOptIn")
}
}
dependencies {
implementation 'androidx.core:core-ktx:1.9.0'
implementation 'androidx.appcompat:appcompat:1.5.1'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation "androidx.room:room-runtime:2.4.3"
implementation "androidx.room:room-ktx:2.4.3"
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.5.1"
implementation 'androidx.coordinatorlayout:coordinatorlayout:1.2.0'
implementation "androidx.fragment:fragment-ktx:1.5.3"
implementation 'com.github.tianscar:quickbitmap:1.0.2.6'
implementation 'net.yslibrary.keyboardvisibilityevent:keyboardvisibilityevent:3.0.0-RC3'
implementation 'io.github.beyka:Android-TiffBitmapFactory:0.9.9.0'
implementation 'com.github.bumptech.glide:glide:4.13.2'
implementation 'com.github.duanhong169:colorpicker:1.1.6'
implementation 'com.google.android.material:material:1.6.1'
implementation 'com.google.code.gson:gson:2.9.1'
implementation 'com.android.volley:volley:1.2.1'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4'
implementation 'androidx.activity:activity-compose:1.6.0'
implementation "androidx.compose.ui:ui:$compose_version"
implementation "androidx.compose.ui:ui-tooling-preview:$compose_version"
implementation 'androidx.compose.material3:material3:1.0.0-rc01'
implementation "androidx.lifecycle:lifecycle-viewmodel-compose:2.5.1"
implementation "androidx.navigation:navigation-compose:2.5.2"
implementation "com.google.dagger:hilt-android:2.44"
implementation 'androidx.hilt:hilt-navigation-compose:1.0.0'
kapt "com.google.dagger:hilt-compiler:2.44"
debugImplementation "androidx.compose.ui:ui-tooling:$compose_version"
debugImplementation "androidx.compose.ui:ui-test-manifest:$compose_version"
runtimeOnly 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4'
testImplementation 'junit:junit:4.13.2'
testImplementation 'io.mockk:mockk-agent-jvm:1.12.3'
testImplementation 'io.mockk:mockk-android:1.12.3'
androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_version"
androidTestImplementation 'androidx.test.ext:junit-ktx:1.1.3'
androidTestImplementation 'junit:junit:4.13.2'
androidTestImplementation "org.jetbrains.kotlin:kotlin-test-junit:1.6.10"
androidTestImplementation "io.mockk:mockk-android:1.12.3"
androidTestImplementation "io.mockk:mockk-agent-jvm:1.12.3"
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-contrib:3.0.2'
androidTestImplementation 'androidx.test:rules:1.4.0'
androidTestImplementation 'androidx.test:runner:1.4.0'
androidTestImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.4'
androidTestImplementation "org.mockito.kotlin:mockito-kotlin:4.0.0"
androidTestImplementation "androidx.arch.core:core-testing:2.1.0"
androidTestImplementation "org.jetbrains.kotlin:kotlin-test:1.6.10"
androidTestUtil 'androidx.test:orchestrator:1.4.1'
kapt "androidx.room:room-compiler:2.4.3"
annotationProcessor 'com.github.bumptech.glide:compiler:4.13.2'
}
I have been almost going insane trying to fix this issue, as I have tried almost everything. I have looked through Stack Overflow posts of people asking similar questions.
I do:
- Have the appropriate
@HiltAndroidApp
annotation - Have the appropriate
@HiltViewModel
annotation - Have the appropriate
@AndroidEntryPoint
annotation - Have the appropriate
@Inject
annotation - Have used
hiltViewModel
to construct the ViewModel instance - Have the right Gradle dependencies
I have no idea as to what I am doing wrong as I am following the rules that are required, I have even read a book on Hilt and followed many tutorials without issue, but for some reason my app is getting an exception.
CodePudding user response:
I don't annotate my Repo class. Instead I annotate in my Module class. Try something like this instead.
Repo:
class QuickPresetsRepository @Inject constructor(private val dao: QuickPresetsDao) {
suspend fun getQuickPresets(): List<QuickPreset> {
return withContext(Dispatchers.IO) {
return@withContext dao.getAll()
}
}
Module:
@Module
@InstallIn(SingletonComponent::class)
object QuickPresetsModule {
@Provides
@Singleton
fun providesQuickPresetsRepository(dao: QuickPresetsDao): QuickPresetsRepository {
return QuickPresetsRepository(dao)
}
@Provides
fun provideRoomDao(database: QuickPresetsDb): QuickPresetsDao {
return database.dao
}
@Singleton
@Provides
fun provideRoomDatabase(app: Application): QuickPresetsDb {
return QuickPresetsDb.getInstance(app)
}
}
CodePudding user response:
After battling the issue for hours, I found a solution.
The issue stemmed - believe it or not - because I named my directory 'new', which seems to have been a reserved directory name which was confusing Hilt.
Changing the directory name in which the Composables were stored in to something other than 'new' fixed the issue.