The war between MVP and MVVM
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
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