ViewModel
ViewModel 类 旨在 以 注重生命周期的方式 , 存储和管理 ,界面 相关 数据。
先来看看ViewModel初始化的几种方式。
本文不考虑使用Hilt情况下的初始化。
1.使用by lazy初始化。
先添加依赖。
def lifecycle_version = "2.6.0-alpha02"
// LiveData
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
// ViewModel
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
ViewModel和LiveData一般一起使用。
下面来看看 ViewModel 和 AndroidViewModel 的简单使用。
新建TestViewModel.kt。
// 使用不带参数的ViewModel
class StringViewModel: ViewModel(){
var stringLiveData: MutableLiveData<String> = MutableLiveData()
fun updateStringData(){
stringLiveData.postValue("AAA")
}
}
// 使用带参数的ViewModel
class StringViewModelWithArg(private val string: String) : ViewModel() {
var stringLiveData: MutableLiveData<String> = MutableLiveData()
fun updateStringData(){
stringLiveData.postValue(string)
}
}
/*使用ViewModel的时候,不能将任何含有Context引用的对象传入ViewModel,因为这可能会导致内存泄露。
* 可以使用AndroidViewModel类,它继承自ViewModel,并且接收Application作为Context这样就不会内存泄露了
* */
// 使用 AndroidViewModel,无需创建Application
class UserAndroidViewModel(application: Application) : AndroidViewModel(application){
var stringLiveData: MutableLiveData<String> = MutableLiveData()
private val user:User by lazy{
User(application,"CCC")
}
fun updateStringData(){
stringLiveData.postValue(user.string)
}
}
// AndroidViewModel 增加参数
class UserAndroidViewModelWithTwoArg(application: Application,private val string: String) : AndroidViewModel(application){
var stringLiveData: MutableLiveData<String> = MutableLiveData()
private val user:User by lazy{
User(application,string)
}
fun updateStringData(){
stringLiveData.postValue(user.string)
}
}
data class User(val context: Context,val string: String)
新建TestViewModelFactory.kt
class StringViewModelFactory(private val string: String) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
// return super.create(modelClass)
return StringViewModelWithArg(string) as T
}
}
class UserAndroidViewModelFactory(private val application: Application,private val string: String) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
// return super.create(modelClass)
return UserAndroidViewModelWithTwoArg(application,string) as T
}
}
MainActivity 中简单使用。
const val TAG = "MainActivity"
class MainActivity : AppCompatActivity() {
private val stringViewModel by lazy {
ViewModelProvider(this).get(StringViewModel::class.java)
}
private val stringViewModelWithArg by lazy {
ViewModelProvider(this,StringViewModelFactory("BBB")).get(StringViewModelWithArg::class.java)
}
private val userAndroidViewModel by lazy {
ViewModelProvider(this).get(UserAndroidViewModel::class.java)
}
private val userAndroidViewModelWithTwoArg by lazy {
ViewModelProvider(this,UserAndroidViewModelFactory(application,"DDD")).get(UserAndroidViewModelWithTwoArg::class.java)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 注册观察者
startObserver()
//更新数据
stringViewModel.updateStringData()
stringViewModelWithArg.updateStringData()
userAndroidViewModel.updateStringData()
userAndroidViewModelWithTwoArg.updateStringData()
}
private fun startObserver() {
stringViewModel.stringLiveData.observe(this, Observer {
Log.i(TAG, it.toString())
})
stringViewModelWithArg.stringLiveData.observe(this, Observer {
Log.i(TAG,it.toString())
})
userAndroidViewModel.stringLiveData.observe(this, Observer {
Log.i(TAG,it.toString())
})
userAndroidViewModelWithTwoArg.stringLiveData.observe(this, Observer {
Log.i(TAG,it.toString())
})
}
}
显示结果如下:
image.png
2.使用扩展库初始化。
2.1 by viewModels
https://developer.android.com/topic/libraries/architecture/viewmodel?hl=zh-cn
借助 ViewModelProvider.get() 方法,您可以获取作用域限定为任何 ViewModelStoreOwner 的 ViewModel 实例。
ViewModel 的作用域限定为最近的ViewModelStoreOwner
可以将 ViewModel 的作用域限定为 activity、fragment 。借助 Activity 库、Fragment 库提供的 viewModels() 扩展函数可以获取作用域限定为最近的 ViewModelStoreOwner 的 ViewModel 实例。
class MyActivity : AppCompatActivity() {
// ViewModel API available in activity.activity-ktx
// The ViewModel is scoped to `this` Activity
val viewModel: MyViewModel by viewModels()
}
class MyFragment : Fragment() {
// ViewModel API available in fragment.fragment-ktx
// The ViewModel is scoped to `this` Fragment
val viewModel: MyViewModel by viewModels()
}
添加依赖activity-ktx
或fragment-ktx
就可以在Activity
或Fragment
中使用 by viewModels()
初始化ViewModel
。
implementation "androidx.activity:activity-ktx:1.6.0"
implementation "androidx.fragment:fragment-ktx:1.5.4"
// 使用 Activity 库 提供的 viewModels() 扩展函数
//不带参数的 ViewModel
private val stringViewModel: StringViewModel by viewModels()
//带参数的 ViewModel
private val stringViewModelWithArg: StringViewModelWithArg by viewModels{
StringViewModelFactory("BBB")
}
2.2 by activityViewModels
使用ViewModel 在 Fragment 之间,共享数据
可参考这篇:
https://developer.android.com/codelabs/basic-android-kotlin-training-shared-viewmodel?hl=zh-cn#0
代码地址:
https://github.com/google-developer-training/android-basics-kotlin-cupcake-app
简单来说,就是在不同的fragment中使用by activityViewModels()初始化ViewModel,这样确保ViewModel只初始化一次,不同的fragment获取的是同一个ViewModel。
记得要添加依赖:
implementation "androidx.fragment:fragment-ktx:1.5.4"
3. ViewModel2.5版本之后的使用
创建具有依赖项的 ViewModel(即带参数的),使用2.5版本以上的 ViewModel的话,可以使用CreationExtras。
官方文档推荐把Factory 写成自定义ViewModel的伴生对象。
修改ViewModel。
// 使用不带参数的ViewModel
class StringViewModel: ViewModel(){
var stringLiveData: MutableLiveData<String> = MutableLiveData()
fun updateStringData(){
stringLiveData.postValue("AAA")
}
}
val stringExtraKey = object : CreationExtras.Key<String> {}
// 使用带参数的ViewModel
class StringViewModelWithArg(private val string: String) : ViewModel() {
var stringLiveData: MutableLiveData<String> = MutableLiveData()
fun updateStringData(){
stringLiveData.postValue(string)
}
// Define ViewModel factory in a companion object
companion object {
val Factory: ViewModelProvider.Factory = object : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(
modelClass: Class<T>,
extras: CreationExtras
): T {
var string = "string"
try {
string = checkNotNull(extras.get(stringExtraKey))
}catch (e:Exception){
e.printStackTrace()
}
return StringViewModelWithArg(string) as T
}
}
}
}
/*使用ViewModel的时候,不能将任何含有Context引用的对象传入ViewModel,因为这可能会导致内存泄露。
* 可以使用AndroidViewModel类,它继承自ViewModel,并且接收Application作为Context这样就不会内存泄露了
* */
// 使用 AndroidViewModel,无需创建Application
class UserAndroidViewModel(application: Application) : AndroidViewModel(application){
var stringLiveData: MutableLiveData<String> = MutableLiveData()
private val user:User by lazy{
User(application,"CCC")
}
fun updateStringData(){
stringLiveData.postValue(user.string)
}
}
val userExtraKey = object : CreationExtras.Key<String> {}
// AndroidViewModel 增加参数
class UserAndroidViewModelWithTwoArg(application: Application,private val string: String) : AndroidViewModel(application){
var stringLiveData: MutableLiveData<String> = MutableLiveData()
private val user:User by lazy{
User(application,string)
}
fun updateStringData(){
stringLiveData.postValue(user.string)
}
// Define ViewModel factory in a companion object
companion object {
val Factory: ViewModelProvider.Factory = object : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(
modelClass: Class<T>,
extras: CreationExtras
): T {
// Get the Application object from extras
val application = checkNotNull(extras[APPLICATION_KEY])
var string = "string"
try {
string = checkNotNull(extras.get(userExtraKey))
}catch (e:Exception){
e.printStackTrace()
}
return UserAndroidViewModelWithTwoArg(application,string) as T
}
}
}
}
data class User(val context: Context,val string: String)
// MainActivity
//通过by lazy 初始化
// 2.5版本以上的 使用CreationExtras
private val extras = MutableCreationExtras().apply {
set(stringExtraKey, "BBB")
}
private val stringViewModelWithArg by lazy{
StringViewModelWithArg.Factory.create(StringViewModelWithArg::class.java,extras)
}
// 通过by viewModels 初始化
private val stringViewModelWithArg: StringViewModelWithArg by viewModels{
StringViewModelWithArg.Factory
}
override fun getDefaultViewModelCreationExtras(): CreationExtras {
super.getDefaultViewModelCreationExtras()
return MutableCreationExtras().apply {
set(ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY, application)
set(stringExtraKey, "EEE")
set(userExtraKey, "FFF")
}
}
最后,全使用by viewModels初始化,减少代码量。
const val TAG = "MainActivity"
class MainActivity : AppCompatActivity() {
// 使用 Activity 库 提供的 viewModels() 扩展函数
private val stringViewModel: StringViewModel by viewModels()
private val userAndroidViewModel: UserAndroidViewModel by viewModels()
private val stringViewModelWithArg: StringViewModelWithArg by viewModels{
StringViewModelWithArg.Factory
}
private val userAndroidViewModelWithTwoArg:UserAndroidViewModelWithTwoArg by viewModels {
UserAndroidViewModelWithTwoArg.Factory
}
override fun getDefaultViewModelCreationExtras(): CreationExtras {
super.getDefaultViewModelCreationExtras()
return MutableCreationExtras().apply {
set(ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY, application)
set(stringExtraKey, "EEE")
set(userExtraKey, "FFF")
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 注册观察者
startObserver()
//更新数据
stringViewModel.updateStringData()
stringViewModelWithArg.updateStringData()
userAndroidViewModel.updateStringData()
userAndroidViewModelWithTwoArg.updateStringData()
}
private fun startObserver() {
stringViewModel.stringLiveData.observe(this, Observer {
Log.i(TAG, it.toString())
})
stringViewModelWithArg.stringLiveData.observe(this, Observer {
Log.i(TAG,it.toString())
})
userAndroidViewModel.stringLiveData.observe(this, Observer {
Log.i(TAG,it.toString())
})
userAndroidViewModelWithTwoArg.stringLiveData.observe(this, Observer {
Log.i(TAG,it.toString())
})
}
}
结果如下:
image.png
不同的fragment中使用by activityViewModels()初始化ViewModel,这样确保ViewModel只初始化一次,不同的fragment获取的是同一个ViewModel。
那么如何在activity中共享viewmodel呢呢。
然后去查了下发现,其实是不推荐在activity之间使用的。
how can i share viewModel between Activities?
这里有一个 Single Activity talk的概念。
但是如果非要使用,毕竟不是所有项目都是单一activity的。
如果需要在activity之间使用同一个viewmodel,可参考这篇:
ViewMode的使用(五)-全局ViewModel
更多官方demo地址:
Codelab
- Android 生命周期感知型组件
- Android 架构组件基本示例
-
Sunflower,这是一个演示版应用,演示了与架构组件相关的最佳做法
Sunflower包含了以下Jetpack组件的综合使用。
Room
Lifecycle-aware components
ViewModels
LiveData
Paging
Navigation
ViewBinding
WorkManager
ViewModel中的数据,尽量设置成private,不要暴露出来,使界面控制器(Activity 和 Fragment)尽可能保持精简,不应试图获取自己的数据。
使用viewmodel和livedata的时候,要遵循以下规则。
https://developer.android.com/topic/libraries/architecture/lifecycle?hl=zh-cn
生命周期感知型组件的最佳做法
- 使界面控制器(Activity 和 Fragment)尽可能保持精简。它们不应试图获取自己的数据,而应使用 ViewModel 执行此操作,并观察 LiveData 对象以将更改体现到视图中。
- 设法编写数据驱动型界面,对于此类界面,界面控制器的责任是随着数据更改而更新视图,或者将用户操作通知给 ViewModel。
- 将数据逻辑放在 ViewModel 类中。ViewModel 应充当界面控制器与应用其余部分之间的连接器。不过要注意,ViewModel 不负责获取数据(例如,从网络获取)。但是,ViewModel 应调用相应的组件来获取数据,然后将结果提供给界面控制器。
- 使用数据绑定在视图与界面控制器之间维持干净的接口。这样一来,您可以使视图更具声明性,并尽量减少需要在 Activity 和 Fragment 中编写的更新代码。如果您更愿意使用 Java 编程语言执行此操作,请使用诸如 Butter Knife 之类的库,以避免样板代码并实现更好的抽象化。
- 如果界面很复杂,不妨考虑创建 presenter 类来处理界面的修改。这可能是一项艰巨的任务,但这样做可使界面组件更易于测试。
- 避免在 ViewModel 中引用 View 或 Activity 上下文。如果 ViewModel 存在的时间比 activity 更长(在配置更改的情况下),activity 将泄漏并且不会获得垃圾回收器的妥善处置。
- 使用 Kotlin 协程管理长时间运行的任务和其他可以异步运行的操作。
如果不使用by lazy或者by viewModels()初始化,会怎么样?例如改成直接赋值(在Activity执行onCreate 之前),具体如下。
class MainActivity : AppCompatActivity() {
// 使用 Activity 库 提供的 viewModels() 扩展函数
// private val stringViewModel: StringViewModel by viewModels()
private val stringViewModel: StringViewModel = ViewModelProvider(this).get(StringViewModel::class.java)
...
}
报错如下
Caused by: java.lang.IllegalStateException: Your activity is not yet attached to the Application instance. You can't request ViewModel before onCreate call.
image.png
为什么会这样?接下来,带着这个问题,去简单分析下源码。
具体看下一篇。
参考链接:
https://developer.android.com/topic/libraries/architecture/lifecycle?hl=zh-cn
https://developer.android.com/topic/libraries/architecture/viewmodel?hl=zh-cn
网友评论