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)

搞定。就这么简单。

💡 小提示: 记得加 @Suppress("UNCHECKED_CAST"),不然编译器会警告。虽然不影响运行,但看着烦。

带多个参数的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传进去,那就要小心内存泄漏了。

⚠️ 避坑指南: 我曾经在一个项目里直接把Activity实例传给了ViewModel,结果屏幕旋转后Activity重建,ViewModel还持有旧的Activity引用,直接内存泄漏。后来我改用ApplicationContext或者依赖注入框架才解决。

依赖注入基础:从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之间的关系:

ViewModelFactory 工作流程 Activity / Fragment ViewModelProvider ViewModelProvider.Factory ViewModel 实例 调用 get() 调用 create() 返回新实例 缓存命中? 如果缓存中有ViewModel实例,直接返回,不会调用Factory的create() 如果缓存中没有,则通过Factory创建并存入缓存

流程其实很简单: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