Design Pattern

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

Leave a Reply