7

It seems like the official Android docs way of using Compose, Hilt and ViewModel injection with Navigation does not work.

I have the following setup:

A main screen, which only hosts the nav controller. The Home screen shows a button to. navigation to the name screen which has a view model.

@Composable
fun MainScreen() {
    val navController = rememberNavController()
    NavHost(navController, startDestination = "home") {
        composable("home") {
            HomeScreen() {
                navController.navigate("my/Name")
            }
        }

        composable("my/{name}") { navBackStackEntry ->
            val viewModel: NameScreenViewModel = hiltViewModel(navBackStackEntry)
            NameScreen(viewModel)
        }
    }
}
@Composable
fun HomeScreen(onButtonClicked: () -> Unit) {
    Button(onClick = onButtonClicked, modifier = Modifier.padding(8.dp)) {
        Text("Continue")
    }
}
@HiltViewModel
class NameScreenViewModel @Inject constructor(private val test: TestClass): ViewModel() {
    var name = MutableLiveData("empty")

    fun change() {
        name.value = test.load()
    }
}

@Composable
fun NameScreen(viewModel: NameScreenViewModel) {
    val name by viewModel.name.observeAsState()

    Column {
        Text("Hi there, $name")

        Button(onClick = { viewModel.change() }) {
            Text("Click me!")
        }
    }
}

Activity and Application have the correct annotations:

@AndroidEntryPoint
class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            ComposeViewModelTheme {
                // A surface container using the 'background' color from the theme
                Surface(color = MaterialTheme.colors.background) {
                    MainScreen()
                }
            }
        }
    }
}
@HiltAndroidApp
class MainApplication : Application() {
}

The injected class is very simple and is even registered for being provided instead of using the constructor annotation:

class TestClass {
    fun load(): String {
        return "name"
    }
}
@Module
@InstallIn(SingletonComponent::class)
class AppModule {
    @Provides
    @Singleton
    fun providesTestClass(): TestClass {
        return TestClass()
    }
}

If using hiltViewModel as above, the app will crash with the error

java.lang.IllegalStateException: Given component holder class eu.meecolabs.composeviewmodel.MainActivity does not implement interface dagger.hilt.internal.GeneratedComponent or interface dagger.hilt.internal.GeneratedComponentManager
        at dagger.hilt.EntryPoints.get(EntryPoints.java:62)
        at dagger.hilt.android.internal.lifecycle.HiltViewModelFactory.createInternal(HiltViewModelFactory.java:129)
        at androidx.hilt.navigation.HiltViewModelFactory.create(HiltNavBackStackEntry.kt:56)
        at eu.meecolabs.composeviewmodel.ui.ComposableSingletons$MainScreenKt$lambda-1$1.invoke(MainScreen.kt:26)
        at eu.meecolabs.composeviewmodel.ui.ComposableSingletons$MainScreenKt$lambda-1$1.invoke(MainScreen.kt:25)
        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$6$1.invoke(NavHost.kt:146)
        at androidx.navigation.compose.NavHostKt$NavHost$6$1.invoke(NavHost.kt:145)
        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:215)
        at androidx.compose.runtime.saveable.SaveableStateHolderImpl.SaveableStateProvider(SaveableStateHolder.kt:84)
        at androidx.navigation.compose.NavBackStackEntryProviderKt.SaveableStateProvider(NavBackStackEntryProvider.kt:59)
        at androidx.navigation.compose.NavBackStackEntryProviderKt.access$SaveableStateProvider(NavBackStackEntryProvider.kt:1)
        at androidx.navigation.compose.NavBackStackEntryProviderKt$LocalOwnersProvider$1.invoke(NavBackStackEntryProvider.kt:51)
        at androidx.navigation.compose.NavBackStackEntryProviderKt$LocalOwnersProvider$1.invoke(NavBackStackEntryProvider.kt:50)
        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:215)
        at androidx.navigation.compose.NavBackStackEntryProviderKt.LocalOwnersProvider(NavBackStackEntryProvider.kt:46)
        at androidx.navigation.compose.NavHostKt$NavHost$6.invoke(NavHost.kt:145)
        at androidx.navigation.compose.NavHostKt$NavHost$6.invoke(NavHost.kt:144)
        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$1$1.invoke(Crossfade.kt:74)
        at androidx.compose.animation.CrossfadeKt$Crossfade$1$1.invoke(Crossfade.kt:69)
        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:86)
        at androidx.navigation.compose.NavHostKt.NavHost(NavHost.kt:144)
        at androidx.navigation.compose.NavHostKt$NavHost$7.invoke(Unknown Source:13)
        at androidx.navigation.compose.NavHostKt$NavHost$7.invoke(Unknown Source:10)
        at androidx.compose.runtime.RecomposeScopeImpl.compose(RecomposeScopeImpl.kt:140)
        at androidx.compose.runtime.ComposerImpl.recomposeToGroupEnd(Composer.kt:2156)
        at androidx.compose.runtime.ComposerImpl.skipCurrentGroup(Composer.kt:2399)
        at androidx.compose.runtime.ComposerImpl$doCompose$2$5.invoke(Composer.kt:2580)
        at androidx.compose.runtime.ComposerImpl$doCompose$2$5.invoke(Composer.kt:2573)
        at androidx.compose.runtime.SnapshotStateKt.observeDerivedStateRecalculations(SnapshotState.kt:540)
        at androidx.compose.runtime.ComposerImpl.doCompose(Composer.kt:2566)
2021-07-27 17:46:40.678 22501-22501/? E/AndroidRuntime:     at androidx.compose.runtime.ComposerImpl.recompose$runtime_release(Composer.kt:2542)
        at androidx.compose.runtime.CompositionImpl.recompose(Composition.kt:613)
        at androidx.compose.runtime.Recomposer.performRecompose(Recomposer.kt:764)
        at androidx.compose.runtime.Recomposer.access$performRecompose(Recomposer.kt:103)
        at androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2$2.invoke(Recomposer.kt:447)
        at androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2$2.invoke(Recomposer.kt:416)
        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)
        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:7356)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:491)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:940)

I am using the latest Android Studio Preview and the following dependency versions:

ext {
        compose_version = '1.0.0-rc02'
        hilt_version = '2.38'
    }
    dependencies {
        classpath "com.android.tools.build:gradle:7.1.0-alpha04"
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.10"

        classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version"

        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
implementation 'androidx.core:core-ktx:1.6.0'

    implementation 'androidx.appcompat:appcompat:1.3.1'

    implementation 'com.google.android.material:material:1.4.0'

    implementation "androidx.compose.ui:ui:$compose_version"
    implementation "androidx.compose.material:material:$compose_version"
    implementation "androidx.compose.ui:ui-tooling-preview:$compose_version"

    implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.1'

    implementation "androidx.compose.runtime:runtime-livedata:$compose_version"
    implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.3.1'

    implementation 'androidx.hilt:hilt-lifecycle-viewmodel:1.0.0-alpha03'
    implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:1.0.0-alpha07'

    implementation 'androidx.activity:activity-compose:1.3.0-rc02'

    implementation "androidx.navigation:navigation-compose:2.4.0-alpha05"
    implementation 'androidx.hilt:hilt-navigation-compose:1.0.0-alpha03'

    implementation "com.google.dagger:hilt-android:$hilt_version"
    kapt "com.google.dagger:hilt-compiler:$hilt_version"
    kapt 'androidx.hilt:hilt-compiler:1.0.0'

I really do not know what else to try. I have checked so many different combinations already and read through the hand full or blog posts or SO issues, but all seem to be using outdated libraries or there does not seem to be a real solution.

dst
  • 1,770
  • 13
  • 26

2 Answers2

6

The solution seems to be updating dependencies. Since I posted this question, Hilt released v2.38.1 and Compose was released in v1.0.0 (final).

That combination will work as expected.

dst
  • 1,770
  • 13
  • 26
1

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 class hosting this code was not injected but navigated to.

D Lad
  • 146
  • 8
  • So you manually create this class in the NavController’s composable and then pass it to the Composable? – dst Aug 02 '21 at 09:58
  • I don’t think that this is the issue. Your solution just manually creates the viewModel instead of using the compose helper. The exception that I see is that the Activity is somehow not properly implementing some internal hilt/dagger interfaces, which should be automatically achieved by the annotation + kapt. – dst Aug 02 '21 at 13:16
  • The viewmodel is not manually created. It is created by HiltViewModelFactory as stated in the docs. All its dependencies are injected at creation time. – D Lad Aug 21 '21 at 12:06