Compose Multiplatform中如何注入ViewModel?Koin通用模块暂不支持求替代
在Compose Multiplatform中给Composable注入ViewModel的可行方案
1. 手动封装跨平台ViewModel容器
既然Koin暂时不支持common模块的ViewModel注入,你可以自己搞一套跨平台的ViewModel存储逻辑:
- 在common模块定义
ViewModelStore接口,只留获取ViewModel的方法 - Android端直接用Jetpack的
ViewModelStore实现接口,iOS端用字典做内存缓存 - 用
CompositionLocal把ViewModelStore传到Composable里,再写个通用扩展函数方便调用
示例代码:
// common模块 interface ViewModelStore { fun <T : ViewModel> getViewModel(key: String, factory: () -> T): T } // 定义CompositionLocal供Composable获取 val LocalViewModelStore = compositionLocalOf<ViewModelStore> { error("ViewModelStore未初始化") } // 通用Composable扩展,简化获取逻辑 @Composable inline fun <reified T : ViewModel> viewModel(factory: () -> T): T { val store = LocalViewModelStore.current return store.getViewModel(T::class.simpleName!!, factory) } // Android模块实现 class AndroidViewModelStore(private val viewModelStore: androidx.lifecycle.ViewModelStore) : ViewModelStore { override fun <T : ViewModel> getViewModel(key: String, factory: () -> T): T { return ViewModelProvider(ViewModelStoreOwner { viewModelStore }, object : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") override fun <T : androidx.lifecycle.ViewModel> create(modelClass: Class<T>): T { return factory() as T } })[key, T::class.java] } } // iOS模块实现 class IOSViewModelStore : ViewModelStore { private val cache = mutableMapOf<String, ViewModel>() override fun <T : ViewModel> getViewModel(key: String, factory: () -> T): T { return cache.getOrPut(key) { factory() } as T } }
2. 用MVIKotlin框架
MVIKotlin是专为跨平台设计的MVI框架,自带的Store就相当于ViewModel,支持在common模块直接注入和使用:
- 在common模块定义Store,里面写业务逻辑
- 用Koin或者其他跨平台DI框架注入Store实例
- 在Composable里直接获取,配合
collectAsState监听状态变化
示例代码:
// common模块定义Store class CounterStore : Store<CounterState, CounterIntent, CounterLabel>() { private val _state = MutableStateFlow(CounterState(0)) override val state = _state.asStateFlow() override fun handleIntent(intent: CounterIntent) { when (intent) { is CounterIntent.Increment -> _state.value = _state.value.copy(count = _state.value.count + 1) } } } // Composable中使用 @Composable fun CounterScreen() { val store: CounterStore = get() // 假设用Koin注入 val state by store.state.collectAsState() Button(onClick = { store.accept(CounterIntent.Increment) }) { Text("当前计数: ${state.count}") } }
3. Koin平台特定注入+通用层封装
如果不想换DI框架,可以在平台模块里用Koin注入ViewModel,再通过通用接口暴露给common模块的Composable:
- 在common模块定义ViewModel的接口,只暴露需要的方法和状态
- 各平台实现这个接口,Android端用Jetpack ViewModel,iOS端用普通类
- 用expect/actual定义平台特定的获取方法,在Composable里直接调用
示例代码:
// common模块 interface CounterViewModel { val count: StateFlow<Int> fun increment() } // Android模块实现 class AndroidCounterViewModel : ViewModel(), CounterViewModel { private val _count = MutableStateFlow(0) override val count = _count.asStateFlow() override fun increment() { _count.value++ } } // Android模块的Koin注入 val androidModule = module { viewModel { AndroidCounterViewModel() } } // common模块的Composable @Composable fun CounterScreen(viewModel: CounterViewModel = getPlatformViewModel()) { val count by viewModel.count.collectAsState() Button(onClick = { viewModel.increment() }) { Text("计数: $count") } } // 平台特定的获取方法 expect fun getPlatformViewModel(): CounterViewModel // Android端actual实现 actual fun getPlatformViewModel(): CounterViewModel { return getKoin().get<AndroidCounterViewModel>() } // iOS端actual实现(假设用Koin注入iOS版本的ViewModel) actual fun getPlatformViewModel(): CounterViewModel { return getKoin().get<IOSCounterViewModel>() }
内容的提问来源于stack exchange,提问作者Ahsan Ullah Rasel
相关产品推荐
相关产品推荐

