Why learn MVVM
The company’s projects have always been developed based on Activity and MVC mode. Since I joined the company one year ago, I have been working on new projects and filling in old holes. The main page of the project is still using TabActivity, which has been abandoned for a long time. I made up my mind to change the main page to the mode of Activity+ multi-fragment. The logic of the Activity in the previous interface was too complicated, so I took this opportunity to learn about MVVM with my colleagues. For the future development is to do their own preparation.
Learning process
DataBinding
Google has provided a number of 🌰 and frameworks for MVVM, and now Google is pushing Jetpack. The core of MVVM is data binding, which is in Android because XML as a View is extremely weak. Google provides Databinding components in Jetpack that bind XML to ViewModel data so that the UI responds to changes in ViewModel data.
Use DataBinding in XML
<variable
name="viewmodel"
type="com.acclex.ViewModel" />
<TextView
android:text="@{viewmodel.user.name}"
Copy the code
LiveData
In order to enable ViewModel to manipulate data and facilitate activities and XML to observe changes in data, Google also provides LiveData framework. Its essence is a framework similar to RxJava to implement the observer mode. The general usage is as follows
In the ViewModel
Private val _user: MutableLiveData<User> val User :LiveData<User> get() = _user // Change data private funupdateUser() {
_user.value = User()
}
Copy the code
Activity or Fragment
viewModel.user.observe(this, Observer<User> { user -> user? .let { //tododo something
}
})
Copy the code
It is very simple and effective to implement the observer mode, decouple the View layer from the ViewModel layer, and handle data changes in this way, which is consistent with the RxJava implementation, so MVVM can use RxJava to implement the same function. With observer mode, views are decoupled from data, and activities and fragments no longer need to do anything related to data.
LiveData has some benefits because it is developed and packaged by Google, and it has lifecycle management because it observes the LifecycleOwner object directly. If the LifecycleOwner object is destroyed, LiveData will to clean off, personally think that binding and life cycle, this is a great advantage, more and more advantages, Google’s official document has detailed introduction: developer. The android, Google. Cn/topic/libra…
ViewModel
Jetpack also provides viewModels for developers to use. Viewmodels are operations on business logic and business data. In a ViewModel, there are no and cannot be any references to a View. We usually use code like val viewModel = viewModelproviders.of (this).get(viewModel ::class.java) in the View layer to get an instance of the viewModel. So this can be an Activity or a Fragment, and the ViewModel, when it’s initialized, stays in memory until it’s in the realm, which is the Fragment triggering the nineteenth or the Activity triggering the eighteenth, it’s reclaimed. If we need to initialize the ViewModel when introduced to structural parameter, so we have to write an inherited from ViewModelProvider. NewInstanceFactory class, the code is as follows
class SampleViewModelFactory(
private val model: Model
) : ViewModelProvider.NewInstanceFactory() {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(modelClass: Class<T>) =
with(modelClass) {
when {
isAssignableFrom(SampleViewModel::class.java) ->
SampleViewModel(model)
else ->
throw IllegalArgumentException("Unknown ViewModel class: ${modelClass.name}")
}
} as T
}
Copy the code
These are all for the convenience of developers to use MVVM mode, Google in Jetpack to provide us with some components, separately, the use of these components, the cost of learning, and did not involve the Model layer to do a separate component packaging, because Model is the most free, and most customized components. Before, when I wrote the MVVM demo, I did not write a separate Model, and even wrote the data acquisition in ViewMode, so that the ViewModel can obtain and parse the data, and process the data. After that, I will continue to learn. Especially after reading the source code of Google Android-Architecture, the previous idea can be said to be completely wrong. Next, we will talk about the definition and use of Model layer in MVVM
Model
No matter MVC,MVP or MVVM design patterns, there is a Model layer, so it can be seen that the Model layer is extremely important. But in Android development, the Model layer is probably the weakest layer, because now the code to get data, whether it is network access, or to read the database, or to read local data, has been reduced to a few lines of code can be implemented, a lot of development time, Write these methods in an Activity, Fragment, or ViewModel or Presenter. When I read an MVVM implementation, I wrote the data directly in the ViewModel.
So what’s the problem with writing this? If it’s simple data and relatively simple logic, it doesn’t have much impact, and readability doesn’t suffer much either, but if unit testing is required, the data is coupled to the logic, which can have a significant impact on unit testing. This is my view on the problem. (PS: younger brother technical dishes, did not think of some other problems, can only see this impact may be larger problems, there are additional welcome to comment, thank you!)
According to the specification, the Model layer is responsible for data storage, data processing, and data retrieval. But Google doesn’t provide ready-made specifications and standards like ViewModel, LiveData, and DataBinding, so I actually have some problems with the Model layer
How is the Model layer structured, including the interfaces and basic methods
This is arguably the most critical issue in the Model layer, as it relates to the implementation of the Model layer. I think we need to refer to the code for this.
Just in the new development task of this project, I adopted MVVM to realize some modules, and tried to design the Model by myself, so I directly went to the code and explained my understanding.
Interface BaseModel {interface ModelDataCallBack<T> {/** ** @param result returns the required type */ fun onSuccess(result: T) /** * failed callback * @param errorLog failed to pass back the error data */ fun onFailure(errorLog: String)}}Copy the code
The interface ModelDataCallBack is responsible for calling back the results to the ViewModel to process the results. Since each ViewModel needs different data, the returned results are determined by the generics passed in from the initialization. Failure, in my scenario, would be to return a parse result or an exception log, maybe a Toast or some other logic, so returning failure is defined as returning a String. So the ViewModel, Model concrete implementation code is roughly as follows
In the Model
class SampleModel : BaseModel { fun getData(callBack: BaseModel. ModelDataCallBack < List < User > >) {/ / if the success callBack. OnSuccess (listOf (User ("A",15)) // Fail callback.onFailure ("Data acquisition failure")}}Copy the code
In the ViewModel
class SampleViewModel(private val model:SampleModel) : ViewModel {
private val _list = MutableLiveData<List<User>>()
val list: LiveData<List<User>>
get() = _list
private fun updateUser() {
model.getData(object :BaseModel.ModelDataCallBack<List<User>>{
override fun onSuccess(result: List<User>) {
list.value = result
}
override fun onFailure(errorLog: String) {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
})
}
}
Copy the code
The above code, can achieve ViewModel is responsible for processing logic, and Model is responsible for obtaining data, ViewModel received a successful or failed callback, can trigger the data update of LiveData, View layer can observe the data in LiveData, do UI update or transformation. This is how I imagine a simple Model layer. If a Model has many methods or interfaces to get data during development, there will be a lot of interface callbacks. With Kotlin, you can use higher-order functions to pass in success or failure callbacks directly, as shown in the following code
fun getData(success:(List<User>) -> Unit,
fail:(String) ->Unit){
success.invoke(listOf(User("A",15)))
fail.invoke("Data acquisition failure")}Copy the code
In my opinion, a large number of interface callbacks are basically impossible to avoid. If dalao has a better solution, welcome to put forward in the comments section, thanks!
The above code is just a very simple Model design. If you use such a Model in development, you will encounter the following problems:
- How are unit tests implemented
- Does the BaseModel interface make sense? Does it only need one ModelDataCallBack interface
- If you have data caching requirements, what should you do
These are all problems that this simple Model will encounter, and the unit test pit is quite deep, so I will write it separately later when I have time. There is no common implementation method for this model, so there is no need for a separate BaseModel interface. The meaning of BaseModel does not exist. If the Model needs to be cached, and if only one data is stored in the Model, this logic inevitably affects the unit tests. So this is just a simple idea that I have to work on.
Google MVVM Sample
With these thoughts in mind, I took a look at the source code for Google’s Sample. Here is the link to Google’s Sample: github.com/googlesampl… . Let’s start with an image from a friend’s blog, blog link: Blog link
interface TasksDataSource {
interface LoadTasksCallback {
fun onTasksLoaded(tasks: List<Task>)
fun onDataNotAvailable()
}
interface GetTaskCallback {
fun onTaskLoaded(task: Task)
fun onDataNotAvailable()
}
fun getTasks(callback: LoadTasksCallback)
fun getTask(taskId: String, callback: GetTaskCallback)
fun saveTask(task: Task)
fun completeTask(task: Task)
fun completeTask(taskId: String)
fun activateTask(task: Task)
fun activateTask(taskId: String)
fun clearCompletedTasks()
fun refreshTasks()
fun deleteAllTasks()
fun deleteTask(taskId: String)
}
Copy the code
Repository implements the TasksDataSource interface and contains multiple sources of data that implement the TasksDataSource interface, either local or cached. And only the getTasks and getTask functions have callback methods as callbacks to the data interface of the ViewModel. Other functions are exposed as models to the ViewModel to manipulate functions that process data. Improved versatility, allowing a Repository to handle both cached data and new data.
To summarize
The use of MVVM does have an excellent effect on code decoupling and code readability. Many things in the article are still my own ideas, and I have not found good solutions to some problems encountered. Meanwhile, in the development, the debug of the code after using DataBinding has become a bit more troublesome, and the difficulty of designing the Model layer is the most difficult point in my opinion. I think there are some bad things in Google Sample, and I will write another article to discuss Google’s MVVM Sample later.
Thank you for reading, if you have any ideas, welcome to comment, criticism.