MVC Design Pattern(Model View Controller)
After developing the complex application it is very tough to manage the application, so the stable app we want it to be, we use design pattern so any developer can easily understand the code any easy change the code according to need
MVC firstly comes picture in design pattern
The Model-View-Controller Pattern:-
Model — the model layer, responsible for managing the business logic and handling network or database API.
View — the UI layer, perform the operation related to the user interface
Controller — the controller layer, gets notified of the user’s behaviour and updates the Model.
Note:- Here we can see that both the Controller and the View depend on the Model
Q- How to use MVC in android
The Activities, Fragments and Views should be the Views in the MVC world.
The Controllers should be separate classes that don’t extend or use any Android class.
The Model should be separate classes that don’t extend or use any Android class.
Things you should know:-
One problem arises when connecting the Controller to the View since the Controller needs to tell the View to update. In the Model MVC architecture, the Controller needs to hold a reference to the View. The easiest way of doing this, while focusing on testing, is to have a Base View interface, that the Activity/Fragment would extend. So the Controller would have a reference to the Base View.
Advantage:-
The Model-View-Controller pattern highly supports the separation of concerns. This advantage not only increases the understanding of the code but it also makes it easier to extend, allowing easy implementation of new features.
The Model classes don’t have any reference to Android classes. The Controller doesn’t extend or implement any Android classes and should have a reference to an interface class of the View.
the Views respect the single responsibility principle then their role is just to update the Controller for every user event and just display data from the Model, without implementing any business logic.
According to the MVC pattern, the Controller updates the Model and the View gets the data to be displayed from the Model. But who decides on how to display the data?
we have a User, with first name and last name. In the View, we need to display the user name as “Lastname, Firstname” (e.g. “ABC”, “DEF”).
If the Model’s role is to just provide the “raw” data, it means that the code in the View would be:
String firstName = userModel.getFirstName(); String lastName = userModel.getLastName(); nameTextView.setText(lastName + ", " + firstName)
The other approach is to have the Model expose only the data that needs to be displayed, hiding any business logic from the View. But then, we end up with Models that handle both business and UI logic. It would be unit-testable, but then the Model ends up, implicitly being dependent on the View.
String name = userModel.getDisplayName(); nameTextView.setText(name);
MVP(Model View Presenter)
MVP is one of the software development design patterns which is currently in use by many android application developers for better app development
The MVP pattern allows separating the presentation layer from the logic.
MVP:——–
Model: The Model holds the business logic of the application. It controls how data is created, stored, and modified.
View: The View is an interface that displays data and routes user actions to the Presenter.
Presenter: The Presenter acts as the middleman. It retrieves data from the Model and shows it in the View. It also processes user actions forwarded by the View.
MVP makes views independent from our data source. We divide the application into at least three different layers
How to implement MVP?
There are many variations of MVP and everyone can adjust the pattern idea to their needs and the way they feel more comfortable.
interface ChangePasswordContractor { interface View: BaseActivityContractor.View { fun showError(resId: Int, errorMessage: String) fun setBackground(resId: Int, drawbleId: Int) fun moveToLoginScreen() fun isOldObsecure() : Boolean fun setIsOldObsecure(isOldObsecure : Boolean) fun isNewObsecure() : Boolean fun setIsNewObsecure(isNewObsecure : Boolean) fun isConfirmObsecure() : Boolean fun setIsConfirmObsecure(isConfirmObsecure : Boolean) } interface Presenter : BaseActivityContractor.Presenter, AlertDialogCallback { fun showAlertDialog() fun onChangePasswordSuccess() fun onChangePasswordFailed(errorMessage: String) fun onSaveButtonClick(oldPassword: String, newPassword: String, confirmPassword : String) fun getSaveInstanceDataMap() : HashMap<String, Any> fun initializeSavedInstanceData(map : HashMap<String, Any>) } interface Interactor: BaseActivityContractor.Interactor { fun onApiHitOfChangePassWord(accessToken: String, map: HashMap<String, String>) } }
Above interfaces will use to pass data between three layers. To implement these interfaces we will create three classes which will implement the defined methods of interfaces.
Above methods will interact with the view layer to update user interface as needed. This ILoginView will be implemented by LoginActivity.
class ChangePasswordActivity : BaseActivity(), ChangePasswordContractor.View { override fun isOldObsecure(): Boolean = this.isOldObsecure override fun setIsOldObsecure(isOldObsecure: Boolean) { this.isOldObsecure = isOldObsecure } override fun isNewObsecure(): Boolean = this.isNewObsecure override fun setIsNewObsecure(isNewObsecure: Boolean) { this.isNewObsecure = isNewObsecure } override fun isConfirmObsecure(): Boolean = this.isConfirmObsecure override fun setIsConfirmObsecure(isConfirmObsecure: Boolean) { this.isConfirmObsecure = isConfirmObsecure } private val mChangePasswordPresenter: ChangePasswordContractor.Presenter = ChangePasswordPresenter(this) private var isOldObsecure = true private var isNewObsecure = true private var isConfirmObsecure = true override fun getLayoutResourseId(): Int = R.layout.activity_change_password override fun showError(resId: Int, errorMessage: String) { findViewById<TextInputLayout>(resId).error = errorMessage } override fun moveToLoginScreen() { val intent = Intent(this, LoginActivity::class.java) intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK) startActivity(intent) } override fun setBackground(resId: Int, drawbleId: Int) { findViewById<EditText>(resId).setBackgroundResource(drawbleId) } override fun initViews() { if (isOldObsecure) { etOldPassword.inputType = InputType.TYPE_TEXT_VARIATION_PASSWORD or InputType.TYPE_CLASS_TEXT etOldPassword.transformationMethod = PasswordTransformationMethod() etOldPassword.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, R.drawable.ic_password_hide, 0) } else { etOldPassword.inputType = InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD etOldPassword.transformationMethod = null etOldPassword.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, R.drawable.ic_password_show, 0) } if (isNewObsecure) { etNewPassword.inputType = InputType.TYPE_TEXT_VARIATION_PASSWORD or InputType.TYPE_CLASS_TEXT etNewPassword.transformationMethod = PasswordTransformationMethod() etNewPassword.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, R.drawable.ic_password_hide, 0) } else { etNewPassword.inputType = InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD etNewPassword.transformationMethod = null etNewPassword.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, R.drawable.ic_password_show, 0) } if (isConfirmObsecure) { etConfirmPassword.inputType = InputType.TYPE_TEXT_VARIATION_PASSWORD or InputType.TYPE_CLASS_TEXT etConfirmPassword.transformationMethod = PasswordTransformationMethod() etConfirmPassword.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, R.drawable.ic_password_hide, 0) } else { etConfirmPassword.inputType = InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD etConfirmPassword.transformationMethod = null etConfirmPassword.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, R.drawable.ic_password_show, 0) } tvBack.setOnClickListener { onBackPressed() } btnSave.setOnClickListener { mChangePasswordPresenter.onSaveButtonClick( etOldPassword.text.toString().trim(), etNewPassword.text.toString().trim(), etConfirmPassword.text.toString().trim() ) } etOldPassword.setOnTouchListener(object : DrawableOnClickListener(etOldPassword) { override fun onClick(v: View?, event: MotionEvent?, whichDrawable: Int) { ViewUtils.showKeyboard(this@ChangePasswordActivity, etOldPassword) if (isOldObsecure) { etOldPassword.inputType = InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD etOldPassword.transformationMethod = null etOldPassword.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, R.drawable.ic_password_show, 0) } else { etOldPassword.inputType = InputType.TYPE_TEXT_VARIATION_PASSWORD or InputType.TYPE_CLASS_TEXT etOldPassword.transformationMethod = PasswordTransformationMethod() etOldPassword.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, R.drawable.ic_password_hide, 0) } isOldObsecure = !isOldObsecure } }) etNewPassword.setOnTouchListener(object : DrawableOnClickListener(etNewPassword) { override fun onClick(v: View?, event: MotionEvent?, whichDrawable: Int) { ViewUtils.showKeyboard(this@ChangePasswordActivity, etNewPassword) if (isNewObsecure) { etNewPassword.inputType = InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD etNewPassword.transformationMethod = null etNewPassword.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, R.drawable.ic_password_show, 0) } else { etNewPassword.inputType = InputType.TYPE_TEXT_VARIATION_PASSWORD or InputType.TYPE_CLASS_TEXT etNewPassword.transformationMethod = PasswordTransformationMethod() etNewPassword.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, R.drawable.ic_password_hide, 0) } isNewObsecure = !isNewObsecure } }) etConfirmPassword.setOnTouchListener(object : DrawableOnClickListener(etConfirmPassword) { override fun onClick(v: View?, event: MotionEvent?, whichDrawable: Int) { ViewUtils.showKeyboard(this@ChangePasswordActivity, etConfirmPassword) if (isConfirmObsecure) { etConfirmPassword.inputType = InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD etConfirmPassword.transformationMethod = null etConfirmPassword.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, R.drawable.ic_password_show, 0) } else { etConfirmPassword.inputType = InputType.TYPE_TEXT_VARIATION_PASSWORD or InputType.TYPE_CLASS_TEXT etConfirmPassword.transformationMethod = PasswordTransformationMethod() etConfirmPassword.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, R.drawable.ic_password_hide, 0) } isConfirmObsecure = !isConfirmObsecure } }) } override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) outState.putSerializable(SAVED_INSTANCE_DATA, mChangePasswordPresenter.getSaveInstanceDataMap()) } @Suppress("UNCHECKED_CAST") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) if (savedInstanceState != null) { mChangePasswordPresenter.initializeSavedInstanceData( savedInstanceState.getSerializable( SAVED_INSTANCE_DATA ) as HashMap<String, Any> ) } mChangePasswordPresenter.onCreate() } }
In LoginPresenterImpl, we will have an instance of view(ILoginView) and Model(ILoginInteractor) which call methods as needed.
Interactor:-
personally we prefer Api related operation here
class ChangePasswordIntractor(private val mChangePasswordPresenter: ChangePasswordContractor.Presenter) : BaseActivityInteractor(mChangePasswordPresenter), ChangePasswordContractor.Interactor { override fun onApiHitOfChangePassWord(accessToken: String, map: HashMap<String, String>) { if (!mChangePasswordPresenter.isInternetAvailable()) { mChangePasswordPresenter.onChangePasswordFailed(ERROR_NO_INTERNET) return } RestClient.getApiInterface() .changePassword(getHeader(accessToken, CHANGE_PASSWORD), map) .enqueue(object : ResponseCallback<Any>() { override fun success(responseBody: BaseResponse<Any>) { mChangePasswordPresenter.onChangePasswordSuccess() } override fun failure(statusCode: Int, errorMessage: String) { mChangePasswordPresenter.onChangePasswordFailed(errorMessage) } override fun sessionExpired() { mChangePasswordPresenter.onSessionExpired() } override fun onForceUpdate() { mChangePasswordPresenter.onForceUpdate() } }) } } Presenter:-Act as a brain,which contain the reference of view and intractor. class ChangePasswordPresenter(private val mChangePasswordView: ChangePasswordContractor.View) : BaseActivityPresenter(mChangePasswordView), ChangePasswordContractor.Presenter { private val mChangePasswordIntractor: ChangePasswordIntractor = ChangePasswordIntractor(this) private var isAlertDialogOpen: Boolean = false override fun getSaveInstanceDataMap(): java.util.HashMap<String, Any> = hashMapOf( "is_old_password_visible" to mChangePasswordView.isOldObsecure(), "is_new_password_visible" to mChangePasswordView.isNewObsecure(), "is_confirm_password_visible" to mChangePasswordView.isConfirmObsecure(), "isAlertDialogOpen" to isAlertDialogOpen ) override fun initializeSavedInstanceData(map: java.util.HashMap<String, Any>) { mChangePasswordView.setIsOldObsecure(map["is_old_password_visible"].toString().toBoolean()) mChangePasswordView.setIsNewObsecure(map["is_new_password_visible"].toString().toBoolean()) mChangePasswordView.setIsConfirmObsecure(map["is_confirm_password_visible"].toString().toBoolean()) this.isAlertDialogOpen = map["isAlertDialogOpen"].toString().toBoolean() } override fun onSaveButtonClick(oldPassword: String, newPassword: String, confirmPassword : String) { mChangePasswordView.setTextFor(R.id.tv_passwoordnotmatch, "") if (oldPassword.isEmpty()) { mChangePasswordView.setTextFor(R.id.tv_passwoordnotmatch, mChangePasswordView.getStringFor(R.string.error_empty_old_password)) } else if (newPassword.isEmpty()) { mChangePasswordView.setTextFor(R.id.tv_passwoordnotmatch, mChangePasswordView.getStringFor(R.string.error_empty_new_password)) } else if (!newPassword.isPassword() || newPassword.length < 7) { mChangePasswordView.setTextFor( R.id.tv_passwoordnotmatch, mChangePasswordView.getStringFor(R.string.error_invalid_password) ) } else if (newPassword.length > 15) { mChangePasswordView.setTextFor( R.id.tv_passwoordnotmatch, mChangePasswordView.getStringFor(R.string.error_password_greaterthan15) ) } else if (confirmPassword.isEmpty()) { mChangePasswordView.setTextFor(R.id.tv_passwoordnotmatch, mChangePasswordView.getStringFor(R.string.error_empty_confirm_password)) } else if (confirmPassword.replace( "\\s".toRegex(), "" ) != newPassword.replace("\\s".toRegex(), "") ) { mChangePasswordView.setTextFor(R.id.tv_passwoordnotmatch, mChangePasswordView.getStringFor(R.string.error_password_mismatch)) } else { mChangePasswordView.showProgressDialog() mChangePasswordIntractor.onApiHitOfChangePassWord( accessToken!!, hashMapOf( "oldPassword" to oldPassword.replace("\\s".toRegex(), ""), "newPassword" to newPassword.replace("\\s".toRegex(), "") ) ) } } override fun onChangePasswordSuccess() { mChangePasswordView.dismissProgressDialog() showAlertDialog() } override fun onChangePasswordFailed(errorMessage: String) { mChangePasswordView.dismissProgressDialog() mChangePasswordView.showSnackbarMessage(errorMessage) } override fun onAlertDialogPositiveButtonClicked(purposeType: Int) { } override fun onAlertDialogNegativeButtonClicked(purposeType: Int) { if (purposeType == 0) { isAlertDialogOpen = false mChangePasswordView.dismissAlertDialog() super.onBackPressed() } } override fun onAlertDialogNeutralButtonClicked(purposeType: Int) { } override fun showAlertDialog() { isAlertDialogOpen = true mChangePasswordView.showPrimaryAlertDialog( title = mChangePasswordView.getStringFor(R.string.password_change_successfully), positiveButton = "", negativeButton = mChangePasswordView.getStringFor(R.string.text_ok), isCanclable = true, purposeType = 0 ) } override fun onCreate() { if (isAlertDialogOpen) { showAlertDialog() } mChangePasswordView.addAlertDialogCallback(this) super.onCreate() mChangePasswordView.initViews() } }
MVVM (MODEL-VIEW-VIEWMODEL)
MVVM is a software architectural pattern which facilitates separation of development UI (Activity or Fragment) from the development of the business logic or back-end logic (the data model).
ViewModels are simples classes that interact with the logic/model layer and just exposes states/data and actually have no idea by whom or how that data will be consumed. Only View(Activity) holds the reference to ViewModel and not vice versa, this solves our tight coupling issue.
Note:– A single view can hold a reference to multiple ViewModels.
Q- Why it is good to use MVVM over MVP?
In MVP, the role of a Presenter is to drive the View changes and at the same time provide data for completing those operations whereas MVVM, the ViewModel only provides the data, whereas the View is responsible for consuming them and does not hold the reference to the View. The ViewModel can also hold the logic for processing users input, same as the Presenter
Note:-we can worry less about lifecycle, memory leaks and crashes.
MVVM components:-
Data Model — it abstracts the data source. The ViewModel works with the DataModel to get and save the data. it represents the business objects that encapsulate the data and behaviour of the application.
ViewModel — it exposes streams of data relevant to the View and provides the data for a specific UI component, such as a fragment or activity, and contains data-handling business logic to communicate with the model.
View — the UI that users see and view send and receive the data from the view model
The Repository holds all the business logic of the particular app section in the app.
Step to implement MVVM in your project:-
1- Add the following line in your project level build.gradle
repositories { google() jcenter() maven { url 'https://maven.fabric.io/public' }
2- Open the build.gradle file for your app or module and add dependencies:
implementation “androidx.lifecycle:lifecycle-viewmodel:2.2.0”
annotationProcessor “androidx.lifecycle:lifecycle-compiler:2.2.0”
3- Create data source
you will create a data source for your application if you are using RestAPI you might use Retrofit or Volley for parsing the JSON data and if you are using SQL database you will use Room Database or SQLite.
4- Create repository
A Repository is basically a wrapper class which contains the business logic of our app.
we are Using Room database we need to have ApplicationContext and we will write all the database operation here so the person who is working on the UI does not have to worry about any database operations.
Public class Reprository{ Public Reprository(Application application) { } }
In this Repository class, we will write all the DAO operations and access them in the UI through ViewModel.
5- Create a viewmodel
The ViewModel class allows data to survive configuration changes such as screen rotations.
The purpose of the ViewModel is to acquire and keep the information that is necessary for an Activity or a Fragment. The Activity or the Fragment should be able to observe changes in the ViewModel. ViewModels usually expose this information via LiveData or Android Data Binding. You can also use any observability construct from your favourite framework.
public class SignupViewModel extends ViewModel implements RetrofitResponseCallback { private SignupCallback signupCallback; private Context context; public void setCallback(Context context) { this.context = context; this.signupCallback = (SignupCallback) context; } private ApiInterface apiInterface(String token) { return ApiClient.getRetrofitInstance(token).create(ApiInterface.class); } public void signupUser(String email, String language, String name, String country, String state, String password) { SignupRequest signupRequest = new SignupRequest(); signupRequest.setEmail(email); signupRequest.setLanguage(language); signupRequest.setFirst_name(name); signupRequest.setCountry(country); signupRequest.setPassword(password); signupRequest.setRole("DMS_Admin"); signupRequest.setState(state); Call<SignupResponse> call = apiInterface("").signUpUser(signupRequest); ApiClient.retrofitInvokeWebService(call, this, AppConstants.REQUEST_CODE_SIGNUP); } public void sendTokenToServer(String token, String authToken, String userId, String userEmail) { ApiInterface apiInterface = ApiClient.getRetrofitInstance(authToken).create(ApiInterface.class); UserAppDetail userAppDetail = new UserAppDetail(); userAppDetail.setFirebaseId(token); userAppDetail.setUserId(userId); userAppDetail.setUserEmail(userEmail); userAppDetail.setAppVersionId(Integer.toString(BuildConfig.VERSION_CODE)); userAppDetail.setAppVersionName(BuildConfig.VERSION_NAME); Call<UserAppResponse> call = apiInterface.sendFirebaseUserDetail(userAppDetail); ApiClient.retrofitInvokeWebService(call, this, AppConstants.REQUEST_CODE_SEND_TOKEN); } @Override public void onStart(int requestCode) { if (requestCode == AppConstants.REQUEST_CODE_SIGNUP) { signupCallback.onShowProgress("SIgnUp ..."); } } @Override public void onResponse(Object call, Object response, int requestCode) { signupCallback.onSuccess(response, requestCode); } @Override public void onFailure(Object call, Object faliure, int requestCode) { if (faliure instanceof String) { signupCallback.onHideProgress(); ToasterNotification.showToast(context, faliure.toString()); } } }
Note:-ViewModel can handle the lifecycle configurations.
6- Use view model in UI