CreationExtras 来了,创建 ViewModel 的新方式
Androidx-Lifecycle 在近期迈入到了 2.5.0 版本,其中最重要的一个变化是引入了 CreatioinExtras
的概念。一句话概括 CreationExtras
的作用:帮助我们在创建 ViewModel
时更优雅地获取初始化参数
1. 现状的问题
先回顾一下目前为止的 ViewModel 的创建方式
val vm : MyViewModel by viewModels()
我们知道其内部其实是通过 ViewModelProvider
获取 VM。当 VM 不存在时使用 ViewModelProvider.Factory
创建 VM 实例。默认 Factory 使用反射创建实例,所以 VM 的构造函数不能有参数 。如果希望使用初始化参数创建 VM 则需要定义自己的 Factory :
class MyViewModelFactory( private val application: Application,
private val param: String
) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return MyViewModel(application, param) as T
}
}
然后,在 Activity 或 Fragment 中声明 VM 的现场,创建自定义 Factory:
val vm : MyViewModel by viewModels { MyViewModelFactory(application, "some data")
}
"some data"
可能来自 Activity 的 Intent
或者 Fragment 的 argements
,所以一个真实项目中为了准备 VM 参数的代码可能要复杂得多。一个持有“状态”的 Factory 不利于复用,为了保证 VM 创建时的正确性,往往需要为每个 VM 都配备专属的 Factory,失去了“工厂”原本存在的意义。随着 App 的页面越发复杂,每一处需要共享 VM 的地方都要单独构建 Factory ,冗余代码也越来越多。
除了直接使用 ViewModelProvider.Factory
,还有其他几种初始化方式,例如借助 SavedStateHandler
等,但是无论何种方式本质上都是借助了 ViewModelProvider.Factory
,都免不了上述 Stateful Factory 的问题。
2. CretionExtras 是怎么解决的?
Lifecycle 2.5.0-alpha01 开始引入了 CreationExtras
的概念,它替代了 Factory 的任务为 VM 初始化所需的参数,Factory 无需再持有状态。
我们知道 ViewModelProvider.Factory
使用 create(modelClass)
创建 VM ,在 2.5.0 之后方法签名发生了如下变化:
//before 2.5.0fun <T : ViewModel> create(modelClass: Class<T>): T
//after 2.5.0
fun <T : ViewModel> create(modelClass: Class<T>, extras: CreationExtras): T
2.5.0 之后在创建 VM 时可以通过 extras
获取所需的初始化参数。定义 Factory 变成下面这样:
class ViewModelFactory : ViewModelProvider.Factory { override fun <T : ViewModel> create(modelClass: Class<T>, extras: CreationExtras): T {
return when (modelClass) {
MyViewModel::class.java -> {
// 通过extras获取自定义参数
val params = extras[extraKey]!!
// 通过extras获取application
val application = extras[ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY]
// 创建 VM
MyViewModel(application, params)
}
// ...
else -> throw IllegalArgumentException("Unknown class $modelClass")
} as T
}
}
一个 Stateless 的 Factory 可以更好地复用。我们可以在一个 Factory 中使用 when
处理所有类型的 VM 创建,一次定义多处使用。
3. CreationExtras.Key
上面代码中使用 extras[key]
获取初始化参数,key
的类型是 CreationExtras.Key
。
看一下 CreationExtras
的定义就明白了,成员 map
后文会介绍到
public sealed class CreationExtras { internal val map: MutableMap<Key<*>, Any?> = mutableMapOf()
/**
* Key for the elements of [CreationExtras]. [T] is a type of an element with this key.
*/
public interface Key<T>
/**
* Returns an element associated with the given [key]
*/
public abstract operator fun <T> get(key: Key<T>): T?
/**
* Empty [CreationExtras]
*/
object Empty : CreationExtras() {
override fun <T> get(key: Key<T>): T? = null
}
}
Key
的泛型 T
代表对应 Value
的类型。相对于 Map<K,V>
,这种定义方式可以更加类型安全地获取多种类型的键值对,CoroutineContext
等也是采用这种设计。
如下, 我们可以自定义一个 String
类型数据的 Key
private val extraKey = object : CreationExtras.Key<String> {}
系统以及提供了几个预置的 Key 供使用:
CreationExtras.Key | Descriptions |
---|---|
ViewModelProvider.NewInstanceFactory.VIEW\_MODEL\_KEY | ViewModelProvider 可以基于 key 区分多个 VM 实例,VIEW\_MODEL\_KEY 用来提供当前 VM 的这个 key |
ViewModelProvider.AndroidViewModelFactory.APPLICATION\_KEY | 提供当前 Application context |
SavedStateHandleSupport.SAVED\_STATE\_REGISTRY\_OWNER\_KEY | 提供创建 createSavedStateHandle 所需的 SavedStateRegistryOwner |
SavedStateHandleSupport.VIEW\_MODEL\_STORE\_OWNER\_KEY | createSavedStateHandle 所需的 ViewModelStoreOwner |
SavedStateHandleSupport.DEFAULT\_ARGS\_KEY | createSavedStateHandle 所需的 Bundle |
后三个 Key
都跟 SavedStateHandle
的创建有关,后文会进行介绍
4. 如何创建 CreationExtras
那么我们如何创建 Extras
并传入 create(modelClass, extras)
的参数中呢?
从 CreatioinExtras
的定义中我们知道它是一个密封类,因此无法直接实例化。我们需要使用其子类 MutableCreationExtras
来创建实例,这是一种读写分离的设计思想,保证了使用处的不可变性。
顺便看一眼 MutableCreationExtras
的实现吧,非常简单:
public class MutableCreationExtras(initialExtras: CreationExtras = Empty) : CreationExtras() { init {
map.putAll(initialExtras.map)
}
/**
* Associates the given [key] with [t]
*/
public operator fun <T> set(key: Key<T>, t: T) {
map[key] = t
}
public override fun <T> get(key: Key<T>): T? {
@Suppress("UNCHECKED_CAST")
return map[key] as T?
}
}
还记得 CreationExtras
中的 map
成员吗,这里使用到了。从 initialExtras
的使用可看出来 CreationExtras
可以通 merge 实现内容的继承,例如:
val extras = MutableCreationExtras().apply { set(key1, 123)
}
val mergedExtras = MutableCreationExtras(extras).apply {
set(key2, "test")
}
mergedExtras[key1] // => 123
mergedExtras[key2] // => test
ViewModelProvider
的 defaultCreationExtras
也是通过 merge 实现的传递。看一下获取 VM 的代码:
public open operator fun <T : ViewModel> get(key: String, modelClass: Class<T>): T { val viewModel = store[key]
val extras = MutableCreationExtras(defaultCreationExtras)
extras[VIEW_MODEL_KEY] = key
return factory.create(
modelClass,
extras
).also { store.put(key, it) }
}
可以发现 extras
默认会继承一个 defaultCreationExtras
5. 默认参数 DefaultCreationExtras
上面提到的 defaultCreationExtras
实际上是 ViewModelProvider
从当前 Activity 或者 Fragment 中获取的。
以 Activity 为例,我们可以通过重写 getDefaultViewModelCreationExtras()
方法,来提供 defaultCreationExtras
给 ViewModelProvider
,最终传入 create(modelClass, extras)
的参数
注意: Activity 1.5.0-alpha01 和 Fragment 1.5.0-alpha01 之后才能重写 getDefaultViewModelCreationExtras 方法。之前的版本中,访问 defaultCreationExtras 将返回 CreationExtras.Empty
看一下 ComponentActivity
的默认实现:
public CreationExtras getDefaultViewModelCreationExtras() { MutableCreationExtras extras = new MutableCreationExtras();
if (getApplication() != null) {
extras.set(ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY, getApplication());
}
extras.set(SavedStateHandleSupport.SAVED_STATE_REGISTRY_OWNER_KEY, this);
extras.set(SavedStateHandleSupport.VIEW_MODEL_STORE_OWNER_KEY, this);
if (getIntent() != null && getIntent().getExtras() != null) {
extras.set(SavedStateHandleSupport.DEFAULT_ARGS_KEY, getIntent().getExtras());
}
return extras;
}
有 Application 以及 Intent 等,前面介绍的预设 Key 都是这里注入的。
当我们需要使用 Activity 的 Intent 初始化 VM 时,代码如下:
object : ViewModelProvider.Factory { override fun <T : ViewModel> create(
modelClass: Class<T>,
extras: CreationExtras
): T {
// 使用 DEFAULT_ARGS_KEY 获取 Intent 中的 Bundle
val bundle = extras[DEFAULT_ARGS_KEY]
val id = bundle?.getInt("id") ?: 0
return MyViewModel(id) as T
}
}
6. 对 AndroidViewModel 和 SavedStateHandle 的支持
前面说了,CreationExtras
本质上就是让 Factory 变得无状态。以前为了构建不同参数类型的 ViewModel 而存在的各种特殊的 Factory 子类,比如 AndroidViewModel
的 AndroidViewModelFactory
以及 SavedStateHandler ViewModel
的 SavedStateViewModelFactory
等等,都会由于 CreationExtras
出现而逐渐退出舞台。
class CustomFactory : ViewModelProvider.Factory { override fun <T : ViewModel> create(modelClass: Class<T>, extras: CreationExtras): T {
return when (modelClass) {
HomeViewModel::class -> {
// Get the Application object from extras
val application = checkNotNull(extras[ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY])
// Pass it directly to HomeViewModel
HomeViewModel(application)
}
DetailViewModel::class -> {
// Create a SavedStateHandle for this ViewModel from extras
val savedStateHandle = extras.createSavedStateHandle()
DetailViewModel(savedStateHandle)
}
else -> throw IllegalArgumentException("Unknown class $modelClass")
} as T
}
}
如上,无论 Application
还是 SavedStateHandler
都可以统一从 CreationExtras
获取。
createSavedStateHandle()
扩展函数可以基于 CreationExtras
创建 SavedStateHandler
public fun CreationExtras.createSavedStateHandle(): SavedStateHandle { val savedStateRegistryOwner = this[SAVED_STATE_REGISTRY_OWNER_KEY]
val viewModelStateRegistryOwner = this[VIEW_MODEL_STORE_OWNER_KEY]
val defaultArgs = this[DEFAULT_ARGS_KEY]
val key = this[VIEW_MODEL_KEY]
return createSavedStateHandle(
savedStateRegistryOwner, viewModelStateRegistryOwner, key, defaultArgs
)
}
所需的 savedStateRegistryOwner
等参数也来自 CreationExtras
,此外,查看 SavedStateViewModelFactory
的最新代码可知,其内部实现也像上面那样基于 CreationExtras
重构过了。
7. 对 Compose 的支持
再来简单看看 Compose 中如何使用 CreationExtras
。
注意 Gradle 依赖升级如下:
- androidx.activity:activity-compose:1.5.0-alpha01
- androidx.lifecycle:lifecycle-viewmodel-compose:2.5.0-alpha01
val owner = LocalViewModelStoreOwner.currentval defaultExtras =
(owner as? HasDefaultViewModelProviderFactory)?.defaultViewModelCreationExtras
?: CreationExtras.Empty
val extras = MutableCreationExtras(defaultExtras).apply {
set(extraKeyId, 123)
}
val factory = remember {
object : ViewModelProvider.Factory {
override fun <T : ViewModel> create(
modelClass: Class<T>,
extras: CreationExtras
): T {
val id = extras[extraKeyId]!!
return MainViewModel(id) as T
}
}
}
val viewModel = factory.create(MainViewModel::class.java, extras)
可以通过 LocalViewModelStoreOwner
获取当前的 defaultExtras
,然后根据需要添加自己的 extras
即可。
8. 使用 DSL 创建 ViewModelFactory
2.5.0-alpha03 新增了用 DSL 创建 ViewModelFactory 的方式,
注意 Gradle 依赖升级如下:
- androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.0-alpha03
- androidx.fragment:fragment-ktx:1.5.0-alpha03
使用效果如下:
val factory = viewModelFactory { initializer {
TestViewModel(this[key])
}
}
viewModelFactory{...}
与 intializer{...}
的定义分别如下:
public inline fun viewModelFactory( builder: InitializerViewModelFactoryBuilder.() -> Unit
): ViewModelProvider.Factory =
InitializerViewModelFactoryBuilder().apply(builder).build()
inline fun <reified VM : ViewModel> InitializerViewModelFactoryBuilder.initializer(
noinline initializer: CreationExtras.() -> VM
) {
addInitializer(VM::class, initializer)
}
InitializerViewModelFactorBuilder
用来 build 一个 InitializerViewModelFactory
,稍后对其进行介绍。
addInitializer
将 VM::class
与对应的 CreationExtras.() -> VM
存入 initializers
列表:
private val initializers = mutableListOf<ViewModelInitializer<*>>()fun <T : ViewModel> addInitializer(clazz: KClass<T>, initializer: CreationExtras.() -> T) {
initializers.add(ViewModelInitializer(clazz.java, initializer))
}
刚提到的 InitializerViewModelFactor
在 create
时,通过 initializers
创建 VM,代码如下:
class InitializerViewModelFactory( private vararg val initializers: ViewModelInitializer<*>
) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>, extras: CreationExtras): T {
var viewModel: T? = null
@Suppress("UNCHECKED_CAST")
initializers.forEach {
if (it.clazz == modelClass) {
viewModel = it.initializer.invoke(extras) as? T
}
}
return viewModel ?: throw IllegalArgumentException(
"No initializer set for given class ${modelClass.name}"
)
}
}
由于 initializers
是一个列表,所以可以存储多个 VM 的创建信息,因此可以通过 DSL 配置多个 VM 的创建:
val factory = viewModelFactory { initializer {
MyViewModel(123)
}
initializer {
MyViewModel2("Test")
}
}
以上是 CreationExtras 来了,创建 ViewModel 的新方式 的全部内容, 来源链接: utcz.com/z/267600.html