preface

This is the third installment of the architecture Learning series, which focuses on MVC, MVP, and MVVM architecture. MVI will be covered separately later. The purpose of these MVX’s is to decoupled and loosely coupled the business from the view, which is familiar to most Android programmers.

The Model determines the data of the App, while the View determines how to present the data to the user. Most frameworks or components are basically used to deal with the interaction between the two.

Therefore, an App architecture needs to handle two tasks:

1. Update Model — How do YOU handle View Actions?

2. Update the View — How do I present Model data to the View?

Based on this, there are three commonly used architectures on Android (MVI is not covered in this issue) :

MVC — model-view-Controller: Actvity/Fragment as Controller layer acts as View, with bloated code; At the same time, it is easy to operate Model directly in View layer, resulting in the coupling between View and Model layer, which cannot be reused independently. Sometimes it’s a nightmare to see an Activity with thousands or even thousands of lines of code.

**MVP ** — Model-view-Presenter: The Presenter and View layer communicate by defining the interface, decoupling the View and Model layer. However, when the business scenario is complex, the definition of the interface will be more and more, and the definition may be vague. Once the interface changes, the corresponding implementation also needs to change.

MVVM — model-view-ViewModel: MVVM solves the PROBLEM of MVP, making ViewModel and View no longer rely on interface communication, but through LiveData, RxJava, Flow and other responsive development way to communicate.

Here we can look at the understanding of Model and View:

View: The interface presented to the user and the layer of direct interaction with the user.

Model: The Model should typically include data and some business logic, namely the structural definition of the data, as well as storage and retrieval. In the case of external components, models tend to represent the data provided to them. After all, they don’t care about how the data came and went, they only care about themselves.

MVC

The architecture involves three roles: Model-view-Controller. Controller is the bridge between Model and View and is used to control the flow of the program.

I remember reading a lot of MVC articles on the Internet, but it seems that some articles give different model diagrams, which is a little hard to understand. In fact, these differences lie in the variation of MVC model after development. A version of MVC looks like this:

The general interaction flow of this version is:

1. The user manipulates the View, for example, generating a click event.

2. The Controller receives the event and reacts to it. For example, the click on login event will verify that the user input is null, and if it is, it will return to the View and ask it to prompt the user. If not, request the Model layer.

3. After processing, Model needs to notify the data of the logged-in user to the relevant members, which is the View layer in the figure above. View will make relevant display after receiving it.

In the figure above, the View layer relies on the Model layer, reducing the reusability of the View. For decoupling, the following version appears:

The main change in this version is that the View and the Model do not communicate directly. The View updates the data of the Model layer through the Controller. The Model layer notifies the Controller layer when the logic is complete, and the Controller updates the View.

MVC architecture summary

MVC provides a groundbreaking way to separate View and business, decouples View and Model layers, and improves reusability.

However, in the actual application of Android, it is easy to appear a new role – ViewController, such as Activity as View and Controller, very bloated, coupling also becomes serious, but also not convenient for unit testing.

MVP

The architecture involves three roles: Model-view-Presenter. The diagram is as follows:

This diagram is similar to the MVC structure of the second version above, except that the Controller layer is replaced by the Presenter layer, which has similar responsibilities, but is implemented differently. MVPS communicate with each other through interfaces, and each layer has its own interface to define its behavior and capabilities, which reduces coupling, improves reusability, and facilitates unit testing.

The interaction flow is still as follows: the user operates on the View layer, generating an event; Presenter receives events and reacts to them, requesting the Model layer; The Model layer notifies the Presenter, which in turn notifies the View layer.

Through the login scenario, for example, 🌰

1. First define the interfaces of each layer, and write the interfaces of a scene together.

Interface ILogin {interface ILoginView {fun loginLoading() Fun isAvailable(): Boolean // IView isAvailable} interface ILoginPresenter {attachView(view: attachView) ILoginView) // Attach View fun detachView() // Attach View fun isViewAvailable(): Boolean fun login() } interface ILoginModel { fun login(listener: OnLoginListener) } interface OnLoginListener { fun result(result: Boolean) } }Copy the code

2. View layer implementation.

class MVPLoginActivity : AppCompatActivity(), ILogin.ILoginView { private val loginPresenter = LoginPresenter() override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState) setContentView(Button(this). Apply {text = "login" setOnClickListener {super.onCreate(savedInstanceState) setContentView(Button(this). loginPresenter.login() } }) loginPresenter.attachView(this) } override fun loginLoading() { Toast.makeText(this, "Login..." , Toast.LENGTH_SHORT).show() } override fun loginResult(result: Boolean) { Toast.makeText(this, "Login result: $result", Toast.LENGTH_SHORT).show() } override fun isAvailable() = ! isDestroyed && ! isFinishing override fun onDestroy() { super.onDestroy() loginPresenter.detachView() } }Copy the code

Presenter layer implementation.

class LoginPresenter : ILogin.ILoginPresenter, ILogin.OnLoginListener { private val loginModel: ILogin.ILoginModel = LoginModel() private var loginView: ILogin.ILoginView? = null override fun attachView(view: ILogin.ILoginView) { loginView = view } override fun detachView() { loginView = null } override fun isViewAvailable(): Boolean = loginView? .isAvailable() ? : false override fun login() { loginView? .loginLoading() loginModel.login(this) } override fun result(result: Boolean) { if (isViewAvailable()) { loginView? .loginResult(result) } } }Copy the code

4. Model layer implementation.

class LoginModel : ILogin.ILoginModel { override fun login(listener: Ilogin.onloginlistener) {thread {thread.sleep (1000) runOnUIThread {// Return login result listener.result(random.nextBoolean ())} }}}Copy the code

The above is just an example. In practice, of course, some basic repetitive logic will be abstracted into Base classes.

MVP architecture summary

• MVP mode clearly divides the responsibilities of each layer, avoiding ViewController problems and reducing code bloat.

• Decouple View from Model and interact through interface, which improves reusability and expansibility and is conducive to unit testing.

• However, as businesses become more complex, interfaces become more defined, increasing the complexity of projects and requiring greater design capabilities for development.

• Presenter is prone to memory leaks and out-of-sync life cycles if it holds a reference to an Activity.

MVVM

The MVVM pattern

The architecture involves three roles: model-view-viewModel. The diagram is as follows:

The ViewModel is responsible for interacting with the Model layer and providing data to the View in the form of an observable. The ViewModel is separated from the View layer. The ViewModel should not know what the View it is interacting with is.

As mentioned above, the Model layer contains some business logic and business data models, while the ViewModel layer is the Model of View, which contains the presentation data and logic of the View. For example, the business data of Model layer is 1, 2, 3, 4, while translating to View layer may represent A, B, C, D. In addition to doing this, the ViewModel encapsulates the view’s behavior, such as what happens when a control is clicked. Also note that the ViewModel here is not the same thing as the ViewModel component provided in the Jetpack package, where the ViewModel is a concept and the Jetpack package provides a more convenient implementation.

Many examples of MVVM articles use DataBinding, but you can use the MVVM architecture without DataBinding by borrowing LiveData, RxJava, Flow, etc. These tools are based on responsive development principles. To replace interface-based communication. You don’t see much use of DataBinding in actual development, and if you do use DataBinding, try to avoid writing code logic in XML, instead replacing it with a variable representing an attribute and assigning it to it in Kotlin code.

Reactive development here emphasizes an observer-based approach to development: The View subscribes to the responsive interface exposed by the ViewModel, performs logic upon receiving notifications, and the ViewModel no longer holds any kind of reference to the View, reducing coupling and improving reusability.

In addition, if you use LiveData, the ViewModel only exposes the LiveData interface to the View layer, and does not allow you to update LiveData directly in the View layer, because once the View layer has the ability to update LiveData directly, The View layer cannot be constrained to conduct business processing:

class LoginViewModel : ViewModel() {
    private val _loginResult: MutableLiveData<Boolean> = MutableLiveData()
    val loginResult: LiveData<Boolean> = _loginResult
}
Copy the code

Take the login result as an example, MVVM interactive process based on LiveData: First, there is a LiveData attribute in ViewModel to represent the login result, exposing LiveData instead of MutableLiveData, and View layer will subscribe to this data; After the View layer clicks login, the LOGIN interface of VM is invoked, and VM then requests the login capability of Model layer. The Model notifies the VM when it’s done, the VM updates the MutableLiveData login status, and the View receives notification of LiveData changes and updates the UI.

The instance

1. Model layer simulates login and returns the login result.

Class LoginModel {suspend fun login(): Boolean = withContext(Dispatchers.IO) { delay(1000) Random.nextBoolean() } }Copy the code

2, the ViewModel layer exposes the login method, and provides LiveData data to represent the login status, so that the View layer subscribe.

class LoginViewModel : ViewModel(), CoroutineScope by MainScope() { private val loginModel = LoginModel() private val _loginResult: MutableLiveData<Int> = MutableLiveData() val loginResult: LiveData<Int> = _loginResult fun login() { launch { _loginResult.value = 0 val result = loginModel.login() _loginresult. value = if (result) 1 else -1}} String = when (result) {0 -> "1 ->" else -> "1 ->" "}}Copy the code

3. View layer handles click events and subscribes to login status.

class MVVMLoginActivity : AppCompatActivity() { private val viewModel: LoginViewModel by lazy { ViewModelProvider(this).get(LoginViewModel::class.java) } override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState) setContentView(Button(this). Apply {text = "login" setOnClickListener {super.onCreate(savedInstanceState) setContentView(Button(this). ViewModel. The login ()}}) / / to monitor the login status viewModel. LoginResult. Observe (this, {Toast. MakeText (this, "the login result: ${viewModel.loginProgressText(it)}", Toast.LENGTH_SHORT ).show() }) } }Copy the code

Repository

The concept of the Repository pattern is derived from Domain Driven Design. The idea is that the business (domain) layer is shielded from the details of access to different data sources by abstractions of a Repository layer, and the business layer (possibly the ViewModel) is not concerned with the details of data access.

A Repository provides access to a variety of data sources, including remote data, a Cache, and a Database. Different Fetcher implementations are possible, and Repository holds multiple Fetcher references.

Therefore, the LoginModel in the above example can be replaced with the LoginRepository class, which does not expose the specific data access method, but only the interface to that capability.

Write in the last

Architecture is not built overnight. Hopefully, one day, we can find the sense of accomplishment in architecture from the code we write instead of just running away. This series will be updated all the time to record the footprints of my learning on the road of architecture, removing the mysterious veil of architecture one by one.

If the content of the article is wrong, welcome to point out, common progress! If you think it is good, leave a thumbs-up before you go. Your three companies are my motivation to write!