You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

依赖ViewModel的Composable函数如何实现正常预览?

错误根因

Compose 预览环境没有提供完整的ViewModelStore实现,也没有初始化Hilt依赖注入容器,所以调用hiltViewModel()创建ViewModel实例的时候就会抛出该异常,和你把ViewModel放在参数默认值还是函数内部没有关系,只要预览路径执行了hiltViewModel()就会报错。

最优解决方案:UI层与注入层拆分

这个方案同时解决预览和可测试性问题:

  1. 把原有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) }
    }
}
  1. 改写预览函数,直接给纯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 = {}
    )
}
  1. 原有跳转逻辑的地方,把调用HomeScreen改成调用HomeScreenRoute即可,业务逻辑完全不受影响。
备选方案:传入模拟ViewModel

如果不想拆分分层,也可以手动构造模拟的ViewModel传入预览:

  1. 首先保留ViewModel作为HomeScreen的公开参数(不要移到函数内部)
  2. 构造模拟的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.04 20:18:02