-
Notifications
You must be signed in to change notification settings - Fork 178
Compose rememberViewModel can crash with generic ViewModel bounds (Cannot create TypeToken for non fully reified type) #506
Description
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
- 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)
- 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
- 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)