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.0

fun <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.KeyDescriptions
ViewModelProvider.NewInstanceFactory.VIEW\_MODEL\_KEYViewModelProvider 可以基于 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\_KEYcreateSavedStateHandle 所需的 ViewModelStoreOwner
SavedStateHandleSupport.DEFAULT\_ARGS\_KEYcreateSavedStateHandle 所需的 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

ViewModelProviderdefaultCreationExtras 也是通过 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() 方法,来提供 defaultCreationExtrasViewModelProvider,最终传入 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 子类,比如 AndroidViewModelAndroidViewModelFactory 以及 SavedStateHandler ViewModelSavedStateViewModelFactory 等等,都会由于 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.current

val 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,稍后对其进行介绍。

addInitializerVM::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))

}

刚提到的 InitializerViewModelFactorcreate 时,通过 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

回到顶部