如何基于上一个Fragment参数向Hilt ViewModel注入对应Foo子类?
解决方案
核心通过辅助工厂模式实现需求,完全规避硬编码实例化和EntryPoint的不优雅写法,同时天然支持测试。
第一步:重构Foo子类,移除EntryPoint依赖
你当前的Dependency1、Dependency2本身已经支持Hilt注入,完全不需要在Foo子类中通过EntryPoint手动拿依赖,直接把依赖放到构造方法上加@Inject即可:
interface Foo { fun doThis() fun doThat() } // 直接通过构造注入依赖,无需耦合Context class Bar1 @Inject constructor(val dependency1: Dependency1): Foo { // 重写方法直接使用dependency1即可 ... } class Bar2 @Inject constructor(val dependency2: Dependency2): Foo { // 重写方法直接使用dependency2即可 ... } // 原有依赖类无需修改 class Dependency1 @Inject constructor(val yetAnotherDep: ButWhosCounting) class Dependency2 @Inject constructor(val yetAnotherDep: ButWhosCounting)
第二步:创建可注入的Foo工厂
工厂本身由Hilt注入管理,内部持有两个子类的Provider,根据传入的参数动态返回对应Foo实例:
class FooFactory @Inject constructor( private val bar1Provider: Provider<Bar1>, private val bar2Provider: Provider<Bar2> ) { fun create(fooType: String): Foo { return when(fooType) { "bar1" -> bar1Provider.get() "bar2" -> bar2Provider.get() else -> throw IllegalArgumentException("不支持的Foo类型") } } }
这里用Provider包装子类是为了懒加载,只有实际用到对应类型的时候才会实例化,不会额外产生性能开销。
第三步:ViewModel中根据SavedStateHandle参数获取实例
无需直接注入Foo实例,注入工厂和SavedStateHandle后,初始化阶段根据上一个页面传的参数生成对应Foo实例即可:
@HiltViewModel class YourViewModel @Inject constructor( private val fooFactory: FooFactory, savedStateHandle: SavedStateHandle ): ViewModel() { // 从上一个Fragment传递的参数中取Foo类型标识,示例key为foo_type private val fooType: String = savedStateHandle["foo_type"] ?: error("缺少Foo类型参数") // 直接拿到对应子类的Foo实例,和你期望的写法效果完全一致 val thatFooINeed: Foo = fooFactory.create(fooType) }
方案优势
- 完全符合Hilt注入规范,没有硬编码的实例化逻辑,后续修改Foo子类的构造参数不需要调整上层代码
- 天然支持测试:单元测试时可以直接Mock FooFactory返回测试用的Foo实现,不需要依赖Hilt运行环境
- 消除了EntryPoint和Context的耦合,代码更简洁易维护
如果一定要实现@Inject lateinit var thatFooINeed: Foo的写法,也可以结合Hilt的自定义Qualifier + 动态绑定实现,但对于这种由运行时参数决定实现类的场景,工厂模式是更清晰、更易维护的选择。
内容的提问来源于stack exchange,提问作者MayNotBe
相关产品推荐
相关产品推荐

