依赖ViewModel的Composable函数如何实现正常预览?
错误根因
Compose 预览环境没有提供完整的ViewModelStore实现,也没有初始化Hilt依赖注入容器,所以调用hiltViewModel()创建ViewModel实例的时候就会抛出该异常,和你把ViewModel放在参数默认值还是函数内部没有关系,只要预览路径执行了hiltViewModel()就会报错。
最优解决方案:UI层与注入层拆分
这个方案同时解决预览和可测试性问题:
- 把原有
HomeScreen拆分为两层:
- 路由层(带注入,无UI逻辑):负责获取ViewModel、转换数据格式、处理跳转逻辑
- 纯UI层(无依赖,仅接收状态和回调):负责界面渲染,不依赖任何ViewModel、Hilt相关代码
路由层代码示例:
@Composable fun HomeScreenRoute( navigateToDetailsAction: () -> Unit, openCardDetailsAction: (Int) -> Unit, viewModel: HomeViewModel = hiltViewModel() ) { val cities = viewModel.cities.observeAsState(listOf()) HomeScreen( cities = cities.value, navigateToDetailsAction = navigateToDetailsAction, openCardDetailsAction = openCardDetailsAction ) }
纯UI层代码示例:
@Composable fun HomeScreen( cities: List<City>, navigateToDetailsAction: () -> Unit, openCardDetailsAction: (Int) -> Unit ) { Scaffold( topBar = { HomeAppBar() }, floatingActionButton = { HomeFab(navigateToDetailsAction) } ) { HomeContent(cities) { id -> openCardDetailsAction(id) } } }
- 改写预览函数,直接给纯UI层传模拟测试数据:
@Preview @Composable private fun HomeScreenPreview() { // 构造模拟的测试数据 val mockCities = listOf( City(id = 1, name = "北京"), City(id = 2, name = "上海"), City(id = 3, name = "广州") ) HomeScreen( cities = mockCities, navigateToDetailsAction = {}, openCardDetailsAction = {} ) }
- 原有跳转逻辑的地方,把调用
HomeScreen改成调用HomeScreenRoute即可,业务逻辑完全不受影响。
备选方案:传入模拟ViewModel
如果不想拆分分层,也可以手动构造模拟的ViewModel传入预览:
- 首先保留ViewModel作为
HomeScreen的公开参数(不要移到函数内部) - 构造模拟的HomeViewModel实例,传入模拟的Repository返回固定测试数据:
// 模拟Repository,返回固定测试数据 class FakeCityRepository: CityRepository(/* 构造模拟的AppDatabase或者直接重写allCities属性 */) { override val allCities = flowOf( listOf( City(id = 1, name = "北京"), City(id = 2, name = "上海") ) ) } // 模拟ViewModel class FakeHomeViewModel: HomeViewModel(FakeCityRepository()) @Preview @Composable private fun HomeScreenPreview() { HomeScreen( viewModel = FakeHomeViewModel(), navigateToDetailsAction = {}, openCardDetailsAction = {} ) }
内容的提问来源于stack exchange,提问作者Pierre Vieira
相关产品推荐
相关产品推荐

