需求背景

使用 Kotlin 委托,拆分比较复杂的 ViewModel

  1. 在实际的开发场景中,一个页面的数据,可能是由多个事务的数据来组成的。
  2. 运用 MVVM 架构进行完成,在 ViewModel 中存放和处理多个事务的数据,通知 View 层改写 UI。

传统完成

比如上面的比如,页面由3 个模块数据构成。

我们能够创立一个 ViewModel ,以及 3个 LiveData 来驱动改写对应的 UI 。

    class HomeViewModel() : ViewModel() {
        private val _newsViewState = MutableLiveData<String>()
        val newsViewState: LiveData<String>
            get() = _newsViewState
        private val _weatherState = MutableLiveData<String>()
        val weatherState: LiveData<String>
            get() = _weatherState
        private val _imageOfTheDayState = MutableLiveData<String>()
        val imageOfTheDayState: LiveData<String>
            get() = _imageOfTheDayState
	fun getNews(){}
	fun getWeather(){}
	fun getImage(){}
    }

这样的完成会有个缺陷,便是跟着事务的迭代,页面的逻辑变得复杂,这儿的 ViewModel 类代码会变复杂,变得臃肿。

这个时分,就可能需求考虑进行拆分 ViewModel

一种完成办法,便是直接简略地拆分为3个 ViewModel,每个 ViewModel 处理对应的事务。但是这样会带来其他问题,便是在 View 层运用的时分,要判别当时是什么事务,然后再去获取对应的ViewModel,运用起来会比较费事。

优化完成

目标:

  • 将 ViewModel 拆分成多个子 ViewModel,每个子 ViewModel 只重视处理本身的事务逻辑
  • 尽量考虑代码的可维护性、可扩展性

Kotlin 托付

  • 托付(Delegate)是 Kotlin 的一种语言特性,用于愈加高雅地完成代理模式
  • 本质上便是运用了 by 语法后,编译器会帮忙生成相关代码。
  • 类托付: 一个类的办法不在该类中界说,而是直接托付给另一个对象来处理。
  • 根底类和被托付类都完成同一个接口,编译时生成的字节码中,继承自 Base 接口的办法都会托付给BaseImpl 处理。
// 根底接口
interface Base {   
    fun print()
}
// 根底对象
class BaseImpl(val x: Int) : Base {
    override fun print() { print(x) }
}
// 被托付类
class Derived(b: Base) : Base by b
fun main(args: Array<String>) {
    val b = BaseImpl(10)
    Derived(b).print() // 终究调用了 Base#print()
}

具体完成

界说子 ViewModel 的接口,以及对应的完成类

    interface NewsViewModel {
        companion object {
            fun create(): NewsViewModel = NewsViewModelImpl()
        }
        val newsViewState: LiveData<String>
        fun getNews()
    }
    interface WeatherViewModel {
        companion object {
            fun create(): WeatherViewModel = WeatherViewModelImpl()
        }
        val weatherState: LiveData<String>
        fun getWeather()
    }
    interface ImageOfTheDayStateViewModel {
        companion object {
            fun create(): ImageOfTheDayStateViewModel = ImageOfTheDayStateImpl()
        }
        val imageState: LiveData<String>
        fun getImage()
    }
    class NewsViewModelImpl : NewsViewModel, ViewModel() {
        override val newsViewState = MutableLiveData<String>()
        override fun getNews() {
            newsViewState.postValue("测验")
        }
    }
    class WeatherViewModelImpl : WeatherViewModel, ViewModel() {
        override val weatherState = MutableLiveData<String>()
        override fun getWeather() {
            weatherState.postValue("测验")
        }
    }
    class ImageOfTheDayStateImpl : ImageOfTheDayStateViewModel, ViewModel() {
        override val imageState = MutableLiveData<String>()
        override fun getImage() {
            imageState.postValue("测验")
        }
    }
  • 把一个大模块,划分成若干个小的事务模块,由对应的 ViewModel 来进行处理,彼此之间尽量保持独立。
  • 界说接口类,提供需求对外暴漏的字段和办法
  • 界说接口完成类,内部负责完成 ViewModel 的事务细节,修改对应字段值,完成相应办法。
  • 这种完成方式,就不需求像上面的比如一样,每次都要多声明一个带划线的私有变量。而且能够对外躲藏更多 ViewModel 的完成细节,封装性更好

组合 ViewModel

使用 Kotlin 委托,拆分比较复杂的 ViewModel

    interface HomeViewModel : NewsViewModel, WeatherViewModel, ImageOfTheDayStateViewModel {
        companion object {
            fun create(activity: FragmentActivity): HomeViewModel {
                return ViewModelProviders.of(activity, object : ViewModelProvider.Factory {
                    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
                        return if (modelClass == HomeViewModelImpl::class.java) {
                            @Suppress("UNCHECKED_CAST")
                            val newsViewModel = NewsViewModel.create()
                            val weatherViewModel = WeatherViewModel.create()
                            val imageOfTheDayStateImpl = ImageOfTheDayStateViewModel.create()
                            HomeViewModelImpl(
                                newsViewModel,
                                weatherViewModel,
                                imageOfTheDayStateImpl
                            ) as T
                        } else {
                            modelClass.newInstance()
                        }
                    }
                }).get(HomeViewModelImpl::class.java)
            }
        }
    }
    class HomeViewModelImpl(
        private val newsViewModel: NewsViewModel,
        private val weatherViewModel: WeatherViewModel,
        private val imageOfTheDayState: ImageOfTheDayStateViewModel
    ) : ViewModel(),
        HomeViewModel,
        NewsViewModel by newsViewModel,
        WeatherViewModel by weatherViewModel,
        ImageOfTheDayStateViewModel by imageOfTheDayState {
        val subViewModels = listOf(newsViewModel, weatherViewModel, imageOfTheDayState)
        override fun onCleared() {
            subViewModels.filterIsInstance(BaseViewModel::class.java)
                .forEach { it.onCleared() }
            super.onCleared()
        }
    }
  • 界说接口类 HomeViewModel,继承了多个子 ViewModel 的接口
  • 界说完成类 HomeViewModelImpl,组合多个子 ViewModel,并经过 Kotlin 类托付的形式,把对应的接口交给相应的完成类来处理
  • 经过这种方式,能够把对应模块的事务逻辑,拆分到对应的子 ViewModel 中进行处理
  • 假如后续需求新增一个新事务数据,只需新增相应的子模块对应的 ViewModel,而无需修改其他子模块对应的 ViewModel。
  • 自界说 ViewModelFactory,提供 create 的静态办法,用于外部获取和创立 HomeViewModel。

运用方式

  • 对于 View 层来说,只需求获取 HomeViewModel 就行了。
  • 调用露出的办法,最后会托付给对应子 ViewModel 完成类进行处理。
        val viewModel = HomeViewModel.create(this)
        viewModel.getNews()
        viewModel.getWeather()
        viewModel.getImage()
        viewModel.newsViewState.observe(this) {
        }
        viewModel.weatherState.observe(this) {
        }
        viewModel.imageState.observe(this) {
        }

扩展

  • 上面的比如,HomeViewModel 下面,能够由若干个子 ViewMdeol 构成。
  • 跟着事务拓宽,NewsViewModel、WeatherViewModel、ImageOfTheDayStateViewModel,也可能是分别由若干个子 ViewModel 构成。那也能够参照上面的方式,进行完成,最后会构成一棵”ViewModel 树“,各个节点的 ViewModel 负责处理对应的事务逻辑。

使用 Kotlin 委托,拆分比较复杂的 ViewModel

总结

这儿只是提供一种拆分 ViewModel 的思路,在项目中进行应用的话,能够根据需求进行改造。

参考文章

slicing-your-viewmodel-with-delegates

Kotlin | 托付机制 & 原理 & 应用 –