美文网首页
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