Introduction to MVP in SUSI Android App

Introduction to MVP in SUSI Android App

Recently in SUSI Android app, we started shifting to MVP Design Pattern. We have shifted some part of it and some part is remaining and will be shifted soon. In this blog post, I am not going to tell you about “What is MVP”. Neither will I talk about advantages and disadvantages of using MVP design pattern over other design patterns. There are many good blogs by very experienced Android Developers about the same on the internet (Some good resources at the end of this blog). Rather in this post, I will talk about how to shift your already written app code to MVP design steadily without breaking the app. Also if you are willing to contribute in SUSI Android app, this post will guide you a little about how the things are been implemented and explains the code a little.

Brief Intro

I will not go into detail about MVP Design but just a formal introduction and explanation of some terms that will make you understand the blog post better. If you want to go into details, I have added resources at the bottom, read those. So, MVP stands for Model View Presenter. Initially when you write code for an Android app, what you do is make an XML file storing all views and design and a Java Class storing everything else, which may include sending a request over the Internet, fetching through databases, updating views, etc.

But in MVP design, the logic for getting data like network request and UI updates like showing the data is separated. As the name suggests, MVP has three components:

  1. Model: A Model is a place where the code for all the business logic is written. It includes making network calls, fetching data from the database etc.
  2. View: All the UI updates like showing data, showing dialog boxes and errors, taking inputs from click listeners, animations etc are done here.
  3. Presenter: This acts as a middleman between View and Model. Takes input from View, gives it to Model. Takes data from Model and gives back to the View.

The Process goes like this

I have made this schematic to explain things better.

Implementation

Let’s take the example of Login functionality and go step by step to convert it to MVP Design Pattern.

Requirements

Before starting, make a new package named “login” and add following 6 files in it:

  1. ILoginView -> Interface
  2. ILoginPresenter -> Interface
  3. ILoginModel -> Interface
  4. LoginActivity -> Class
  5. LoginPresenter -> Class
  6. LoginModel  -> Class

Explanation

Let’s see what the above files do. Just remember, the main aim of MVP Pattern is to keep business logic separate from UI Updates.

LoginActivity -> Takes input and Makes UI Updates. Asks LoginPresenter to do word using ILoginPresenter Interface.

LoginPresenter -> Acts as a presentation layer which does all checks on data and invokes LoginModel using ILoginModel interface and in return asks LoginActivity to update UI using ILoginView interface.

LoginModel -> Stores all business logic like making network calls and fetching data. Notifies LoginPresenter about success using OnLoginFinishedListener.

I have divided the process of login 5 steps:

1. In the LoginActivity, this below code snippet takes input email, password and login URL (SUSI default or custom) and asks the presenter to login using ILoginPresenter interface.

fun startLogin() {
   val stringEmail = email.editText?.text.toString()
   val stringPassword = password.editText?.text.toString()
   val stringURL = input_url.editText?.text.toString()
   loginPresenter.login(stringEmail, stringPassword, susi_default.isChecked, this, stringURL)
}

2. In LoginPresenter, all checks like whether the email is empty or password is empty or email is not a valid email etc. If anything is not valid, then it notifies the LoginActivity using ILoginView interface to show that error. When it is confirmed that everything is correct, the presenter then asks model using ILoginModel interface to make a network call.

override fun login(email: String, password: String, isSusiServerSelected: Boolean, context: Context, url: String) {
   if (email.isEmpty()) {
       loginView?.invalidCredentials(true, Constant.EMAIL)
       return
   }

   if(password.isEmpty()) {
       loginView?.invalidCredentials(true, Constant.PASSWORD)
       return
   }

   if (!CredentialHelper.isEmailValid(email)) {
       loginView?.invalidCredentials(false, Constant.EMAIL)
       return
   }

   this.email = email
   this.context = context
   loginView?.showProgress(true)
   loginModel.login(email.trim({ it <= ' ' }).toLowerCase(), password, this)
}
  1. Now LoginModel makes the network call using below code snippet and notifies success or failure to LoginPresenter using OnLoginFinishedListener.
override fun login(email: String, password: String, listener: ILoginModel.OnLoginFinishedListener) {

   authResponseCall = ClientBuilder().susiApi
           .login(email, password)

   authResponseCall.enqueue(object : Callback<LoginResponse> {
       override fun onResponse(call: Call<LoginResponse>, response: Response<LoginResponse>) {
           listener.onSuccess(response)
       }

       override fun onFailure(call: Call<LoginResponse>, t: Throwable) {
           t.printStackTrace()
           listener.onError(t)
       }
   })
}
  1. Now presenter has been notified that whether the login network call was a success or a failure.

4.1 Success:  If Network call was made successfully, LoginPresenter checks whether the response from SUSI Server was “Okay!! Allow a user to enter” or “NO!! The password you entered is invalid” and then asks LoginActivity to act accordingly.

override fun onSuccess(response: Response<LoginResponse>) {

   loginView?.showProgress(false)

   if (response.isSuccessful && response.body() != null) {

       loginView?.onLoginSuccess(response.body().message)
 
  } else if (response.code() == 422) {
       loginView?.onLoginError(context.getString(R.string.password_invalid_title),
               context.getString(R.string.password_invalid))
   } else {
       loginView?.onLoginError("${response.code()} " + context.getString(R.string.error), response.message())
   }
}

4.2 Failure: If Network call was a failure due to reasons like SocketTimeOutException or UnknownHostException etc. then onError is called in LoginPresenter which checks what is the cause and invokes LoginActivity to take action accordingly.

override fun onError(throwable: Throwable) {
   loginView?.showProgress(false)

   if (throwable is UnknownHostException) {
       loginView?.onLoginError(context.getString(R.string.unknown_host_exception), throwable.message.toString())
   } else {
       loginView?.onLoginError(context.getString(R.string.error_internet_connectivity),
               context.getString(R.string.no_internet_connection))
   }
}
  1. Everything is done. We just need to update the UI based on success or failure of login.

5.1 Success : When login is successful, LoginActivity is finished and MainActivity is started.

override fun onLoginSuccess(message: String) {
   Toast.makeText([email protected]LoginActivity, message, Toast.LENGTH_SHORT).show()
   val intent = Intent([email protected]LoginActivity, MainActivity::class.java)
   intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK
   intent.putExtra(Constant.FIRST_TIME, true)
   startActivity(intent)
   finish()
}

5.2 Failure: If login is not successful, then Alert dialog box is displayed.

override fun onLoginError(title: String?, message: String?) {
   val notSuccessAlertboxHelper = AlertboxHelper([email protected]LoginActivity, title, message, null, null, getString(R.string.ok), null, Color.BLUE)
   notSuccessAlertboxHelper.showAlertBox()
}

Summary

Though this post does not contain all the basic stuff required to learn MVP and it its advantages of other Design Patterns but it gives you a pretty decent idea about what exactly you need to do in order to shift your already written code to MVP design. Just remember these 3 simple rules.

  1. Make View as dumb as possible. It should not contain any logic at all.
  2. Add all the logic to Model. No UI update should be made from Model.
  3. There should be no direct relation between Model and View. Presenter act as a middle man. Everything should pass from it.

If you follow these three points, MVP is easy to understand and implement.

Resources

  1. A blog/tutorial on MVP by Antonio Leiva: https://antonioleiva.com/mvp-android/
  2. A blog on MVP/tutorial by Francesco Cervone: https://medium.com/@cervonefrancesco/model-view-presenter-android-guidelines-94970b430ddf
  3. A blog/tutorial on MVP by Nitin Agarwal: https://android.jlelse.eu/android-mvp-for-beginners-25889c500443
  4. A blog/tutorial by Tin Megali: https://code.tutsplus.com/tutorials/an-introduction-to-model-view-presenter-on-android–cms-26162
  5. Youtube videos to learn MVP by Dave Kavanagh: https://www.youtube.com/playlist?list=PLLaRyV1pLh_iL47JzywLVmBvEwxeeKOLl
Close Menu