Use Kotlin delegation to split the more complex ViewModel

demand background

insert image description here

  1. In an actual development scenario, the data of a page may be composed of data of multiple businesses.
  2. Use the MVVM architecture to implement, store and process multiple business data in the ViewModel, and notify the View layer to refresh the UI.

traditional implementation

For example, in the above example, the page consists of 3 module data.

We can create a ViewModel, and 3 LiveData to drive refresh the corresponding 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(){
    
    }
				
    }

This kind of implementation has a disadvantage, that is, as the business iterates, the logic of the page becomes complicated, and the ViewModel class code here becomes complicated and bloated.

At this time, it may be necessary to consider splitting the ViewModel .

One way to achieve it is to simply split it into three ViewModels, and each ViewModel handles the corresponding business. But this will bring other problems, that is, when using the View layer, it will be more troublesome to judge the current business and then obtain the corresponding ViewModel.

If you need kotlin study notes, please click here to get them for free

optimization implementation

Target:

  • Split the ViewModel into multiple sub-ViewModels, and each sub-ViewModel only focuses on processing its own business logic .
  • Try to consider the maintainability and scalability of the code

Kotlin delegation

  • Delegate (Delegate) is a language feature of Kotlin, which is used to implement the proxy mode more elegantly .
  • Essentially, after using the by syntax, the compiler will help generate relevant codes.
  • Class delegation : The method of a class is not defined in the class, but is directly delegated to another object for processing.
  • Both the base class and the delegated class implement the same interface. In the bytecode generated at compile time, the methods inherited from the Base interface will be delegated to BaseImpl for processing.
// 基础接口
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()
}

Implementation

Define the interface of the child ViewModel, and the corresponding implementation class

    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("测试")
        }
    }

  • Divide a large module into several small business modules, which are processed by the corresponding ViewModel , keeping each other as independent as possible.
  • Define interface classes and provide fields and methods that need to be exposed externally
  • Define the interface implementation class, which is responsible for implementing the business details of the ViewModel internally, modifying the corresponding field values, and implementing the corresponding methods.
  • In this implementation, there is no need to declare an additional private variable with a dash every time, as in the above example. Moreover, more implementation details of ViewModel can be hidden from the outside, and the encapsulation is better .

Combining ViewModels

insert image description here

    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()
        }
    }

  • Define the interface class HomeViewModel, which inherits the interfaces of multiple sub-ViewModels
  • Define the implementation class HomeViewModelImpl, combine multiple sub-ViewModels , and pass the corresponding interface to the corresponding implementation class through the form of Kotlin class delegation .
  • In this way, the business logic of the corresponding module can be split into the corresponding sub-ViewModel for processing .
  • If you need to add a new business data in the future, you only need to add the ViewModel corresponding to the corresponding sub-module without modifying the ViewModel corresponding to other sub-modules.
  • Customize ViewModelFactory and provide the static method of create for external acquisition and creation of HomeViewModel.

How to use

  • For the View layer, you only need to get the HomeViewModel.
  • Calling the exposed method will finally be delegated to the corresponding sub-ViewModel implementation class for processing.
        val viewModel = HomeViewModel.create(this)

        viewModel.getNews()
        viewModel.getWeather()
        viewModel.getImage()

        viewModel.newsViewState.observe(this) {
    
    

        }
        viewModel.weatherState.observe(this) {
    
    

        }
        viewModel.imageState.observe(this) {
    
    

        }

expand

  • In the above example, under the HomeViewModel, it can be composed of several sub-ViewMdeols.
  • With business expansion, NewsViewModel, WeatherViewModel, and ImageOfTheDayStateViewModel may also be composed of several sub-ViewModels. It can also be realized by referring to the above method, and finally a "ViewModel tree" will be formed, and the ViewModel of each node is responsible for processing the corresponding business logic.

insert image description here

Summarize

Here is just a way to split the ViewModel. If it is applied in the project, it can be modified as needed.

Guess you like

Origin blog.csdn.net/m0_71506521/article/details/130642122