13、ViewModelFactory:自定义ViewModelFactory、带参数的ViewModel创建、依赖注入基础
好,咱们今天聊点实战中绕不开的东西——ViewModelFactory。
说实话,我见过太多新手在ViewModel这里翻车了。明明架构选的是MVVM,结果ViewModel里啥参数都传不进去,最后硬生生把数据塞进Application或者用静态变量。嗯,这味道不对。
你想想看,ViewModel本身是由系统管理的,它的构造函数默认是空参。但实际项目中,我们经常需要传参数——比如用户ID、网络仓库、数据库实例。这时候,你就需要ViewModelFactory了。
为什么需要ViewModelFactory?
先看一个最基础的场景。假设我们有一个UserViewModel,需要传入一个userId:
class UserViewModel(private val userId: String) : ViewModel() {
private val userLiveData = MutableLiveData<User>()
fun getUser(): LiveData<User> = userLiveData
init {
loadUser()
}
private fun loadUser() {
// 根据userId加载用户数据
}
}
这段代码看着没问题吧?但当你试图在Activity或Fragment里创建它时,问题就来了:
// 这样写会报错!
val viewModel = ViewModelProvider(this).get(UserViewModel::class.java)
为什么?因为ViewModelProvider默认调用的是无参构造函数。你传了userId,它不认识。
这时候,ViewModelFactory就是那个「中间人」。它告诉系统:嘿,这个ViewModel需要特殊构造,我来帮你搞定。
自定义ViewModelFactory:手写一个
我个人习惯把Factory定义成ViewModel的内部伴生对象,这样结构清晰,用起来也方便:
class UserViewModel(private val userId: String) : ViewModel() {
// 自定义Factory
companion object {
class Factory(private val userId: String) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return UserViewModel(userId) as T
}
}
}
}
然后在Activity里这样用:
val factory = UserViewModel.Factory("user_12345")
val viewModel = ViewModelProvider(this, factory).get(UserViewModel::class.java)
搞定。就这么简单。
带多个参数的ViewModel创建
实际项目中,ViewModel往往不止一个参数。比如我们还需要传入一个Repository:
class UserViewModel(
private val userId: String,
private val userRepository: UserRepository
) : ViewModel() {
companion object {
class Factory(
private val userId: String,
private val userRepository: UserRepository
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return UserViewModel(userId, userRepository) as T
}
}
}
}
使用方式一样:
val repository = UserRepository()
val factory = UserViewModel.Factory("user_12345", repository)
val viewModel = ViewModelProvider(this, factory).get(UserViewModel::class.java)
嗯,这里要注意一点——Factory只负责创建,不负责管理依赖的生命周期。如果你把Activity或Context传进去,那就要小心内存泄漏了。
依赖注入基础:从Factory到DI
看到这里你可能会想:每次都要手动创建Factory,好麻烦。尤其是当项目大了,ViewModel多了,每个都写一个Factory,代码量不小。
没错,这就是依赖注入(DI)要解决的问题。
说白了,依赖注入就是「把创建对象的控制权交给别人」。你不需要自己new对象,而是告诉框架:我需要什么,你帮我准备好。
在Android里,最常用的DI框架是Dagger Hilt和Koin。咱们先看一个简单的Koin例子:
// 1. 定义模块
val appModule = module {
single { UserRepository() }
viewModel { UserViewModel(get(), get()) }
}
// 2. 在Application中启动Koin
class MyApp : Application() {
override fun onCreate() {
super.onCreate()
startKoin {
androidContext(this@MyApp)
modules(appModule)
}
}
}
// 3. 在Activity中直接获取ViewModel
class UserActivity : AppCompatActivity() {
private val viewModel: UserViewModel by viewModel()
}
看到区别了吗?你不再需要手写Factory了。Koin自动帮你处理了参数注入。get() 的意思就是「从容器里拿一个实例」。
我个人更推荐用Hilt,因为它和Android生命周期结合得更紧密。但Koin上手简单,适合中小型项目。怎么选?看团队和项目规模。
Factory vs DI:什么时候用什么?
| 场景 | 推荐方式 | 原因 |
|---|---|---|
| 只有1-2个参数,且不涉及复杂依赖 | 手写Factory | 简单直接,不需要引入额外框架 |
| 参数来自Activity/Fragment(如ID) | 手写Factory | DI框架不适合注入运行时参数 |
| 项目较大,依赖关系复杂 | Hilt / Koin | 减少样板代码,统一管理依赖 |
| 团队多人协作 | DI框架 | 规范统一,新人上手快 |
你看,没有银弹。手写Factory和DI框架各有适用场景。我见过一些团队,项目就三个Activity,硬上Hilt,结果配置半天,反而拖慢了进度。没必要。
核心逻辑图:ViewModelFactory的工作流程
下面这张图帮你理清ViewModel、Factory、ViewModelProvider之间的关系:
流程其实很简单:Activity请求ViewModel → ViewModelProvider检查缓存 → 缓存未命中 → 调用Factory的create() → 返回ViewModel实例。
嗯,这里有个关键点——ViewModelProvider会缓存ViewModel实例。同一个ViewModelProvider作用域内,多次get()返回的是同一个对象。这也是为什么ViewModel能存活过屏幕旋转的原因。
总结一下
- ViewModelFactory 是连接「带参构造函数」和「ViewModelProvider」的桥梁
- 手写Factory 适合参数少、依赖简单的场景
- DI框架(Hilt/Koin)适合大型项目,减少样板代码
- 注意内存泄漏:别把Activity/Context直接传给ViewModel
- 缓存机制:ViewModelProvider会缓存实例,同一个作用域内只创建一次
说实话,ViewModelFactory这个知识点不大,但卡住的人真不少。你想想看,如果连参数都传不进去,那ViewModel还有什么用?所以,把这个基础打牢,后面的状态管理才能玩得转。
📌 核心要点: ViewModelFactory的本质就是「自定义创建逻辑」。不管你是手写Factory还是用DI框架,最终目的都是让ViewModel能拿到它需要的依赖。理解了这个,你就掌握了ViewModel的「入口」。
公众号:蓝海资料掘金营,微信deep3321