Dependency injection with Hilt and room database

Viewed 100

So I am currently trying to figure out how to implement a class called UserViewModel, that uses di with hilt into a composable that will contain a button. This button, when clicked, will take users info such as name and e-mail and save it into a database using room. I have, so far, hoisted everything into a "single" composable - UserProfileState.

How can I make it so that when the user clicks the "save info", the UserViewModel will recieve the event from the composable and save it into a database ?

My main viewModel class that is connected to the database.

@HiltViewModel
class UserViewModel @Inject constructor(
    private val repo: UserRepository
): ViewModel() {

    fun addUser(users: Users) = viewModelScope.launch(Dispatchers.IO) {
            repo.addUser(users)
    }

    fun updateUser(users: Users) = viewModelScope.launch(Dispatchers.IO) {
            repo.updateUser(users)
    }

    fun deleteUser(users: Users) = viewModelScope.launch(Dispatchers.IO) {
            repo.deleteUser(users)
    }

}

My Button: (when clicked, should save the user info into a database which I have already created using room)

Button(
  colors = ButtonDefaults.buttonColors(backgroundColor = LightBlue),
  modifier = Modifier.size(160.dp, 50.dp),
  onClick = { 
             val userInfo = Users(id, username, email)
                 viewmodel.addUsers(users)
            },

  ) {
      Text(
           text = "Save Info",
           color = Color.White,
           fontWeight = FontWeight.Bold,
           fontSize = 15.sp
            )
    }

My Hoisted composable, form screen composable:

@Composable
fun UserProfileState() {
    val viewModel = viewModel<UserViewModel>() <-- When I added this, the app crashes !!
    var username by rememberSaveable { mutableStateOf("") }
    var email by rememberSaveable { mutableStateOf("") }
//    val userInfoSaved = viewModel.addUser(Users (username, email) )

    UserProfile(
        username = username,
        onUsernameChange = { username = it },
        email = email,
        onEmailChange = { email = it },

        )
    
}

Update:

This is my Screen composable with button.

@SuppressLint("UnusedMaterialScaffoldPaddingParameter")
@Composable
fun UserProfile(
    username: String,
    onUsernameChange: (String) -> Unit,
    email: String,
    onEmailChange: (String) -> Unit,
    viewModel: UserViewModel,

) {
    val scrollState = rememberScrollState()

    Column(
        modifier = Modifier.fillMaxSize(),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Box(
            modifier = Modifier
                .fillMaxSize()

        ) {
            Column(
                modifier = Modifier
                    .fillMaxSize()
                    .verticalScroll(scrollState), // Allows scroll-ing in profile screen
                verticalArrangement = Arrangement.Top,
                horizontalAlignment = Alignment.CenterHorizontally
            ) {

                ProfilePicture(imageSize = 150.dp)

                // Username
                Spacer(modifier = Modifier.height(20.dp))

                OutlinedTextField(
                    value = username,
                    onValueChange = onUsernameChange,
                    modifier = Modifier
                        .wrapContentSize()
                        .fillMaxWidth(0.944f),
                    colors = TextFieldDefaults.outlinedTextFieldColors(
                        focusedBorderColor = LightBlue
                    ),
                    label = { Text(text = "Name") },
                    singleLine = true,
                    leadingIcon = {
                        Icon(
                            imageVector = Icons.Default.Email,
                            contentDescription = "Name"
                        )
                    },

                    )

                // Email
                Spacer(modifier = Modifier.height(10.dp))

                OutlinedTextField(
                    value = email,
                    onValueChange = onEmailChange,
                    modifier = Modifier
                        .wrapContentSize()
                        .fillMaxWidth(0.944f),
                    colors = TextFieldDefaults.outlinedTextFieldColors(
                        focusedBorderColor = LightBlue
                    ),
                    label = { Text(text = "Email") },
                    singleLine = true,
                    leadingIcon = {
                        Icon(
                            imageVector = Icons.Default.Email,
                            contentDescription = null
                        )
                    },

                    )

                Spacer(modifier = Modifier.height(20.dp))
                BaseScreen()

                Spacer(modifier = Modifier.height(50.dp))
                Button(
                    colors = ButtonDefaults.buttonColors(backgroundColor = LightBlue),
                    modifier = Modifier.size(160.dp, 50.dp),
                    onClick = {
                                viewModel.addUser(Users(id = UUID.randomUUID(), username, email))
                              },
                ) {
                    Text(
                        text = "Save Info",
                        color = Color.White,
                        fontWeight = FontWeight.Bold,
                        fontSize = 15.sp
                    )
                }

            }
        }
    }
}

This is the error I recieve:

W/OnBackInvokedCallback: OnBackInvokedCallback is not enabled for the application.
    Set 'android:enableOnBackInvokedCallback="true"' in the application manifest.
E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.example.myApp2022, PID: 7615
    java.lang.RuntimeException: Cannot create an instance of class com.example.myApp2022.userviewmodel.UserViewModel
        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: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.viewmodel.compose.ViewModelKt.get(ViewModel.kt:215)
        at androidx.lifecycle.viewmodel.compose.ViewModelKt.viewModel(ViewModel.kt:156)
        at com.example.myApp2022.drawer.drawerComposables.profile.ComposableSingletons$ProfileKt$lambda-1$1.invoke(Profile.kt:412)
        at com.example.myApp2022.drawer.drawerComposables.profile.ComposableSingletons$ProfileKt$lambda-1$1.invoke(Profile.kt:40)
        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.material.MaterialTheme_androidKt.PlatformMaterialTheme(MaterialTheme.android.kt:23)
        at androidx.compose.material.MaterialThemeKt$MaterialTheme$1$1.invoke(MaterialTheme.kt:82)
        at androidx.compose.material.MaterialThemeKt$MaterialTheme$1$1.invoke(MaterialTheme.kt:81)
        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.material.TextKt.ProvideTextStyle(Text.kt:265)
        at androidx.compose.material.MaterialThemeKt$MaterialTheme$1.invoke(MaterialTheme.kt:81)
        at androidx.compose.material.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.material.MaterialThemeKt.MaterialTheme(MaterialTheme.kt:72)
        at com.example.myApp2022.ui.theme.ThemeKt.myApp2022Theme(Theme.kt:41)
        at com.example.myApp2022.drawer.drawerComposables.profile.ProfileKt.UserProfileState(Profile.kt:40)
        at com.example.myApp2022.drawer.drawerNavigation.ComposableSingletons$NavHostKt$lambda-2$1.invoke(NavHost.kt:27)
        at com.example.myApp2022.drawer.drawerNavigation.ComposableSingletons$NavHostKt$lambda-2$1.invoke(NavHost.kt:26)
        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)
E/AndroidRuntime:     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$4$1.invoke(Crossfade.kt:115)
        at androidx.compose.animation.CrossfadeKt$Crossfade$4$1.invoke(Crossfade.kt:110)
        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:124)
        at androidx.compose.animation.CrossfadeKt.Crossfade(Crossfade.kt:55)
        at androidx.navigation.compose.NavHostKt.NavHost(NavHost.kt:141)
        at androidx.navigation.compose.NavHostKt$NavHost$5.invoke(Unknown Source:13)
        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:2351)
        at androidx.compose.runtime.ComposerImpl.skipCurrentGroup(Composer.kt:2618)
        at androidx.compose.runtime.ComposerImpl$doCompose$2$5.invoke(Composer.kt:3205)
        at androidx.compose.runtime.ComposerImpl$doCompose$2$5.invoke(Composer.kt:3183)
        at androidx.compose.runtime.SnapshotStateKt__DerivedStateKt.observeDerivedStateRecalculations(DerivedState.kt:252)
        at androidx.compose.runtime.SnapshotStateKt.observeDerivedStateRecalculations(Unknown Source:1)
        at androidx.compose.runtime.ComposerImpl.doCompose(Composer.kt:3183)
        at androidx.compose.runtime.ComposerImpl.recompose$runtime_release(Composer.kt:3148)
        at androidx.compose.runtime.CompositionImpl.recompose(Composition.kt:746)
        at androidx.compose.runtime.Recomposer.performRecompose(Recomposer.kt:876)
        at androidx.compose.runtime.Recomposer.access$performRecompose(Recomposer.kt:107)
        at androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2$2.invoke(Recomposer.kt:485)
        at androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2$2.invoke(Recomposer.kt:454)
        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: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:7898)
E/AndroidRuntime:     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.DiagnosticCoroutineContextException: [androidx.compose.runtime.PausableMonotonicFrameClock@250fef6, androidx.compose.ui.platform.MotionDurationScaleImpl@26fb2f7, StandaloneCoroutine{Cancelling}@420a564, AndroidUiDispatcher@3a313cd]
    Caused by: java.lang.InstantiationException: java.lang.Class<com.example.myApp2022.userviewmodel.UserViewModel> has no zero argument constructor
        at java.lang.Class.newInstance(Native Method)
        at androidx.lifecycle.ViewModelProvider$NewInstanceFactory.create(ViewModelProvider.kt:202)
            ... 88 more

Latest Update: This error persists:

java.lang.RuntimeException: Cannot create an instance of class com.example.myApp2022.userviewmodel.UserViewModel

1 Answers

You should add the below code to your dependencies in build.gradle:

 implementation "androidx.lifecycle:lifecycle-viewmodel-compose:2.5.1"

and in your activity create an instance of ViewModel like this:

val userViewModel  = viewModel<UserViewModel >()

--UPDATE--

Your setContent should be something like this:

setContent {
            YourComposeTheme {
                val userViewModel  = viewModel<UserViewModel >()
                var username by rememberSaveable { mutableStateOf("") }
                var email by rememberSaveable { mutableStateOf("") }

                Column(...) {

                //your TextFields ...

                Button(//according your codes
                    colors = ButtonDefaults.buttonColors(backgroundColor = LightBlue),
                    modifier = Modifier.size(160.dp, 50.dp),
                    onClick = {
                        val userInfo = Users(id, username, email)
                        userViewModel.addUsers(userInfo)
                    },

                    ) {
                    Text(
                        text = "Save Info",
                        color = Color.White,
                        fontWeight = FontWeight.Bold,
                        fontSize = 15.sp
                    )
                }
            }
            }
        }

--UPDATE2--

According to your Error message, you should add this to your manifest inside the application tag:

android:enableOnBackInvokedCallback="true"

and ensure your activity/fragment is having @AndroidEntryPoint annotation.

--Update3--

If the error still persists then:

1- Make sure your view Model class is public

2- Make sure your view model class constructor is public

--Update4--

enter image description here

Related