The war between MVP and MVVM

Syed Ovais Akhtar
4 min readOct 24, 2020

Building an application high quality, maintainable, and testable is a goal of every developer. To achieve this we have to work on some set of rules and protocols or an architecture.

The benefits of using architecture are when our line of code is increasing its more troublesome to understand the functionality and harder to make it readable so we have to implement an architecture to make it easy

There are lots of architectural patterns but in this story, we will talk about MVP and MVVM

Mvp and MVVM

MVP

MVP composed of 3 layers that are model, view, and presenter

Model: Model represents our data layer like POJO class/data class or structures that hold data

View: View represents the UI layer which includes app components like EditText, TextView, etc.

Presenter: The presenter is the connection which connects UI and model and controls all the functionality of an app

let's see an example below

View Part (UI)

class RegisterUserActivity : AppCompatActivity(), RegisterUserContracts.View {

private lateinit var presenter: RegisterUserPresenter

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_register_user)
setupPresenter()
setupListeners()
}

private fun setupPresenter() {
presenter = RegisterUserPresenter(this)
}

private fun setupListeners() {
signup_button.setOnClickListener {
presenter.onFetchedUser(User(username.text.toString(), email.text.toString(),password.text.toString(),phone.text.toString()), this)
}
already_a_user.setOnClickListener{
navigateToLoginScreen()
}

}

override fun onSuccessfulRegistration() {
val message = getString(R.string.on_successful_registration)
Toast.makeText(this, message, Toast.LENGTH_LONG).show()
navigateToLoginScreen()
}

override fun onRegistrationFailure() {
val message = getString(R.string.on_registration_failure)
Toast.makeText(this, message, Toast.LENGTH_LONG).show()
}

override fun onEmptyEmail() {
email.error = getString(R.string.empty_email)
email.requestFocus()
}

override fun onEmptyPassword() {
password.error = getString(R.string.empty_password)
password.requestFocus()
}

override fun onEmptyUsername() {
username.error = getString(R.string.empty_username)
username.requestFocus()
}
private fun navigateToLoginScreen() {
val intent = Intent(this, LoginActivity::class.java)
startActivity(intent)
}
}

Contracts(interfaces)


interface RegisterUserContracts {
interface View{
fun onSuccessfulRegistration()
fun onRegistrationFailure()
fun onEmptyEmail()
fun onEmptyPassword()
fun onEmptyUsername()
}
interface Presenter{
fun onFetchedUser(user:User,context: Context)

}
}

Data Layer


@Entity
data class User(
@PrimaryKey(autoGenerate = true)
val id:Int = 0,
@ColumnInfo(name = "username")
val username: String?,
@ColumnInfo(name = "email")
val email: String?,
@ColumnInfo(name = "password")
val password: String?,
@ColumnInfo(name = "phone")
val phone: String?
)

Presentation Layer

class RegisterUserPresenter(private val view:RegisterUserContracts.View) :RegisterUserContracts.Presenter{

private var shouldRegister = true
override fun onFetchedUser(user: User,context:Context) {
val email = user.email
val password = user.password
val username = user.username
when {
email.isNullOrBlank() -> {
shouldRegister = false
view.onEmptyEmail()
}
password.isNullOrBlank() -> {
shouldRegister = false
view.onEmptyPassword()
}
username.isNullOrBlank() -> {
shouldRegister = false
view.onEmptyUsername()
}
}
if (!shouldRegister){
view.onRegistrationFailure()
} else {
launch {
withContext(Dispatchers.IO){
UserDatabase.getDatabase(context).userDao().insertUser(user)
}
}
view.onSuccessfulRegistration()
}
}


}

Here we see our presentation layers hold reference of view

MVVM

MVVM composed of model, view, and view model

Model: Model represents our data layer like POJO class/data class or structures that hold data

View: View represents the UI layer which includes app components like EditText, TextView, etc.

ViewModel: Most of the logic lies here. It is a bridge between view and business logic and it doesn’t have any direct reference to view but it needs to update UI which will be done by observables. When data changes observable notifies

let’s see an example below

Data layer(Model)

data class User(val username: String,val password: String) 

View(Activity)

class LoginActivity : AppCompatActivity() {

var binding: ActivityLoginBinding? = null
var viewmodel: LoginViewModel? = null
var customeProgressDialog: CustomeProgressDialog? = null

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_login)
viewmodel = ViewModelProviders.of(this).get(LoginViewModel::class.java)
binding?.viewmodel = viewmodel
customeProgressDialog = CustomeProgressDialog(this)
initObservables()


}

private fun initObservables() {
viewmodel?.progressDialog?.observe(this, Observer {
if (it!!) customeProgressDialog?.show() else customeProgressDialog?.dismiss()
})

viewmodel?.userLogin?.observe(this, Observer { user ->
Toast.makeText(this, "welcome, ${user?.username}", Toast.LENGTH_LONG).show()
})
}


}

View Model

class LoginViewModel(application: Application) : AndroidViewModel(application), Callback<User> {


var btnSelected: ObservableBoolean? = null
var username: ObservableField<String>? = null
var password: ObservableField<String>? = null
var progressDialog: SingleLiveEvent<Boolean>? = null
var userLogin: MutableLiveData<User>? = null

init {
btnSelected = ObservableBoolean(false)
progressDialog = SingleLiveEvent<Boolean>()
username = ObservableField("")
password = ObservableField("")
userLogin = MutableLiveData<User>()
}

fun onUsernameChanged(s: CharSequence, start: Int, befor: Int, count: Int) {
btnSelected?.set(Util.isUsernameValid(s.toString()) && password?.get()!!.length >= 8)


}

fun onPasswordChanged(s: CharSequence, start: Int, befor: Int, count: Int) {
btnSelected?.set(Util.isUsernameValid(username?.get()!!) && s.toString().length >= 8)


}

fun login() {
progressDialog?.value = true
WebServiceClient.client.create(DummyBackEndApi::class.java).LOGIN(username = username?.get()!!
, password = password?.get()!!)
.enqueue(this)

}

override fun onResponse(call: Call<User>?, response: Response<User>?) {
progressDialog?.value = false
userLogin?.value = response?.body()

}

override fun onFailure(call: Call<User>?, t: Throwable?) {
progressDialog?.value = false

}

}

Why MVVM is better than MVP?

MVVM offers some important reasons to choose MVVM instead of MVP that are No tight coupling, no interfaces, event-driven code, more testable and maintainable while MVP have tight coupling between view and presenter and less maintainable and testable than MVVM

What do you say? tell me in the comments section

--

--