ViewModel的日常使用封装 ViewModel基本使用

Kotlin协程基础及原理系列

Flow系列

扩展系列

笔者也只是一个普普通通的开发者,设计不一定合理,大家可以自行吸收文章精华,去糟粕。

ViewModel基本使用

在上一篇文中《封装DataBinding让你少写万行代码》中我们已经学会了如何封装DataBinding进行代码优化。本章节我们将在上一篇文章的基础上讲解对ViewModel的使用封装。

ViewModel基本使用

我们都知道基础的创建ViewModel方式是通过下面三种方式进行创建的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
KOTLIN复制代码class MainActivity05:AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val viewModel = ViewModelProvider(this).get(MainViewModel::class.java)
/** 或者携带实例化工厂
val viewModel = ViewModelProvider(this,MainViewModelFactory(MainRepository())).get(MainViewModel::class.java)
或者
val viewModel = ViewModelProvider(viewModelStore,MainViewModelFactory(MainRepository())).get(MainViewModel::class.java) */
viewModel.mUser.observe(this) {
Log.d("mUser","$it")
}
}
}

class MainViewModel:ViewModel() {
private val _user:MutableLiveData<User> = MutableLiveData(User(1,"测试"))
val mUser: LiveData<User> = _user
}

//创建一个最简单的ViewModel工厂
class MainViewModelFactory(
) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return MainViewModel() as T
}
}

因为我们只是为了演示所以MainViewModel我们只是简单创建了一个mUser: LiveData<User>对象供于MainActivity进行observe

在上面基础上我们通过引入lifecycle-extensions这个扩展库。

1
KOTLIN复制代码    implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'

我们通过它提供的方法ViewModelProviders这种方式创建ViewModel

1
2
3
4
5
6
7
8
9
10
11
KOTLIN复制代码class MainActivity:AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val viewModel = ViewModelProviders.of(this).get(MainViewModel::class.java)
viewModel.mUser.observe(this) {
Log.d("mUser","$it")
}
}
}

这种通过ViewModelProviders这种方式创建ViewModel,是基于lifecycle-extensions这个扩展库提供的方法的,我们可以看到实际上ViewModelProviders最终也是通过ViewModelProvider去创建。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
KOTLIN复制代码/**
* Utilities methods for {@link ViewModelStore} class.
* @deprecated Use the constructors for {@link ViewModelProvider} directly.
*/
@Deprecated
public class ViewModelProviders {
@Deprecated
@NonNull
@MainThread
public static ViewModelProvider of(@NonNull FragmentActivity activity) {
return new ViewModelProvider(activity);
}

@Deprecated
@NonNull
@MainThread
public static ViewModelProvider of(@NonNull FragmentActivity activity,
@Nullable Factory factory) {
if (factory == null) {
factory = activity.getDefaultViewModelProviderFactory();
}
return new ViewModelProvider(activity.getViewModelStore(), factory);
}

@Deprecated
@NonNull
@MainThread
public static ViewModelProvider of(@NonNull Fragment fragment) {
return new ViewModelProvider(fragment);
}

@Deprecated
@NonNull
@MainThread
public static ViewModelProvider of(@NonNull Fragment fragment, @Nullable Factory factory) {
if (factory == null) {
factory = fragment.getDefaultViewModelProviderFactory();
}
return new ViewModelProvider(fragment.getViewModelStore(), factory);
}
}

这个方法在早期的版本中是使用到最多的,目前网上很多文章也还是使用的这种写法。但是我们可以看到在高版本的lifecycle-extensions库中ViewModelProviders已经被废弃。

通过KTX扩展库方式创建ViewModel

由于ViewModelProviders已经被废弃了,同时上面的写法确实也稍微有点繁琐。所以为了进一步简化,我们使用到下面ktx库中的方法:

1
2
Groovy复制代码 implementation "androidx.activity:activity-ktx:1.2.2"
implementation "androidx.fragment:fragment-ktx:1.3.3"

我们通过这2个扩展库,使用viewModels就可以非常快速的创建出我们所需要的ViewModel,

1
2
3
4
5
6
7
8
9
10
11
KOTLIN复制代码class MainActivity:AppCompatActivity() {
private val viewModel:MainViewModel by viewModels()

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
viewModel.mUser.observe(this) {
Log.d("mUser","$it")
}
}
}

而在Fragment中我们可以使用activityViewModels或者viewModels来创建。

1
2
3
4
5
KOTLIN复制代码class HomeFragment:Fragment() {
val activityViewModel:MainViewModel by activityViewModels()
//或者
val viewModel:MainViewModel by viewModels()
}

可以看到viewModels其实是ComponentActivity的一个内联函数,通过在viewModels函数中调用ViewModelLazy来获取指定的ViewModel,同时viewModels可以传入用于创建ViewModel的工厂factoryProducer

1
2
3
4
5
6
7
8
9
KOTLIN复制代码public inline fun <reified VM : ViewModel> ComponentActivity.viewModels(
noinline factoryProducer: (() -> Factory)? = null
): Lazy<VM> {
val factoryPromise = factoryProducer ?: {
defaultViewModelProviderFactory
}

return ViewModelLazy(VM::class, { viewModelStore }, factoryPromise)
}

我们通过观察ViewModelLazy实现可以看到,ViewModelLazy最终还是调用的ViewModelProvider方法去创建,这就是传说中的kotlin语法糖,好甜。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
KOTLIN复制代码public class ViewModelLazy<VM : ViewModel> (
private val viewModelClass: KClass<VM>,
private val storeProducer: () -> ViewModelStore,
private val factoryProducer: () -> ViewModelProvider.Factory
) : Lazy<VM> {
private var cached: VM? = null

override val value: VM
get() {
val viewModel = cached
return if (viewModel == null) {
val factory = factoryProducer()
val store = storeProducer()
ViewModelProvider(store, factory).get(viewModelClass.java).also {
cached = it
}
} else {
viewModel
}
}

override fun isInitialized(): Boolean = cached != null
}

这里需要提一下的是在Fragment中通过activityViewModels创建的ViewModel,得到的将会这个Fragment所在的AcivityViewModel,后面的步骤流程基本就是都是一样的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
KOTLIN复制代码public inline fun <reified VM : ViewModel> Fragment.viewModels(
noinline ownerProducer: () -> ViewModelStoreOwner = { this },
noinline factoryProducer: (() -> Factory)? = null
): Lazy<VM> = createViewModelLazy(VM::class, { ownerProducer().viewModelStore }, factoryProducer)

public inline fun <reified VM : ViewModel> Fragment.activityViewModels(
noinline factoryProducer: (() -> Factory)? = null
): Lazy<VM> = createViewModelLazy(
VM::class, { requireActivity().viewModelStore },
factoryProducer ?: { requireActivity().defaultViewModelProviderFactory }
)

public fun <VM : ViewModel> Fragment.createViewModelLazy(
viewModelClass: KClass<VM>,
storeProducer: () -> ViewModelStore,
factoryProducer: (() -> Factory)? = null
): Lazy<VM> {
val factoryPromise = factoryProducer ?: {
defaultViewModelProviderFactory
}
return ViewModelLazy(viewModelClass, storeProducer, factoryPromise)
}

可以看到在activityViewModels方法中实际是调用了requireActivity来进行创建。如果我们需要实现activityFragment的数据共享就可以通过这种方式去创建。

image.png

通过反射创建ViewModel

虽然经过上面引入KTX库来处理,可能还是不能满足某些人的胃口。毕竟实际开发环境往往存在各种各样的问题,导致不符合实际的开发使用。没关系,我们还可以通过反射的方式再进一步的去处理。

通过上面演进,我们知道不管外部初始化如何变化,最终的方式还是调用ViewModelProvider去创建。这时我们可以参考封装DataBinding的方式,通过反射去创建我们的ViewModel

我们先增加一个ComponentActivity的扩展方法,通过它去获取我们在BaseActivity中得到泛型ViewModel的具体类型:

1
2
3
4
5
KOTLIN复制代码inline fun <VM: ViewModel> ComponentActivity.createViewModel(position:Int): VM {
val vbClass = (javaClass.genericSuperclass as ParameterizedType).actualTypeArguments.filterIsInstance<Class<*>>()
val viewModel = vbClass[position] as Class<VM>
return ViewModelProvider(this).get(viewModel)
}

然后我们就可以在BaseActivity中通过createViewModel创建我们需要的ViewModel,这里的BaseActivity中的ViewDataBinding不明白的可以去看看封装DataBinding

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
KOTLIN复制代码abstract class BaseActivity<VB : ViewDataBinding,VM: ViewModel> : AppCompatActivity(), BaseBinding<VB> {
protected val mBinding: VB by lazy(mode = LazyThreadSafetyMode.NONE) {
getViewBinding(layoutInflater)
}
protected lateinit var viewModel:VM

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(mBinding.root)
viewModel = createViewModel(1)
mBinding.initBinding()
initObserve()
}
abstract fun initObserve()
}

修改一下我们的MainActivity

1
2
3
4
5
6
7
8
9
10
11
12
KOTLIN复制代码class MainActivity : BaseActivity<ActivityMainBinding, MainViewModel>() {

override fun initObserve() {
viewModel.mUser.observe(this){
Log.d("MainViewModel","user: $it") //TestViewModel: user: User(id=1, name=测试)
}
}

override fun ActivityMainBinding.initBinding() {
//数据绑定...
}
}

此时我们成功创建了MainViewModel,通过这种方式节省我们编写大量ViewModel模板代码的时间。无形中有增加了摸鱼吹水的时间。

image.png

但是这个还有个问题,上面的方式只能支持没有参数的ViewModel。如果需要创建的ViewModel需要传入参数怎么办。比如我们将MainViewModel修改为需要一个MainRepository:

1
2
3
4
kotlin复制代码class MainViewModel(private val repository: MainRepository):ViewModel() {
private val _user: MutableLiveData<User> = MutableLiveData(User(1,"测试"))
val mUser: LiveData<User> = _user
}

这个时候如果我们还是使用上面的方式,那我们的程序将会崩溃报错,提示无法创建实例化对象:

1
KOTLIN复制代码java.lang.RuntimeException: Cannot create an instance of class com.carman.kotlin.coroutine.request.viewmodel.MainViewModel

这个时候我们需要创建一个MainViewModelFactory来创建我们的MainViewModel:

1
2
3
4
5
6
7
8
KOTLIN复制代码class MainViewModelFactory(
private val repository: MainRepository
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return MainViewModel(repository) as T
}
}

这个时候我们又该如何把MainViewModelFactory和上面的反射方法结合到一起使用呢。虽然我在使用ViewModelProvider可以传入一个Factory,如我们在最开始MainActivity直接创建使用的时候:

1
KOTLIN复制代码ViewModelProvider(this,MainViewModelFactory(MainRepository())).get(MainViewModel::class.java)

但是我们现在是通过反射的方式去创建,当我们有很多ViewModel的时候,我们是不知道具体使用哪一个去创建。所以这个时候我们需要创建一个ViewModelUtils的工具类,然后提取扩展函数createViewModel方法,我们进一步修改,增加一个factory参数,通过factory去判断是否需要使用工厂创建:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
KOTLIN复制代码object ViewModelUtils {
fun <VM : ViewModel> createViewModel(
activity: ComponentActivity,
factory: ViewModelProvider.Factory? = null,
position: Int
): VM {
val vbClass =
(activity.javaClass.genericSuperclass as ParameterizedType).actualTypeArguments.filterIsInstance<Class<*>>()
val viewModel = vbClass[position] as Class<VM>
return factory?.let {
ViewModelProvider(
activity,
factory
).get(viewModel)
} ?: let {
ViewModelProvider(activity).get(viewModel)
}
}
}

我们再修改一下BaseActivity在构造方法中增加一个factory参数。把我们之前直接调用createViewModel扩展函数修改为使用ViewModelUtilscreateViewModel方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
KOTLIN复制代码abstract class BaseActivity<VB : ViewDataBinding, VM : ViewModel>(
private val factory: ViewModelProvider.Factory? = null
) : AppCompatActivity(), BaseBinding<VB> {
protected val mBinding: VB by lazy(mode = LazyThreadSafetyMode.NONE) {
getViewBinding(layoutInflater)
}
protected lateinit var viewModel:VM

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(mBinding.root)
viewModel = ViewModelUtils.createViewModel(this, factory, 1)
mBinding.initBinding()
initObserve()
}
abstract fun initObserve()
}

这个时候我们再修改一下MainActivity,我们使用provideMainViewModelFactory方法用于获取MainViewModel的建造工厂MainViewModelFactory:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
KOTLIN复制代码class MainActivity : BaseActivity<ActivityMainBinding, MainViewModel>(provideMainViewModelFactory()) {

override fun initObserve() {
viewModel.mUser.observe(this){
Log.d("MainViewModel","user: $it")
}
}

override fun ActivityMainBinding.initBinding() {
this.mainViewModel = viewModel
}
}

fun provideMainViewModelFactory(
): MainViewModelFactory {
return MainViewModelFactory(MainRepository())
}

这个时候我们再运行我们的程序,一切恢复正常运行。这里我们已经对Activity中创建ViewModel进行处理了,那我们在Fragment中又改如何创建呢。

image.png

这个时候我们就需要参考上面Fragment-ktx中的activityViewModelsviewModels方法,也将Fragment中创建ViewModel分为2种形式,同时也区分是否需要通过factory工厂去获取实例化对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
KOTLIN复制代码fun <VM : ViewModel> createViewModel(
fragment: Fragment,
factory: ViewModelProvider.Factory? = null,
position: Int
): VM {
val vbClass =
(fragment.javaClass.genericSuperclass as ParameterizedType).actualTypeArguments.filterIsInstance<Class<*>>()
val viewModel = vbClass[position] as Class<VM>
return factory?.let {
ViewModelProvider(
fragment,
factory
).get(viewModel)
} ?: let {
ViewModelProvider(fragment).get(viewModel)
}
}

fun <VM : ViewModel> createActivityViewModel(
fragment: Fragment,
factory: ViewModelProvider.Factory? = null,
position: Int
): VM {
val vbClass =
(fragment.javaClass.genericSuperclass as ParameterizedType).actualTypeArguments.filterIsInstance<Class<*>>()
val viewModel = vbClass[position] as Class<VM>
return factory?.let {
ViewModelProvider(
fragment.requireActivity(),
factory
).get(viewModel)
} ?: let {
ViewModelProvider(fragment.requireActivity()).get(viewModel)
}
}

现在我们再修改一下BaseFragment,与在Activity不同的是,由于Fragment是存在选择与Activity共享一个ViewModel,或者自己创建一个私有ViewModel。所以我们增加了一个shareViewModel参数来控制我们选择使用哪一种方式创建:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
KOTLIN复制代码abstract class BaseFragment<VB : ViewDataBinding, VM : ViewModel>(
private val shareViewModel: Boolean = false,
private val factory: ViewModelProvider.Factory? = null
) : Fragment(), BaseBinding<VB> {
protected lateinit var mBinding: VB
private set
protected lateinit var viewModel: VM
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
mBinding = getViewBinding(inflater, container)
viewModel = if (shareViewModel) ViewModelUtils.createActivityViewModel(this, factory, 1)
else ViewModelUtils.createViewModel(this, factory, 1)
return mBinding.root
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
mBinding.initBinding()
}

override fun onDestroy() {
super.onDestroy()
if (::mBinding.isInitialized) {
mBinding.unbind()
}
}
}

到现在为止,ViewModel在开发中常用的三种封装使用方式就讲完了。我们接下来讲一讲通过注解的方式去创建,可能有些小伙伴使用过Dagger,但是我们这里要说的不是使用Dagger,而是基于Dagger封装专门为Android环境下的使用的Android Jetpack组件之一Hilt

image.png

通过Hilt注解创建ViewModel

我们先引入Hilt依赖:

1
KOTLIN复制代码 classpath 'com.google.dagger:hilt-android-gradle-plugin:2.35.1'
1
2
3
kotlin复制代码plugins {
id 'dagger.hilt.android.plugin'
}
1
2
KOTLIN复制代码 implementation "com.google.dagger:hilt-android:2.35.1"
kapt "com.google.dagger:hilt-android-compiler:2.35.1"

笔者这里只是作为扩展阅读,不是详细讲解Hilt如何使用,如果想了解详细使用可以去官网看看使用 Hilt 实现依赖项注入或者有兴趣的可以在地下留言,笔者到时候再找个时间单独写一篇Hil的入门何使用t

我们创建一个DemoApplication不做任何实现,只是增加一个@HiltAndroidApp注解,它会触发Hilt的代码生成操作,生成的代码包括应用的一个基类,该基类充当应用级依赖项容器。

1
2
KOTLIN复制代码@HiltAndroidApp
class DemoApplication : Application() { ... }

然后在我们需要使用@HiltViewModel对的MainViewModel进行注解,同时需要使用@Inject来执行字段注入

1
2
3
4
5
KOTLIN复制代码@HiltViewModel
class MainViewModel @Inject constructor(private val repository: MainRepository):ViewModel() {
private val _user: MutableLiveData<User> = MutableLiveData(User(1,"测试"))
val mUser: LiveData<User> = _user
}

同时我们也需要再MainRepository中使用@Inject注解进行字段注入:

1
2
KOTLIN复制代码class MainRepository @Inject constructor():BaseRepository(){
}

然后在我们的MainActivity中使用AndroidEntryPoint注解。@AndroidEntryPoint为项目中的每个Android 类生成一个单独的 Hilt 组件。这些组件可以从它们各自的父类接收依赖项

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
KOTLIN复制代码@AndroidEntryPoint
class MainHiltActivity : BaseVBActivity<ActivityMainBinding>(){
val viewModel:MainViewModel by viewModels()

override fun initObserve() {
viewModel.mUser.observe(this){
Log.d("MainViewModel","user: $it") //MainViewModel: user: User(id=1, name=测试)
}
}

override fun ActivityMainBinding.initBinding() {
this.mainViewModel = viewModel
}

}

我们这里是通过与KTX扩展库结合使用,虽然创建MainViewModel的时候需要传入构建工厂,但是我们通过使用Hilt注解来帮助我们注入。

image.png

需要源码的看这里:demo源码
原创不易。如果您喜欢这篇文章,您可以动动小手点赞收藏image.png

关联文章
Kotlin协程基础及原理系列

Flow系列

扩展系列

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%