The historical evolution of Android frameworks
I remember when I first started with Android, MVP was not popular yet. It was all MVC shuttle. After working, I used MVP. It was difficult to understand its callbacks when I was studying.
Up to now, the mainstream MVVM is actually an upgraded version of MVP, and then the latest MVI use intention transmission, isolating direct calls at all levels. I have experienced the entire process of Android framework changes.
Here are some simple demo use cases for each framework.
1. MVC framework
Classic MVC is divided into:
Model Model layer: Data and network
View View layer: Display of view
Controller Control layer: Logical control, calling model-driven view
Generally, we regard an XML as a View layer, Activity as a Control layer, and Model layer is composed of related data operation classes.
Model layer:
class OtherModel : BaseRepository() { /** * Use the extension method to request the network */ suspend inline fun getIndustry(): OkResult<List<Industry>> { return extRequestHttp { ( Constants.NETWORK_CONTENT_TYPE, Constants.NETWORK_ACCEPT_V1 ) } } }
Controller layer:
class MVCActivity : AbsActivity() { private val mOtherModel: OtherModel by lazy { OtherModel() } override fun setContentView() { setContentView(.activity_demo14_1) } override fun init() { val btnGetData = findViewById<Button>(.btn_get_data) { requestIndustry() } } private fun requestIndustry() { //In MVC, Activity is the Controller, which directly calls the interface, and then directly operates the xml control to refresh after obtaining data. { //Start Loading ().showLoading(this@MVCActivity) val result = () { //Processing successful information toast("list:$it") //doSth... } ().dismissLoading() } } }
XML is the View layer, which gets information and displays it in XML.
This division of labor is actually very clear, but once there is too much logic, it will lead to too bloated activity. It is both a Model and a View in Activcity, and it is too coupling. I remember that at that time, thousands of lines of code in an Activity were common.
In order to solve this problem, we started using the MVP architecture.
2. MVP framework
With the emergence of the MVP framework, each module has clear rights and responsibilities and does its own work, reducing coupling and reducing the bloat of the Activity.
Model layer: Still the MVC model.
View layer: The interface definition is implemented by Activity and is used to operate the corresponding UI.
Presenter layer: A bridge for Model and View, responsible for the interaction between Model and View
What's more complicated is that the Contract contract class is added to associate the Presenter and View of the specified page for easy maintenance.
View interface definition:
interface IDemoView { fun showLoading() fun hideLoading() fun getIndustrySuccess(list: List<Industry>?) fun getIndustryFailed(msg: String?) }
Presenter implementation:
class DemoPresenter(private val view: IDemoView) { private val mOtherModel: OtherModel by lazy { OtherModel() } //Get industry data fun requestIndustry(lifecycleScope: LifecycleCoroutineScope) { { //Start Loading () val result = () ({ //Processing successful information toast("list:$it") (it) }, { //fail (it) }) () } } }
Activity implementation:
class MVPActivity : AbsActivity(), IDemoView { private lateinit var mPresenter: DemoPresenter override fun setContentView() { setContentView(.activity_demo14_1) } override fun init() { //Create Presenter mPresenter = DemoPresenter(this) val btnGetData = findViewById<Button>(.btn_get_data) { //Calling the interface through Presenter (lifecycleScope) } } //The callback triggers again override fun showLoading() { ().showLoading(this) } override fun hideLoading() { ().dismissLoading() } override fun getIndustrySuccess(list: List<Industry>?) { //popupIndustryData } override fun getIndustryFailed(msg: String?) { //showErrorMessage } }
At that time, the MVP framework was popular. If you didn’t know how to do this in the interview, you would be embarrassed to say that it was an Android.
Although it has some shortcomings, such as being too complicated, writing duplicate Views every time, troublesome modification, callback hell, poor data interaction experience, inability to perceive life cycle, rebuilding pages cannot automatically restore data, there are still many couplings, etc. But with no alternative choice at that time, it was a well-deserved king.
But when Google released Jetpack, when ViewModel+LiveData+Lifecycles appeared, we had a new choice. The MVVM framework began to appear and developed rapidly.
3. MVVM framework
Let me first talk about some controversial points. Some people think that as long as you use ViewModel + LiveData, it is considered MVVM framework Model + View+ViewModel. Some people believe that MVVM means data-driven, and the biggest highlight is data binding. Only those who use DataBinding are considered MVVM. In fact, there is no official definition of this. There is no framework in the world. The framework name appears when there are too many people using it. It is a conventional thing. You can define it as it is as follows: Then I will call the former half MVVM and the latter MVVM.
3.1 Semi-MVVM framework
In fact, it can be understood as an upgraded version of MVP, which removes the View interface callback and saves the features of ViewModel.
Model layer: Still the MVC model.
View layer: Activity, used to operate the corresponding UI.
ViewModel: It's still the Presenter of MVP, it's just implemented with ViewModel.
ViewModel implementation:You can see that the code is indeed much less than MVP
class DemoViewModel @ViewModelInject constructor( private val mRepository: Demo5Repository, @Assisted val savedState: SavedStateHandle ) : BaseViewModel() { val liveData = MutableLiveData<List<Industry>?>() //Get industry data fun requestIndustry() { { //Start Loading loadStartLoading() val result = () ({ //Processing successful information toast("list:$it") = it }, { //fail = null }) loadHideProgress() } } }
Activity implementation:
@AndroidEntryPoint class MVVMActivity : BaseVMActivity<DemoViewModel>() { override fun getLayoutIdRes(): Int = .activity_demo14_1 override fun init() { //Automatically inject ViewModel, call the interface through LiveData callback () } override fun startObserve() { //After obtaining network data, change the corresponding value of xml (this) { it?.let { // popopIndustryData } } } }
3.2 MVVM framework with DataBinding
Especially now, plug-ins like kotlin that use id directly have been officially marked as outdated. Why don’t you hurry up and use DataBinding or ViewBinding?
ViewModel implementation:
class DemoViewModel @ViewModelInject constructor( private val mRepository: Demo5Repository, @Assisted val savedState: SavedStateHandle ) : BaseViewModel() { val liveData = MutableLiveData<List<Industry>?>() //Get industry data fun requestIndustry() { { //Start Loading loadStartLoading() val result = () ({ //Processing successful information toast("list:$it") = it }, { //fail = null }) loadHideProgress() } } }
Repository implementation:In fact, it means similar to Model, just a data warehouse. Some of the following annotations use Hilt dependency injection. It is also OK to not directly use new objects, don't care about some details.
@Singleton class Demo5Repository @Inject constructor() : BaseRepository() { suspend inline fun getIndustry(): OkResult<List<Industry>> { return extRequestHttp { ( Constants.NETWORK_CONTENT_TYPE, Constants.NETWORK_ACCEPT_V1 ) } } }
Activity implementation:Some base class encapsulation is made internally, event processing is encapsulated as objects, and viewmodel and event objects are referenced in XML
@AndroidEntryPoint class MVVM2Activity : BaseVDBActivity<DemoViewModel, ActivityDemo142Binding>() { private val clickProxy: ClickProxy by lazy { ClickProxy() } override fun getDataBindingConfig(): DataBindingConfig { return DataBindingConfig(.activity_demo14_2, , mViewModel) .addBindingParams(, clickProxy) } override fun init() { } override fun startObserve() { } /** * DataBinding event handling */ inner class ClickProxy { fun getData() { //MVVM directly calls network request, and the result is automatically displayed in xml () } } }
Implementation of Xml:Note that the package name pointed to by reference should be written correctly. If the package is written correctly, you can jump directly.
<?xml version="1.0" encoding="utf-8"?> <layout xmlns:andro xmlns:app="/apk/res-auto" xmlns:binding="/apk/res-auto" xmlns:tools="/tools"> <data> <variable name="viewModel" type=".kt_demo.demo.demo14_mvi." /> <variable name="click" type=".kt_demo.demo.demo14_mvi.mvvm2." /> </data> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/picture_color_blue" android:orientation="vertical"> <.lib_baselib. android: android:layout_width="match_parent" android:layout_height="wrap_content" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Get data" binding:clicks="@{}" /> <TextView android: android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@{()}" /> </LinearLayout> </layout>
This completes a data-driven MVVM based on DataBinding. If I don't understand some of the above codes, I may later introduce the DataBinding package and open source.
As of the date of publication, the most popular one on the market is the MVVM framework. The only point of this framework may be that Databinding is difficult to debug. Once there is a problem, sometimes the error message is reported inexplicably and does not point to a certain data or syntax error in XML. You need to have a certain understanding of DataBinding. However, AS seems to be getting smarter now, and the error message still points quite clearly. MVVM is fully available.
IV. MVI framework
Since it was not long since it was released, it is still uncertain whether it is called MVI framework. Everyone calls it that way, let’s just call it MVI. The framework that emerged with Compose is mainstreamly used in Compose applications.
The MVI framework is composed of Model View Intent. It can be counted as the upgraded version of MVVM. In the past, we used to directly call the ViewModel method in the Activity. Now we have changed to issue operation instructions, and the ViewModel parses the instructions, calls the corresponding methods, and callbacks to the Activity.
For example, a DemoActivity requires obtaining industry data, school data, etc. Then you can encapsulate both data and operations into specified objects.
//The data and status required for the current page data class Demo14ViewState( val industrys: List<Industry> = emptyList(), val schools: List<SchoolBean> = emptyList(), var isChanged: Boolean = false ) : BaseViewState() //The event definition required by the current page sealed class DemoAction { object RequestIndustry : DemoAction() object RequestSchool : DemoAction() object RequestAllData : DemoAction() data class UpdateChanged(val isChange: Boolean) : DemoAction() }
Activity calls related interfaces instead of directly calling ViewModel methods, but:
override fun init() { //Send Intent instruction, the specific implementation is implemented by ViewModel () }
Then ViewModel needs to parse the instructions:
//Action distribution portal fun dispatch(action: DemoAction) { when (action) { is -> requestIndustry() is -> requestSchool() is -> getTotalData() is -> changeData() } } //Get industry data private fun requestIndustry() { //xxx }
The complete code is as follows:
ViewModel implementation:
class Damo14ViewModel @ViewModelInject constructor( private val mRepository: Demo5Repository, @Assisted val savedState: SavedStateHandle ) : BaseViewModel() { private val _viewStates: MutableLiveData<Demo14ViewState> = MutableLiveData(Demo14ViewState()) //Only one LiveData needs to be exposed, including all the status of the page val viewStates: LiveData<Demo14ViewState> = _viewStates //Action distribution portal fun dispatch(action: DemoAction) { when (action) { is -> requestIndustry() is -> requestSchool() is -> getTotalData() is -> changeData() } } //Get industry data private fun requestIndustry() { { //Start Loading loadStartLoading() val result = () { _viewStates.setState { copy(industrys = it ?: emptyList()) } } loadHideProgress() } } //Get school data private fun requestSchool() { { //Start Loading loadStartLoading() val result = () { _viewStates.setState { copy(schools = it ?: emptyList()) } } loadHideProgress() } } //Get all data private fun getTotalData() { //The coroutines executed by default in the main thread - must be used (the coroutines executed by default in the IO thread) launchOnUI { //Start Loading loadStartProgress() val industryResult = async { () } val schoolResult = async { () } // Process data together val industry = () val school = () //If all succeed, return together if (industry is && school is ) { loadHideProgress() //Set multiple LiveData _viewStates.setState { copy(industrys = ?: emptyList(), schools = ?: emptyList()) } } } } //Change the state private fun changeData(isChanged: Boolean) { _viewStates.setState { copy(isChanged = isChanged) } } //The data and status required for the current page data class Demo14ViewState( val industrys: List<Industry> = emptyList(), val schools: List<SchoolBean> = emptyList(), var isChanged: Boolean = false ) : BaseViewState() //If you want to encapsulate again, you can also encapsulate the result of the callback into an object similar to Action, and the page determines which type of callback is, and performs related operations //In this way, there is no need to use LiveData callbacks. LiveData is just a function to save data and is called back by DemoEvent// sealed class DemoEvent { // object PopBack : DemoEvent() // data class ErrorMessage(val message: String) : DemoEvent() // } //The event definition required by the current page sealed class DemoAction { object RequestIndustry : DemoAction() object RequestSchool : DemoAction() object RequestAllData : DemoAction() data class UpdateChanged(val isChange: Boolean) : DemoAction() } }
Activity implementation:
@AndroidEntryPoint class Demo14Activity : BaseVDBActivity<Damo14ViewModel, ActivityDemo14Binding>() { private val clickProxy: ClickProxy by lazy { ClickProxy() } companion object { fun startInstance() { commContext().let { (Intent(it, Demo14Activity::).apply { addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) }) } } } override fun getDataBindingConfig(): DataBindingConfig { return DataBindingConfig(.activity_demo14) .addBindingParams(, clickProxy) } @SuppressLint("SetTextI18n") override fun startObserve() { // Listen to the data changes between the two ( this, Damo14ViewModel.Demo14ViewState::industrys, Damo14ViewModel.Demo14ViewState::schools ) { industry, school -> ("industry: $industry ; school: $school") } //Only listen for changed transformation (this, Damo14ViewModel.Demo14ViewState::isChanged) { if (it) { val industry = ?.industrys val school = ?.schools = "industry: $industry ; school: $school" } } } override fun init() { //Send Intent instruction, the specific implementation is implemented by ViewModel () } /** * DataBinding event handling */ inner class ClickProxy { fun getData() { //Send Intent instruction, the specific implementation is implemented by ViewModel// () // () ((true)) } } }
Note that some MVIs are written in the way of calling back to the Activity, and also encapsulate the code as I commented:
//If you want to encapsulate again, you can also encapsulate the result of the callback into an object similar to Action, and the page determines which type of callback is, and performs related operations //In this way, there is no need to use LiveData callbacks. LiveData is just a function to save data and is called back by DemoEvent// sealed class DemoEvent { // object PopBack : DemoEvent() // data class ErrorMessage(val message: String) : DemoEvent() // }
You can also use LiveData to return. I use the extension method observeState method to listen here, which ensures that the callback will be received only if the object you are listening to changes. This extension method can also be used in the MVVM framework.
The extension method is as follows:
import .* import .KProperty1 /** * @auther Newki * @date 2022/2/10 * @description LiveData extension supports MVI mode Subscribe to a single LiveData to listen to different operations and data */ // Listen to an attributefun <T, A> LiveData<T>.observeState( lifecycleOwner: LifecycleOwner, prop1: KProperty1<T, A>, action: (A) -> Unit ) { { StateTuple1((it)) }.distinctUntilChanged().observe(lifecycleOwner) { (a) -> (a) } } // Listen to two attributesfun <T, A, B> LiveData<T>.observeState( lifecycleOwner: LifecycleOwner, prop1: KProperty1<T, A>, prop2: KProperty1<T, B>, action: (A, B) -> Unit ) { { StateTuple2((it), (it)) }.distinctUntilChanged().observe(lifecycleOwner) { (a, b) -> (a, b) } } // Listen to three attributesfun <T, A, B, C> LiveData<T>.observeState( lifecycleOwner: LifecycleOwner, prop1: KProperty1<T, A>, prop2: KProperty1<T, B>, prop3: KProperty1<T, C>, action: (A, B, C) -> Unit ) { { StateTuple3((it), (it), (it)) }.distinctUntilChanged().observe(lifecycleOwner) { (a, b, c) -> (a, b, c) } } internal data class StateTuple1<A>(val a: A) internal data class StateTuple2<A, B>(val a: A, val b: B) internal data class StateTuple3<A, B, C>(val a: A, val b: B, val c: C) //Update Statefun <T> MutableLiveData<T>.setState(reducer: T.() -> T) { = ?.reducer() }
Too dry, I didn't even have a picture, finallySummarizeone time:
There is no framework in the world, and more people use it, it becomes a framework, and what suits you is the best. It is not necessary to say the latest framework, but I have to use the latest framework and then use it before I can easily use it.
I currently use the MVVM framework in my daily development. Some MVVM encapsulation and usage will be released laterOpen source。
The above is the detailed content of the evolution of the Android development framework MVC-MVP-MVVM-MVI. For more information about the Android framework MVC MVP MVVM MVI, please pay attention to my other related articles!