I’ve only recently started writing about Android, and I don’t know what to write about. Plus a friend who seems to have some questions about MVP. I didn’t want to write this at the beginning, but I was impatient to tell him about MVP and how to write MVP. I thought that instead of telling him what MVP is, I would like to write a post to share some of my understanding of it.
Said in the previous
First, in my opinion, reading the source code requires a little Android development experience. If you are just a beginner or a guy with no foundation, I advise you not to take the time to read this article. It may not be helpful for your development.
Then talking about the framework, in fact, the first thing you see should be the MVC framework, which is common when you first learn Java. M is the Model layer, V is the View layer and C is the Control layer. What about this article? I want to start with the MVC concept, extend it to the MVP concept, then write a simple MVP demo, and finally actually encapsulate an MVP framework within my control. At the end, I hope to talk about the advantages and disadvantages of MVP based on my development experience.
A, the MVC
First, the MVC framework is divided into three layers. M is for the entity layer to assemble data; V is used by the view layer to display data; C is used by the control layer to distribute user actions to the view layer. In summary, the basic flow should look like the following:
To put it simply, the running flow of MVC is: the user distributes operations through the control layer to the entity layer to assemble data, and finally presents the data to the view layer.
If Android is split up the way it is today, the original entity layer would still be the entity layer, and the fragment/activity would be full of life cycles, business logic, view operations, and so on. What are the benefits of this? Is the code volume more unified, easy to find.
But what about when the business logic is more complex? I even saw an activity with nearly 2,000 lines in a previous project. I was stunned. As I just got in touch with the project, I used a series of operations such as debugging and log, and it took me three days to figure out the code flow.
As an ambitious programmer, but also to become a responsible programmer. I suggest you take a look at the MVP.
Second, the MVP
As mentioned above, in MVC, both the business logic layer and the view are operated in the activity/fragment, and the activity itself needs to maintain its own life cycle. This results in bloated code in your activity/fragment, reducing the readability and maintainability of your code.
In my opinion, the MVP framework is actually a variant of the MVC framework. The original activity/fragment layer is divided into present layer and View layer. M is the original entity layer used to assemble data, P layer is used to isolate the View layer, known as the mediation layer, v layer is the view layer mainly used to display data layer. As shown below:
What happens when you have the Present layer? The View layer focuses on the activity/fragment layer and maintains its own life cycle, delegating business logic to the Present layer, which acts as an intermediary between the entity layer and the view layer. The entity layer and the view layer do not interact directly, but rather through delegation to the Persent layer, which has the following benefits:
- Decoupled view logic and business logic, reducing coupling
- The Activity only handles life-cycle tasks, making the code more concise
- The View logic and business logic are abstracted into the View and Presenter interfaces, respectively, to improve code readability
- Presenter is abstracted as an interface and can be implemented in many concrete ways, making it easy to unit test
- Pull business logic to Presenter to prevent background threads referencing activities from being recycled by the system, resulting in memory leaks and OOM
- Easy code maintenance and unit testing.
In fact, all this talk is nonsense
Talk is cheap, let me show you the code!
3. Simple implementation of an instance with MVP
I saw a lot of MVP in the simulation to write a landing interface, I will simply simulate a landing interface.
Activity_main code:
<? xml version="1.0" encoding="utf-8"? > <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/white">
<android.support.constraint.Group
android:id="@+id/login_group"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="visible"
app:constraint_referenced_ids="edit_username,edit_password,guide_view,login_btn,clear_btn" />
<EditText
android:id="@+id/edit_username"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Please enter your account number"
android:inputType="text"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<EditText
android:id="@+id/edit_password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:hint="Please enter your password"
android:inputType="textPassword"
app:layout_constraintStart_toStartOf="@id/edit_username"
app:layout_constraintTop_toBottomOf="@id/edit_username" />
<android.support.constraint.Guideline
android:id="@+id/guide_view"
android:layout_width="1dp"
android:layout_height="match_parent"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.5" />
<Button
android:id="@+id/login_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="10dp"
android:layout_marginTop="20dp"
android:text="Login"
app:layout_constraintEnd_toStartOf="@id/guide_view"
app:layout_constraintHorizontal_weight="1"
app:layout_constraintTop_toBottomOf="@id/edit_password" />
<Button
android:id="@+id/clear_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:text="Reset"
app:layout_constraintHorizontal_weight="1"
app:layout_constraintStart_toEndOf="@id/guide_view"
app:layout_constraintTop_toTopOf="@id/login_btn" />
<android.support.v4.widget.ContentLoadingProgressBar
android:id="@+id/progress_bar"
style="? android:attr/progressBarStyleSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
app:layout_constraintBottom_toTopOf="parent"
app:layout_constraintEnd_toStartOf="parent"
app:layout_constraintStart_toEndOf="parent"
app:layout_constraintTop_toBottomOf="parent" />
<Button
android:id="@+id/retry_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Try again"
android:visibility="gone"
app:layout_constraintBottom_toTopOf="parent"
app:layout_constraintEnd_toStartOf="parent"
app:layout_constraintStart_toEndOf="parent"
app:layout_constraintTop_toBottomOf="parent" />
<TextView
android:id="@+id/login_success_tips"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Successful landing!!"
android:visibility="gone"
app:layout_constraintBottom_toTopOf="parent"
app:layout_constraintEnd_toStartOf="parent"
app:layout_constraintStart_toEndOf="parent"
app:layout_constraintTop_toBottomOf="parent" />
</android.support.constraint.ConstraintLayout>
Copy the code
Explain: I use a lot of new ConstraintLayout attributes. If you have any questions about this, read my previous article explaining ConstraintLayout.
MainActivity code (view layer) :
class MainActivity : AppCompatActivity(), IView, View.OnClickListener {
private var persent: IPresent? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main) persent = MainPresent(this) login_btn.setOnClickListener(this) clear_btn.setOnClickListener(this) retry_btn.setOnClickListener(this) } override fun onClick(view: View?) { when (view? .id) { R.id.login_btn -> { persent? .checkFrom(edit_username.text.toString(), edit_password.text.toString()) } R.id.clear_btn -> { edit_username.setText("")
edit_password.setText("") } R.id.retry_btn -> { retry_btn.visibility = View.GONE persent? .checkFrom(edit_username.text.toString(), edit_password.text.toString()) } } } override fun errorShowTips(tips: String) { toast(tips) } override funonSubmit() {
login_group.visibility = View.INVISIBLE
progress_bar.visibility = View.VISIBLE
}
override fun showResult(loginSuccess: Boolean) {
progress_bar.visibility = View.GONE
if (loginSuccess) {
login_success_tips.visibility = View.VISIBLE
} else {
retry_btn.visibility = View.VISIBLE
}
}
}
Copy the code
MainModel code (entity layer) :
Override fun login(username: String, password: String): Observable<Boolean> {override fun login(username: String, password: String): Observable<Boolean> {return Observable.just(true)}}Copy the code
MainPresent code (mediation layer) :
class MainPresent(view: IView) : IPresent {
private var view: IView? = null
private var model: IModel? = null
init {
model = MainModel()
this.view = view
}
override fun checkFrom(username: String, password: String) {
if(username.isEmpty()) { view? .errorShowTips("Please enter user name")
return
}
if(password.isBlank()) { view? .errorShowTips("Please enter your password")
return} view? .onsubmit () // .run { login(username = username, password = password) .delay(2, TimeUnit.SECONDS) .observeOn(AndroidSchedulers.mainThread()) .subscribeBy( onNext = { view?.showResult(it) }, onError = { view?.showResult(false)})}}}Copy the code
IFeature code (encapsulating interface) :
interface IView {
fun errorShowTips(tips:String)
fun onSubmit()
fun showResult(loginSuccess: Boolean)
}
interface IPresent {
fun checkFrom(username:String,password:String)
}
interface IModel {
fun login(username:String,password: String): Observable<Boolean>
}
Copy the code
Repackage the MVP
If you are a very demanding programmer, you will try to optimize repetitive code. If you’re familiar with the above code, you’ll notice that we write the same code every time. The upside is that it increases your familiarity with the code layer, but the downside is that it creates a lot of code redundancy.
So at this point we need to extract the same code to encapsulate the operation. Of course, my experience is not too rich, maybe the code consideration is not so complete. What if there are doubts? It can be discussed and refined.
The base class: IFeature kt
interface IModel {
fun onAttach()
fun onDetach()
}
interface IView
interface IPresenter<in V : IView, inM : IModel> { fun attach(view: V? , model: M?) fun onResume() fun onPause() fun detach() fun isAttached(): Boolean }Copy the code
Presenter:
abstract class Presenter : PresenterLifecycle, PresenterLifecycleOwner {
protected open var mContext: Context? = null
/**
* mHandler is main thread handler
*/
protected val mHandler: Handler = Handler(Looper.getMainLooper())
/**
* currentState is current present lifecycle state
*/
override var currentState: Event = Event.DETACH
/**
* mOnAttachStateChangedListeners contains listeners object who would be notified when this presenter's lifecycle changed */ private val mOnAttachStateChangedListeners: FastSafeIterableMap
= FastSafeIterableMap() /** * isAttached is true after presenter has been invoked [onAttach] */ protected var mIsAttached: Boolean = false /** * isPaused is true when presenter'
,>s lifecycle is ON_PAUSE
*/
protected var mIsPaused: Boolean = false
open fun onAttach(context: Context) {
mContext = context
mIsAttached = true
currentState = Event.ATTACH
synchronized(this) {
mOnAttachStateChangedListeners.forEach { (listener, _) ->
listener.onStateChanged(this, Event.ATTACH)
}
}
}
open fun onResume() {
mIsPaused = false
currentState = PresenterLifecycle.Event.ON_RESUME
synchronized(this) {
mOnAttachStateChangedListeners.forEach { (listener, _) ->
listener.onStateChanged(this, Event.ON_RESUME)
}
}
}
open fun onPause() {
mIsPaused = true
currentState = PresenterLifecycle.Event.ON_PAUSE
synchronized(this) {
mOnAttachStateChangedListeners.forEach { (listener, _) ->
listener.onStateChanged(this, Event.ON_PAUSE)
}
}
}
open fun onDetach() {
mIsAttached = false
currentState = PresenterLifecycle.Event.DETACH
synchronized(this) {
mOnAttachStateChangedListeners.forEach { (listener, _) ->
listener.onStateChanged(this, Event.DETACH)
mOnAttachStateChangedListeners.remove(listener)
}
}
}
override fun addOnAttachStateChangedListener(listener: PresenterLifecycle.OnAttachStateChangedListener) {
synchronized(this) {
mOnAttachStateChangedListeners.putIfAbsent(listener, Unit)
}
}
override fun removeOnAttachStateChangesListener(listener: PresenterLifecycle.OnAttachStateChangedListener) {
synchronized(this) {
mOnAttachStateChangedListeners.remove(listener)
}
}
override fun getLifecycle(): PresenterLifecycle {
return this
}
}
Copy the code
PresenterLifecycle
interface PresenterLifecycle {
var currentState: Event
fun addOnAttachStateChangedListener(listener: OnAttachStateChangedListener)
fun removeOnAttachStateChangesListener(listener: OnAttachStateChangedListener)
interface OnAttachStateChangedListener {
fun onStateChanged(presenter: Presenter, event: Event)
}
enum class Event {
ATTACH, ON_RESUME, ON_PAUSE, DETACH
}
}
Copy the code
VMpresent:
abstract class VMPresenter<V : IView, M : IModel>(val context: Context) : Presenter(), IPresenter<V, M> {
/**
* viewRef is weak reference of view object
*/
private var viewRef: WeakReference<V>? = null
/**
* modelRef is weak reference of model object
*/
private var modelRef: WeakReference<M>? = null
/**
* Convenient property foraccessing view object */ protected val view: V? get() = viewRef? .get() /** * Convenient propertyforaccess model object */ protected val model: M? get() = modelRef? .get() /** * isPaused istrue when presenter's lifecycle is ON_PAUSE */ protected val isPaused: Boolean get() = mIsPaused override fun attach(view: V? , model: M?) { super.onAttach(context) viewRef = if (view ! = null) WeakReference(view) else null modelRef = if (model ! = null) WeakReference(model) else null } override fun detach() { super.onDetach() // clear the listeners to avoid strong retain cycle modelRef = null viewRef = null } override fun isAttached(): Boolean = mIsAttached }Copy the code
Model:
abstract class Model(context: Context) : IModel {
protected val context: Context = context.applicationContext
override fun onAttach() {}
override fun onDetach() {}}Copy the code
The above is my own encapsulation of the MVP framework, which may still have a lot of holes.
5. Disadvantages of MVP and an introduction to MVVM
First of all for the MVP advantage, I think I don’t have to say. As for the disadvantages of the MVP, the need to join a Presenter as a bridge between the View and the Model also makes the Presenter bloated and difficult to maintain. And for every Activity, you basically need a Presenter for it.
This kind of code becomes even more structured if it is wrapped in its own wrapper. So I don’t think there’s a need to use the MVP framework unless there’s a big logic requirement.
In addition to the MVP framework, of course, there is MVVM, and even better MVPVM frameworks. Where am I? Just a brief introduction:
-
MVVM is an improvement on the MVP. It replaces presenters with ViewModels and uses two-way data binding to interact with views and data. This means that once you bind the data to the view, the UI will be refreshed automatically when the data changes instead of having to manually refresh it. In MVVM, it simplifies the flow of data as much as possible, making it more concise. The schematic diagram is as follows:
-
MVPVM
MVPVM is model-view-presenter -ViewModel. This mode is a combination of MVVM and MVP mode. But the interaction pattern has changed considerably.
Presenter owns View, Model, and ViewModel, and coordinates the interaction between them.
The View holds the ViewModel. The ViewModel is a map of the data that the View displays, and the two are bidirectionally bound: (1) When the View’s data changes, the View synchronizes the data changes to the ViewModel. Let’s say the user enters something in the input field. (2) The View listens to the data changes of the ViewModel. When the data of the ViewModel changes, the View updates the UI display according to the data of the ViewModel. Such as updating the list of data from the back end.
The Presenter holds the View and the action response of the View is passed to the Presenter. When the Presenter receives an action response from the View, the Presenter obtains backend or database data from the ViewModel that the Presenter holds.
When the Model requests data, the data is returned to the Presenter, who passes the returned data to the ViewModel. Due to the binding relationship between the View and the ViewModel, the View updates the UI display based on the ViewModel data.
Said in the last
What about the project itself? I use the latest kotlin+ Anko with RX writing method, which is why I think this article is not suitable for beginners to learn. Can you understand this article first? You probably need to know something about Kotlin, and then you probably need to know something about RX. This will make sense of the article.
As for the following MVVM and MVPVM in fact, I basically just have some understanding, I did not go into the details, if there is a need later I will also go into the details here is just a brief introduction
I have been in touch with Kotlin for more than a year, and I have written a big project with some insights on this grammar. Later, I will combine my own insights and experiences to tell you one by one. Well, it’s getting late, and for an insomniac, it’s about as good as it gets. Take a bath and go to bed.