您的位置:首页 > 其它

ViewModel的创建

2021-12-23 01:10 881 查看

ViewModel的创建

ViewModel本身只是

ViewModel
这个类的子类:

class MainViewModel: ViewModel() {
}

在屏幕旋转UI重建的时候, 它是如何拥有保持数据的能力的呢? 它又是何时被清理的呢?
答案全跟它是如何创建, 保存的有关系.

本文回顾一下创建ViewModel的几种常见写法.
注: 本文中的图并不是严格意义的时序图(也不符合规范), 只是为了简略表示一下代码中的调用关系.

原生手动创建ViewModel

当ViewModel没有构造参数

当ViewModel没有参数的时候很简单:

class MainViewModel: ViewModel() {}

class MainActivity : ComponentActivity() {

private lateinit var viewModel: MainViewModel

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel = ViewModelProvider(this).get(MainViewModel::class.java)
}
}

当然不是随便就

new
出来的啦, 得通过
ViewModelProvider
来get.

注意: 这句不能在Activity的

onCreate()
之前调用, 也意味着你不能声明字段直接赋值.

否则你就会得到这个报错:

Caused by: java.lang.IllegalStateException: Your activity is not yet attached to the Application instance. You can't request ViewModel before onCreate call.

此时的方法调用大概是这样:

从这里我们看到

ViewModelProvider
这个工具人要借助其他两个东西来提供ViewModel:

  • ViewModelStore
    : 负责存储ViewModel.
  • Factory
    : 负责实例化具体的ViewModel类型.
    请记住这两个知识点, 后面要考.

在上面这个最简单的例子中:

  • Activity是
    ViewModelStoreOwner
    , 它可以
    getViewModelStore()

    ViewModelStoreOwner
    (比如Activity)因为configuration changes重建, 新的owner仍然会get这个旧的
    ViewModelStore
    实例.
  • 我们没有传
    Factory
    , 所以最终用的是没有参数的
    NewInstanceFactory
    .

当ViewModel有构造参数

但是通常, ViewModel会需要一些依赖, 我们就需要从构造传入一些参数

class MainViewModel(
private val repository: MainRepository,
) : ViewModel()

此时我们的工厂就需要自己实现了:
我们需要把依赖对象传给工厂, 好让它构造ViewModel的时候能用上:

class MyViewModelFactory constructor(private val repository: MainRepository) :
ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(MainViewModel::class.j
32da
ava)) {
return MainViewModel(repository) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
}

接着把我们的工厂传给

ViewModelProvider
:

class MainActivity : ComponentActivity() {

private lateinit var viewModel: MainViewModel
private val repository: MainRepository = MainRepository()
private val viewModelFactory: MyViewModelFactory = MyViewModelFactory(repository)

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel = ViewModelProvider(this, viewModelFactory).get(MainViewModel::class.java)
}
}

此时的过程大概是这样:

AndroidX来帮忙

感谢AndroidX

activity-ktx
(
fragment-ktx
里也有Fragment版本的)包里的
by viewModels()
属性代理,
我们上述的代码可以简化成这样:

class MyViewModelFactory constructor(private val repository: MainRepository) :
ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(MainViewModel::class.java)) {
return MainViewModel(repository) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
}

class MainActivity : ComponentActivity() {

private val viewModel: MainViewModel by viewModels {
viewModelFactory
}
private val repository: MainRepository = MainRepository()
private val viewModelFactory: MyViewModelFactory = MyViewModelFactory(repository)

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

// use viewModel
}
}

工厂依然自己写, 简化了provider get的部分:

  • 扩展方法, 隐含activity对象.
  • lazy规避了生命周期的问题, 只要使用ViewModel的地方不在
    onCreate
    之前就行.

ViewModel没有参数的时候更简单:

private val viewModel: MainViewModel by viewModels()

Dagger时代

有了dagger, 我们可以在构造上标记

@Inject
告诉dagger帮我们创建repository和viewModelFactory:

class MainRepository @Inject constructor(){}
@Singleton
class MyViewModelFactory @Inject constructor(private val repository: MainRepository) :
ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(MainViewModel::class.java)) {
return MainViewModel(repository) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
}

但是我们却不能对ViewModel这样做, 因为ViewModel应该被存储在ViewModelStore里, 而它是由activity提供的.

我们会注入viewModelFactory, 然后用它来创建ViewModel:

class MainActivity : ComponentActivity() {

private val viewModel: MainViewModel by viewModels {
viewModelFactory
}
@Inject
lateinit var viewModelFactory: MyViewModelFactory
}

因为你用的是dagger, 你会需要在

onCreate()
里写类似这样的东西:

(applicationContext as MyApplication).appComponent.inject(this)

这里只是用DI框架简化了依赖和工厂的构造.

Hilt时代

在ViewModel上标记:

@HiltViewModel
, 构造标记:
@Inject
:

@HiltViewModel
class MainViewModel @Inject constructor(
private val repository: MainRepository,
) : ViewModel() {
}

依赖们也可以构造标记

@Inject
:

class MainRepository @Inject constructor(){}

在Activity加上注解

@AndroidEntryPoint
,
也用了
by viewModels()
:

@AndroidEntryPoint
class MainActivity : ComponentActivity() {

private val viewModel: MainViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

// use viewModel
}
}

请注意, 和dagger不同的是, 这里并不需要注入工厂(也不需要写工厂), Hilt用的是自己的工厂

HiltViewModelFactory
.

这个过程:

Compose的ViewModel()方法

在Compose中获取一个ViewModel非常简单, 可以只用一个

viewModel()
方法.
(这个方法在
androidx.lifecycle:lifecycle-viewmodel-compose
依赖包里).

@Composable
fun Greeting(name: String) {
val viewModel: MainViewModel = viewModel()
// use viewModel
}

Activity的onCreate()中setContent是Compose, 于是在这里就设置好各种owners,
与ViewModel相关的就是这个

LocalViewModelStoreOwner
, 之后包在里面的内容就可以随时获取owner, 得到ViewModelStore和ViewModel了.

Compose的hiltViewModel()方法

上面的

viewModel()
方法不管在哪里获取, ViewModel的scope都是和当前的Activity或Fragment绑定.
假如我们有多个composable的界面呢?

使用

hiltViewModel()
这个composable方法我们可以获取到一个scope到某个导航目的地的ViewModel.
(这个方法在
androidx.hilt:hilt-navigation-compose
依赖包里).

NavHost(navController = navController, startDestination = "friendslist") {
composable("friendslist") {
val viewModel = hiltViewModel<MainViewModel>()
FriendsList(viewModel = viewModel, navHostController = navController)
}
...
}

这种方式获取的ViewModel, 它的生命周期是和这个导航目的地相关的, 当退出这个界面, ViewModel就被clear, 下次再进就又是新的对象.

此时的owner终于不再是Activity或Fragment, 而是

NavBackStackEntry
.
导航这个话题就先不展开这里讲了.

总结

ViewModel的创建很关键, 关系到它的生命周期.
手动创建比较麻烦, 很多样板代码.
本文总结了几种常见的创建方式, 希望读者看完后能有更清晰的理解, 每种方式都是怎么回事, 那些方便的工具替我们做了什么.

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: