Cannot create viewmodel from composalbe function
Asked Answered
B

3

5

I have a view model. I am using Hilt. I can create this view model from activity, like this

val model: ProfileViewModel by viewModels()

However when I try to create this view model from a composable function

import androidx.compose.material.Button
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource
import androidx.lifecycle.viewmodel.compose.viewModel

@Composable
fun ProfileScreen(){
    val model: ProfileViewModel = viewModel()
    Button(onClick = {
        model.logout()
    }){
        Text(stringResource(R.string.log_out))
    }
}

I am getting error

2021-04-22 10:01:55.503 10482-10482/com.pulsariodev E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.pulsariodev, PID: 10482
    java.lang.RuntimeException: Cannot create an instance of class com.pulsario.ui.profile.ProfileViewModel
        at androidx.lifecycle.ViewModelProvider$NewInstanceFactory.create(ViewModelProvider.java:221)
        at androidx.lifecycle.ViewModelProvider$AndroidViewModelFactory.create(ViewModelProvider.java:278)
        at androidx.lifecycle.SavedStateViewModelFactory.create(SavedStateViewModelFactory.java:112)
        at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.java:185)
        at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.java:150)
        at androidx.lifecycle.viewmodel.compose.ViewModelKt.get(ViewModel.kt:78)
        at androidx.lifecycle.viewmodel.compose.ViewModelKt.viewModel(ViewModel.kt:63)
        at com.pulsario.ui.profile.ProfileScreenKt.ProfileScreen(ProfileScreen.kt:23)
        at com.pulsario.ui.main.MainScreenKt$MainScreen$2$1$1$1.invoke(MainScreen.kt:60)
        at com.pulsario.ui.main.MainScreenKt$MainScreen$2$1$1$1.invoke(MainScreen.kt:59)
        at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:118)
        at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:34)
        at androidx.navigation.compose.NavHostKt$NavHost$5$1$1.invoke(NavHost.kt:138)
        at androidx.navigation.compose.NavHostKt$NavHost$5$1$1.invoke(NavHost.kt:137)
        at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:109)
        at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:34)
        at androidx.compose.runtime.CompositionLocalKt.CompositionLocalProvider(CompositionLocal.kt:193)
        at androidx.compose.runtime.saveable.SaveableStateHolderImpl.SaveableStateProvider(SaveableStateHolder.kt:84)
        at androidx.navigation.compose.NavHostKt.SaveableStateProvider(NavHost.kt:150)
        at androidx.navigation.compose.NavHostKt.access$SaveableStateProvider(NavHost.kt:1)
        at androidx.navigation.compose.NavHostKt$NavHost$5$1.invoke(NavHost.kt:137)
        at androidx.navigation.compose.NavHostKt$NavHost$5$1.invoke(NavHost.kt:136)
        at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:109)
        at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:34)
        at androidx.compose.runtime.CompositionLocalKt.CompositionLocalProvider(CompositionLocal.kt:193)
        at androidx.navigation.compose.NavHostKt.NavHost(NavHost.kt:132)
        at androidx.navigation.compose.NavHostKt$NavHost$6.invoke(Unknown Source:13)
        at androidx.navigation.compose.NavHostKt$NavHost$6.invoke(Unknown Source:10)
        at androidx.compose.runtime.RecomposeScopeImpl.compose(RecomposeScopeImpl.kt:97)
        at androidx.compose.runtime.ComposerImpl.recomposeToGroupEnd(Composer.kt:2117)
        at androidx.compose.runtime.ComposerImpl.skipCurrentGroup(Composer.kt:2375)
        at androidx.compose.runtime.ComposerImpl.doCompose(Composer.kt:2517)
        at androidx.compose.runtime.ComposerImpl.recompose$runtime_release(Composer.kt:2488)
        at androidx.compose.runtime.CompositionImpl.recompose(Composition.kt:546)
        at androidx.compose.runtime.Recomposer.performRecompose(Recomposer.kt:733)
        at androidx.compose.runtime.Recomposer.access$performRecompose(Recomposer.kt:102)
        at androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2$2.invoke(Recomposer.kt:443)
        at androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2$2.invoke(Recomposer.kt:415)
        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:965)
        at android.view.Choreographer.doCallbacks(Choreographer.java:791)
2021-04-22 10:01:55.504 10482-10482/com.pulsariodev E/AndroidRuntime:     at android.view.Choreographer.doFrame(Choreographer.java:722)
        at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:952)
        at android.os.Handler.handleCallback(Handler.java:883)
        at android.os.Handler.dispatchMessage(Handler.java:100)
        at android.os.Looper.loop(Looper.java:214)
        at android.app.ActivityThread.main(ActivityThread.java:7386)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:980)
     Caused by: java.lang.InstantiationException: java.lang.Class<com.pulsario.ui.profile.ProfileViewModel> has no zero argument constructor
        at java.lang.Class.newInstance(Native Method)
        at androidx.lifecycle.ViewModelProvider$NewInstanceFactory.create(ViewModelProvider.java:219)
            ... 52 more

Here is the example from Android tutorial but this does not work for me.

Bema answered 22/4, 2021 at 7:4 Comment(4)
You can't instantiate ViewModel object by constructor, to create the object you have two ways 1. by ViewModelProviders & 2. val model: ProfileViewModel by viewModels() extension provided in viewmodel-ktx libJohnette
ViewModel bound to view lifecycleJohnette
in your case you can use this var model: ProfileViewModel = ViewModelProviders.of("activity-reference").get(ProfileViewModel::class.java)Johnette
But in this example they are doing this in the same way as I do developer.android.com/jetpack/compose/libraries#hiltBema
B
6

Because my composable function is created via navigation but not directly from Fragment or Activity I have to use the function

hiltViewModel()

Details in the documentation

Bema answered 26/4, 2021 at 11:22 Comment(4)
For some reason, in this case, the hilt cannot create a viewmodel if it contains your dependencies (provided via @Module). For example without dependencies or with SavedStateHandle dependency there is no creation error. The documentation is misleading "If ... is scoped to the navigation graph," - and if not, if you need exactly scoped to activity, then where does the error come from? Bug?Falcongentle
Now it's called hiltViewModel()Littlest
In case of navigation use val exampleViewModel = hiltViewModel<ExampleViewModel>()Te
hiltViewModel() from navigation and by viewModels of activity provide different viewmodel instances right? Do you have to pass viewmodel as argument if you want to use the same viewmodel in activity and navigation?Bromley
H
2

Here's my solution:

Step 1

In the Project build.gradle file, define lifecycle_version = "2.4.0-beta01"

as in

// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
    ext {
        compose_version = '1.0.1'
        lifecycle_version = "2.4.0-beta01"
    }
    // rest is the same

Important: It won't work with androidx.lifecycle:lifecycle-livedata:2.3.1 and probably below

Step 2

In the app build.gradle file, make 3 changes: (1) change to compileSdk 31, (2) change to targetSdk 31, and (3) add to dependencies the following implementation "androidx.lifecycle:lifecycle-viewmodel-compose:$lifecycle_version" and implementation "androidx.compose.runtime:runtime-livedata:$compose_version"

as in

android {
    compileSdk 31

    defaultConfig {
        minSdk 21
        targetSdk 31
        //rest is the same
    }
}

//rest is the same

dependencies {
    //...
    implementation "androidx.compose.runtime:runtime-livedata:$compose_version"
    implementation "androidx.lifecycle:lifecycle-viewmodel-compose:$lifecycle_version"
}

Important: It won't work with compileSdk 30 and/or targetSdk 30

Step 3

Make sure to import androidx.lifecycle.viewmodel.compose.*

import androidx.lifecycle.viewmodel.compose.*

Important: Must include the above import

Handfast answered 29/9, 2021 at 6:7 Comment(0)
B
0

I solved this using the below

class MyScreenManager(navController:NavigationController){

lateinit var viewModel:MyViewModel = HiltViewModelFactory(
            activity,
            navController.getBackStackEntry(MyScreen.route)
        ).create(MyViewModel::class.java)
}

The above class was not injected but navigated to from a composable function

Byproduct answered 31/7, 2021 at 23:38 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.