Clean Architecture

Introducción

En esta sección vamos a ver como crear nuestra actividad de registro. Vamos a utilizar el mismo concepto que en nuestro login, con la diferencia que usaremos el metodo createUserWithEmailAndPassword(...) que nos proporciona Firebase para poder crear un usuario.

Antes de comenzar vamos a definir el contrato que va a sostener la firma de los metodo que utilizaremos en la vista y en el presenter

SignUpContract.kt

/**
 * Created by Gastón Saillén on 13 May 2019
 */
interface SignUpContract {

    interface SignUpView{
        fun navigateToMain()
        fun signUp()
        fun showProgress()
        fun hideProgress()
        fun showError(errormsg:String)
    }

    interface SignUpPresenter{
        fun attachView(view:RegisterView)
        fun isViewAttached():Boolean
        fun detachView()
        fun checkEmptyName(fullname:String):Boolean
        fun checkValidEmail(email:String):Boolean
        fun checkEmptyPasswords(pw1:String,pw2:String):Boolean
        fun checkPasswordsMatch(pw1:String,pw2:String):Boolean
        fun signUp(fullname:String,email:String,password:String)
    }
}

SignUpActivity.kt

Utilizamos nuestra interfaz que importa las acciones que vamos a realizar en la vista en la siguiente actividad. Definimos la lógica de nuestra pantalla de registro, mandando a nuestro presenter para crear la cuenta con la opcion presenter.signUp(fullName,email,password)

class SignUpActivity : BaseActivity(),SignUpContract.SignUpView {

    lateinit var presenter:SignUpPresenter

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        presenter = SignUpPresenter(SignUpInteractorImpl())
        presenter.attachView(this)

        btn_signUp.setOnClickListener {
            signUp()
        }

    }

    override fun getLayout(): Int {
        return R.layout.activity_register
    }

    override fun navigateToMain() {
        val intent = Intent(this,MainActivity::class.java)
        intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
        startActivity(intent)
    }

    override fun signUp() {

        val fullname:String = etx_fullname.text.toString().trim()
        val email:String = etxt_email_register.text.toString().trim()
        val pw1:String = etx_pw1.text.toString().trim()
        val pw2:String = etx_pw2.text.toString().trim()

        if(presenter.checkEmptyName(fullname)){
            etx_fullname.error = "The name is empty."
            return
        }

        if(!presenter.checkValidEmail(email)){
            etxt_email_register.error = "The email is invalid"
            return
        }

        if(presenter.checkEmptyPasswords(pw1,pw2)){
            etx_pw1.error = "Empty field"
            etx_pw2.error = "Empty field"
            return
        }

        if(!presenter.checkPasswordsMatch(pw1,pw2)){
            etx_pw1.error = "Passwords dont match"
            etx_pw2.error = "Passwords dont match"
            return
        }

        presenter.signUp(fullname,email,pw1)

    }

    override fun showProgress() {
        progress_signup.visibility = View.VISIBLE
        btn_signUp.visibility = View.GONE
    }

    override fun hideProgress() {
        progress_signup.visibility = View.GONE
        btn_signUp.visibility = View.VISIBLE
    }

    override fun showError(errormsg: String) {
        toast(this,errormsg)
    }

    override fun onDetachedFromWindow() {
        super.onDetachedFromWindow()
        presenter.detachView()
    }

    override fun onDestroy() {
        super.onDestroy()
        presenter.detachView()
    }
}

 

Vamos a definir la logica que va a utilizar nuestro presenter para poder comunicar cualquier cambio a la vista, en este caso necesitamos utilizar un interactor que en este caso va a ser nuestro SignUpInteractor, el cual contiene el metodo para crear nuestor usuario en Firebase. 

Este método nos devuelve un callback si se pudo completar o no, de esta forma obtenemos en nuestro presenter esa respuesta y podemos actualizar nuestra vista como sea necesario.

SignUpPresenter.kt

/**
 * Created by Gastón Saillén on 18 May 2019
 */
class SignUpPresenter(signUpInteractor:SignUpInteractor): RegisterContract.RegisterPresenter {

    var view:RegisterContract.RegisterView? = null
    var signUpInteractor:SignUpInteractor? = null

    init {
        this.signUpInteractor = signUpInteractor
    }

    override fun attachView(view: RegisterContract.RegisterView) {
        this.view = view
    }

    override fun isViewAttached(): Boolean {
        return view != null
    }

    override fun detachView() {
        view = null
    }

    override fun checkEmptyName(fullname: String): Boolean {
        return fullname.isEmpty()
    }

    override fun checkValidEmail(email: String): Boolean {
        return PatternsCompat.EMAIL_ADDRESS.matcher(email).matches()
    }

    override fun checkEmptyPasswords(pw1: String, pw2: String): Boolean {
        return pw1.isEmpty() or pw2.isEmpty()
    }

    override fun checkPasswordsMatch(pw1: String, pw2: String): Boolean {
        return pw1 == pw2
    }

    override fun signUp(fullname: String, email: String, password: String) {
        view?.showProgress()
        signUpInteractor?.signUp(fullname,email,password,object: SignUpInteractor.RegisterCallback{
            override fun onRegisterSuccess() {
                view?.navigateToMain()
                view?.hideProgress()
            }

            override fun onRegisterFailure(errorMsg: String) {
                view?.showError(errorMsg)
                view?.hideProgress()
            }
        })
    }
}

Por último configuramos nuestro SignUpInteractor que utiliza la lógica que nos brinda Firebase para poder registrar un usuario. 

Recordemos que este método va a activar el callback que estamos escuchando en nuestro Presenter para poder invocar metodos de la vista.

Primero definimos su interfaz

SignUpInteractor.kt

interface SignUpInteractor {

    interface RegisterCallback{
        fun onRegisterSuccess()
        fun onRegisterFailure(errorMsg:String)
    }

    fun signUp(fullname:String,email:String,password:String,listener:RegisterCallback)
}

Luego definimos su implementación

class SignUpInteractorImpl: SignUpInteractor {

    override fun signUp(fullname: String, email: String, password: String,
        listener: SignUpInteractor.RegisterCallback) {
        FirebaseAuth.getInstance().createUserWithEmailAndPassword(email,password).addOnCompleteListener { itSignUp ->
            if(itSignUp.isSuccessful){

                val profileUpdates = UserProfileChangeRequest.Builder()
                    .setDisplayName(fullname)
                    .build()

                FirebaseAuth.getInstance().currentUser?.updateProfile(profileUpdates)?.addOnCompleteListener {
                    if(it.isSuccessful){
                        listener.onRegisterSuccess()
                    }
                }

            }else{
                listener.onRegisterFailure(itSignUp.exception?.message.toString())
            }
        }
    }
}

Con esto finalizamos la creación de nuestra lógica de registro, a continuación esta el xml utilizado para crear la pantalla de registro.

activity_register.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".presentation.registro.view.SignUpActivity">

    <EditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:inputType="textPersonName"
            android:hint="@string/full_name"
            android:ems="10"
            android:id="@+id/etx_fullname"
            android:layout_marginStart="8dp"
            app:layout_constraintStart_toStartOf="parent"
            android:layout_marginEnd="8dp"
            app:layout_constraintEnd_toEndOf="parent"
            android:layout_marginTop="96dp"
            app:layout_constraintTop_toTopOf="parent"/>
    <EditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:inputType="textEmailAddress"
            android:ems="10"
            android:hint="@string/e_mail"
            android:id="@+id/etxt_email_register"
            app:layout_constraintStart_toStartOf="parent"
            android:layout_marginStart="8dp"
            app:layout_constraintEnd_toEndOf="parent"
            android:layout_marginEnd="8dp"
            android:layout_marginTop="32dp"
            app:layout_constraintTop_toBottomOf="@+id/etx_fullname"/>
    <EditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:inputType="textPassword"
            android:ems="10"
            android:hint="@string/password"
            android:id="@+id/etx_pw1"
            app:layout_constraintStart_toStartOf="parent"
            android:layout_marginStart="8dp"
            app:layout_constraintEnd_toEndOf="parent"
            android:layout_marginEnd="8dp"
            android:layout_marginTop="32dp"
            app:layout_constraintTop_toBottomOf="@+id/etxt_email_register"/>
    <EditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:inputType="textPassword"
            android:hint="@string/confirm_password"
            android:ems="10"
            android:id="@+id/etx_pw2"
            android:layout_marginTop="32dp"
            app:layout_constraintTop_toBottomOf="@+id/etx_pw1"
            android:layout_marginEnd="8dp"
            app:layout_constraintEnd_toEndOf="parent"
            android:layout_marginStart="8dp"
            app:layout_constraintStart_toStartOf="parent"/>
    <Button
            android:text="@string/signup"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:id="@+id/btn_signUp"
            android:layout_marginTop="32dp"
            app:layout_constraintTop_toBottomOf="@+id/etx_pw2"
            app:layout_constraintEnd_toEndOf="parent"
            android:layout_marginEnd="8dp"
            app:layout_constraintStart_toStartOf="parent"
            android:layout_marginStart="8dp"
            app:layout_constraintHorizontal_bias="0.498"/>
    <ProgressBar
            style="?android:attr/progressBarStyle"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:elevation="2dp"
            android:visibility="gone"
            android:id="@+id/progress_signup" app:layout_constraintEnd_toEndOf="parent" android:layout_marginEnd="8dp"
            app:layout_constraintStart_toStartOf="parent" android:layout_marginStart="8dp"
            android:layout_marginTop="32dp" app:layout_constraintTop_toBottomOf="@+id/etx_pw2"
            app:layout_constraintHorizontal_bias="0.498"/>
</androidx.constraintlayout.widget.ConstraintLayout>

Videos

 

Links útiles

Podes encontrar todo el código fuente en el repositorio en Github