(作者很懒,只提供了思路和代码示例,由ChatGPT完成撰写)
在 Jetpack Compose 开发中,Preview 是一个重要的工具,它允许我们在 IDE 中快速预览 UI,而无需运行整个应用程序。然而,预览环境并不能直接注入依赖(例如 Dagger-Hilt 提供的 ViewModel 或其他依赖项)。为了解决这一问题,可以使用 Java 的动态代理来模拟接口的调用,简化 ViewModel 的依赖管理。
动态代理的基本实现
以下代码展示了一个通用的 fake 函数,它使用 Java 动态代理机制为任意接口创建一个模拟对象。在 Preview 中,我们可以通过这个代理对象来快速构造需要依赖的 ViewModel。
inline fun <reified T : Any> fake(crossinline handler: (Any?) -> Any? = { null }): T {
return Proxy.newProxyInstance(
T::class.java.classLoader,
arrayOf(T::class.java),
object : InvocationHandler {
override fun invoke(proxy: Any?, method: Method?, args: Array<out Any?>?): Any? {
return handler.invoke(method)
}
}
) as T
}
这个函数接受一个泛型类型 T,它会为给定的接口生成一个动态代理。通过 handler 参数,你可以自定义方法调用时的行为。例如,记录日志或返回特定值。
示例:在 Preview 中使用动态代理模拟 ViewModel 的依赖
以下是一个完整的示例,通过动态代理为 HomeViewModel 提供虚拟的依赖,从而实现 Compose UI 的预览。
ViewModel 示例:
@HiltViewModel
class HomeViewModel @Inject constructor(
private val accountRepo: AccountRepo,
private val settingRepo: SettingRepository,
private val vehicleRepo: VehicleRepo,
) : ViewModel()
Compose 预览函数:
@Preview(
widthDp = SCREEN_WIDTH,
heightDp = SCREEN_HEIGHT,
uiMode = UI_MODE_NIGHT_YES
)
@Composable
fun PreviewDarkHome() {
PreviewContainer {
HomeScreen(
HomeViewModel(
fakeAccountRepo = fake(), // 使用动态代理模拟 AccountRepo
settingRepo = fake(), // 使用动态代理模拟 SettingRepository
vehicleRepo = fake() // 使用动态代理模拟 VehicleRepo
)
)
}
}
在预览中,我们不需要真实的依赖实现,只需要满足 ViewModel 的构造函数需求即可。通过 fake() 创建的动态代理对象,可以替代实际的依赖。
动态代理的优势
- 简化依赖管理:在预览环境中,我们无需配置复杂的依赖注入框架,只需要动态代理即可快速模拟依赖。
- 提高开发效率:通过动态代理,开发者可以专注于 UI 开发,而不必等待依赖模块的实现。
- 灵活性:可以通过
handler自定义每个方法的返回值,从而满足特定场景下的测试需求。
注意事项
- 仅限预览使用:动态代理生成的对象是空实现,不适合作为生产环境中的实际依赖。
- 不可上线:动态代理生成的对象无法处理复杂的逻辑,且不具备任何真实功能,仅用于满足构造函数和预览的需求。
- 性能开销:动态代理有一定的性能开销,但在预览环境中影响可以忽略不计。
总结
通过 Java 动态代理和 fake 函数,我们可以在 Jetpack Compose 的预览环境中轻松实现 ViewModel 的依赖注入。这种方法避免了编写繁琐的测试依赖,同时提高了 UI 开发的效率。在实际项目中,只需将真实的依赖替换为动态代理即可完成高效的预览工作。
这种优雅的解决方案不仅适合开发初期的快速迭代,还能为大型项目中的模块化开发提供便捷的支持。