Skip to content

Compose rememberViewModel can crash with generic ViewModel bounds (Cannot create TypeToken for non fully reified type) #506

@Nek-12

Description

@Nek-12

Describe the bug

When using org.kodein.di.compose.viewmodel.rememberViewModel with a generic ViewModel subclass, runtime lookup will crash with:

IllegalArgumentException: Cannot create TypeToken for non fully reified type ...

In our case this happens with FlowMVI's ContainerViewModel<T, S, I, A> (pro.respawn.flowmvi.android.ContainerViewModel).

Stacktrace

java.lang.IllegalArgumentException: Cannot create TypeToken for non fully reified type pro.respawn.flowmvi.api.Container<S, I, A>
    at org.kodein.type.TypeTokensJVMKt.typeToken(typeTokensJVM.kt:101)
    at org.kodein.type.JVMClassTypeToken.getGenericParameters(JVMClassTypeToken.kt:10)
    at org.kodein.type.AbstractTypeToken.equals(TypeToken.kt:117)
    at org.kodein.type.AbstractTypeToken.isAssignableFrom(TypeToken.kt:86)
    at org.kodein.di.internal.TypeChecker$Up.check(DITreeImpl.kt:18)
    ...
    at org.kodein.di.compose.viewmodel.KodeinViewModelScopedSingleton.create(KodeinViewModelScope.kt:44)

Reproduction

  1. Bind a generic ViewModel in DI (example with FlowMVI):
bind<ContainerViewModel<MyContainer, MyState, MyIntent, MyAction>>() with provider {
    ContainerViewModel(MyContainer(...))
}

Here all functions used are inline and parameters reified (so the error message will be misleading)

  1. Resolve it from compose:
val vm by rememberViewModel<ContainerViewModel<MyContainer, MyState, MyIntent, MyAction>>()

in this function, the ContainerViewModel is resolved as erased even though we could use generic() due to VM parameter being reified

  1. App crashes at runtime with the stacktrace above.

Why this seems to happen

rememberViewModel currently uses erased(modelClass) in KodeinViewModelScopedSingleton / KodeinViewModelScopedFactory.
That eventually causes Kodein/kaverit to inspect the class type parameters and their bounds.
For ContainerViewModel<T, S, I, A>, T is reified T: Container<S, I, A>, and S/I/A are reified type variables.

Working workaround

We forked the compose ViewModel helper and resolved with a typed token (generic<VM>()) instead of erased(modelClass), then used that token in DI lookup:

di.direct.Instance(type = generic<VM>(), tag = tag)
// and for factory:
// di.direct.Factory(argType, generic<VM>(), tag).invoke(arg)

With this change the crash is gone.

Request

Could Kodein's compose ViewModel integration support typed token lookup (or otherwise avoid this erased-path failure) out of the box?

Even if the existing behavior is kept for compatibility, a typed overload/option would help with generic ViewModels used in MVI libs.

Environment

  • Kodein: 7.31.0
  • kaverit: 2.12.0
  • Kotlin: 2.3.10
  • Compose Multiplatform app (runtime reproduced on Android/JVM)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions