Android 创建初始化时带有参数的 ViewModel
实际上这是在 ShizuruNotes 开发过程中碰到的一个比较基础的问题,但印象很深刻而且很容易被陷进去,因此简单记录一下。
首先我们既然要使用 ViewModel
,那么肯定会建立一个继承 ViewModel
的子类,比如:
1 | class SharedViewModelChara : ViewModel() { |
然后我们通常需要在 Fragment
中创建一个 ViewModel
去使用它,这也很简单,只要:
1 | val sharedChara = ViewModelProvider(requireActivity())[SharedViewModelChara::class.java] |
一行就能完事了。
那么问题就来了。
如果我们的 ViewModel
在构造时需要带参数,比如:
1 | class SharedViewModelChara( |
这种情况改怎么办?
当然我们完全可以另辟蹊径,在构造 ViewModel
时不带任何参数,只需要在里面声明一个 lateinit
参数,初始化完成之后再立即给它赋值就可以了:
1 | class SharedViewModelChara : ViewModel() { |
不过这种方法明显很不优雅,而且 Google 不可能没有想到 ViewModel
需要在构造时带参数的这种情况。
于是我翻遍了资料,终于在 ViewModelProvider
里找到了一个接口:
1 | public interface Factory { |
并且还发现 ViewModelProvider
有一个初始化方法是:
1 | /** |
简单分析一下,ViewModelProvider.Factory
这个接口有一个叫做 create
的方法需要去实现,这个 create
方法的作用是用来初始化 ViewModel
,也就是说我们所需要用到的 ViewModel
的初始化过程都可以在这里面进行,并且这个 Factory
可以在 ViewModelProvider
初始化时以参数的方式传进去。
那么我们要做的事情就简单了,只需要实现 ViewModelProvider.Factory
接口,并且在这个接口初始化时将我们要用到的参数传进去,然后在实现 create
方法时调用它去初始化我们真正想用到的 ViewModel
就可以了。
最后我们实现的接口如下:
1 | class SharedViewModelCharaFactory( |
看上去好大一堆。其实这里面抛出异常那一大堆玩意可以不用管,那只是为了规范运行时出现错误的报错信息,只需要看上半截就可以了。
简单来说就是,将我们需要的参数在 Factory
初始化时传进来,然后在利用反射 newInstance()
时使用就可以了。
在 Fragment
中实际使用时只需要使用上面的 Factory
:
1 | val sharedChara = ViewModelProvider( |
就搞定了。
延伸一下,其实我们看下源码就知道,ViewModelProvider
本身里面自带了一个叫做 NewInstanceFactory
的静态类去实现了 Factory
接口,也就是说我们平常用的无参数 ViewModel
都是通过这个 Factory
来创建的。所以其实无论用哪种方法,最终创建 ViewModel
实例都还是要使用 Factory
。