Android 创建初始化时带有参数的 ViewModel

实际上这是在 ShizuruNotes 开发过程中碰到的一个比较基础的问题,但印象很深刻而且很容易被陷进去,因此简单记录一下。

首先我们既然要使用 ViewModel,那么肯定会建立一个继承 ViewModel 的子类,比如:

1
2
3
class SharedViewModelChara : ViewModel() {
......
}

然后我们通常需要在 Fragment 中创建一个 ViewModel 去使用它,这也很简单,只要:

1
val sharedChara = ViewModelProvider(requireActivity())[SharedViewModelChara::class.java]

一行就能完事了。

那么问题就来了。

如果我们的 ViewModel 在构造时需要带参数,比如:

1
2
3
4
5
class SharedViewModelChara(
private val someThing: Any
) : ViewModel() {
......
}

这种情况改怎么办?

当然我们完全可以另辟蹊径,在构造 ViewModel 时不带任何参数,只需要在里面声明一个 lateinit 参数,初始化完成之后再立即给它赋值就可以了:

1
2
3
4
class SharedViewModelChara : ViewModel() {
lateinit var someThing: Any
......
}

不过这种方法明显很不优雅,而且 Google 不可能没有想到 ViewModel 需要在构造时带参数的这种情况。

于是我翻遍了资料,终于在 ViewModelProvider 里找到了一个接口:

1
2
3
4
5
6
7
8
9
10
11
12
public interface Factory {
/**
* Creates a new instance of the given {@code Class}.
* <p>
*
* @param modelClass a {@code Class} whose instance is requested
* @param <T> The type parameter for the ViewModel.
* @return a newly created ViewModel
*/
@NonNull
<T extends ViewModel> T create(@NonNull Class<T> modelClass);
}

并且还发现 ViewModelProvider 有一个初始化方法是:

1
2
3
4
5
6
7
8
9
10
11
12
/**
* Creates {@code ViewModelProvider}, which will create {@code ViewModels} via the given
* {@code Factory} and retain them in a store of the given {@code ViewModelStoreOwner}.
*
* @param owner a {@code ViewModelStoreOwner} whose {@link ViewModelStore} will be used to
* retain {@code ViewModels}
* @param factory a {@code Factory} which will be used to instantiate
* new {@code ViewModels}
*/
public ViewModelProvider(@NonNull ViewModelStoreOwner owner, @NonNull Factory factory) {
this(owner.getViewModelStore(), factory);
}

简单分析一下,ViewModelProvider.Factory 这个接口有一个叫做 create 的方法需要去实现,这个 create 方法的作用是用来初始化 ViewModel,也就是说我们所需要用到的 ViewModel 的初始化过程都可以在这里面进行,并且这个 Factory 可以在 ViewModelProvider 初始化时以参数的方式传进去。

那么我们要做的事情就简单了,只需要实现 ViewModelProvider.Factory 接口,并且在这个接口初始化时将我们要用到的参数传进去,然后在实现 create 方法时调用它去初始化我们真正想用到的 ViewModel 就可以了。

最后我们实现的接口如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class SharedViewModelCharaFactory(
private val someThing: Any
) : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return try {
modelClass.getConstructor(SharedViewModelChara::class.java)
.newInstance(someThing)
} catch (e: NoSuchMethodException) {
throw RuntimeException("Cannot create an instance of $modelClass", e)
} catch (e: IllegalAccessException) {
throw RuntimeException("Cannot create an instance of $modelClass", e)
} catch (e: InstantiationException) {
throw RuntimeException("Cannot create an instance of $modelClass", e)
} catch (e: InvocationTargetException) {
throw RuntimeException("Cannot create an instance of $modelClass", e)
}
}
}

看上去好大一堆。其实这里面抛出异常那一大堆玩意可以不用管,那只是为了规范运行时出现错误的报错信息,只需要看上半截就可以了。

简单来说就是,将我们需要的参数在 Factory 初始化时传进来,然后在利用反射 newInstance() 时使用就可以了。

Fragment 中实际使用时只需要使用上面的 Factory

1
2
3
4
val sharedChara = ViewModelProvider(
requireActivity(),
SharedViewModelCharaFactory("para")
)[SharedViewModelChara::class.java]

就搞定了。

延伸一下,其实我们看下源码就知道,ViewModelProvider 本身里面自带了一个叫做 NewInstanceFactory 的静态类去实现了 Factory 接口,也就是说我们平常用的无参数 ViewModel 都是通过这个 Factory 来创建的。所以其实无论用哪种方法,最终创建 ViewModel 实例都还是要使用 Factory


References:
https://medium.com/koderlabs/viewmodel-with-viewmodelprovider-factory-the-creator-of-viewmodel-8fabfec1aa4f

 Comments
Comment plugin failed to load
Loading comment plugin