RecyclerView内嵌ComposeView调用viewModel报No ViewModelStoreOwner问题问询
问题原因
- Compose 中
viewModel()方法默认依赖LocalViewModelStoreOwnerCompositionLocal 来获取ViewModel存储的宿主,默认会从上层View树中就近查找Activity/Fragment实例作为宿主 - RecyclerView滚动时会触发ViewHolder复用、item View的临时附着/脱离窗口操作,此时Compose重组过程中会出现无法获取到有效
ViewModelStoreOwner的情况,直接抛出该异常 - 额外隐藏问题:如果直接使用所在Activity/Fragment的
ViewModelStoreOwner,会导致所有列表item复用同一个ViewModel实例,出现数据互相覆盖的异常
可行解决方案
方案1:状态上提(最推荐,实现成本最低)
直接移除item级别的ViewModel,将所有列表item的状态统一存放在列表所在的Fragment/Activity层级的ViewModel中,bindData时将对应item的状态和事件回调直接传给DataItemComposable即可,完全绕开ViewModelStoreOwner的获取问题,适合绝大多数业务场景。
方案2:给ViewHolder单独实现ViewModelStoreOwner
如果必须保留item级别的ViewModel,可让ViewHolder自己实现ViewModelStoreOwner接口,手动管理ViewModel的生命周期,同时在Compose初始化时显式传入对应的CompositionLocal:
- ViewHolder实现逻辑:
class DataItemViewHolder( private val binding: ItemDataBinding, private val parentLifecycleOwner: LifecycleOwner ) : RecyclerView.ViewHolder(binding.root), ViewModelStoreOwner { private val itemViewModelStore = ViewModelStore() override fun getViewModelStore(): ViewModelStore = itemViewModelStore // ViewHolder回收时清理ViewModel,避免内存泄漏 fun clear() { itemViewModelStore.clear() } override fun bindData(dataItem: DataItem, position: Int) { binding.root.apply { setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) setContent { // 显式提供所需的CompositionLocal CompositionLocalProvider( LocalLifecycleOwner provides parentLifecycleOwner, LocalViewModelStoreOwner provides this@DataItemViewHolder ) { MaterialTheme { DataItemComposable( repository = repository, postId = dataItem.id, ) } } } } } }
- 在RecyclerView.Adapter中重写
onViewRecycled方法,调用对应holder的clear()方法即可。
方案3:直接手动创建ViewModel,规避Compose viewModel()方法
直接在ViewHolder层完成ViewModel的创建,将实例作为参数传给Composable,不需要在Composable内部调用viewModel()方法,完全绕开LocalViewModelStoreOwner依赖:
// DataItemComposable修改参数,直接接收ViewModel @Composable fun DataItemComposable( viewModel: DataItemViewModel ) { var state by remember { viewModel.itemState } Box { // 原有业务逻辑 } } // ViewHolder中bindData逻辑修改 override fun bindData(dataItem: DataItem, position: Int) { // 直接创建ViewModel,不需要依赖Compose的能力 val itemViewModel = DataItemViewModel(repository, dataItem.id) binding.root.apply { setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) setContent { MaterialTheme { DataItemComposable(viewModel = itemViewModel) } } } }
如果不需要item滚动回收后保留状态,该方案实现成本极低,没有额外的生命周期管理成本。
内容的提问来源于stack exchange,提问作者rohan
相关产品推荐
相关产品推荐

