美文网首页
ViewModel初始化简单总结

ViewModel初始化简单总结

作者: 梧叶已秋声 | 来源:发表于2022-10-24 14:48 被阅读0次

    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-ktxfragment-ktx就可以在ActivityFragment中使用 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

    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 中引用 ViewActivity 上下文。如果 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

    知识点 | ViewModel 四种集成方式
    CreationExtras 来了,创建 ViewModel 的新方式

    相关文章

      网友评论

          本文标题:ViewModel初始化简单总结

          本文链接:https://www.haomeiwen.com/subject/idjgzrtx.html