Clean Architecture – Presenter

Clean Architecture

Introducción

En este módulo vamos a hablar sobre el Presenter y su función.

Básicamente la función del presenter es de delegar la logica a su interactor y servir como un pasamanos entre la vista y la capa de dominio, para dar un ejemplo de que es lo que hace vamos al siguiente ejemplo práctico.

Como ejemplo vamos a hacer la funcionalidad que vamos a desarrollar luego con el Presenter, vamos a llevar a cabo el login de un usuario.

Para esto lo primero que debemos hacer es desde la vista llamar a un método en el Presenter.

LoginActivity.kt

override fun signIn() {
    val email = etxt_email.text.toString().trim()
    val password = etxt_password.text.toString().trim()
    if (presenter.checkEmptyFields(email, password)) {
        toast(this, "Uno o ambos campos son vacios")
    } else {
        presenter.signInUserWithEmailAndPassword(email, password)
    }
}

Como podemos observar, desde la Vista estamos llamando a dos metodos del Presenter, el primer metodo checkEmptyFields(email,password) se va a encargar de chequear si los campos ingresados en los EditTexts son vacios.

Tenemos otro método llamado signInUserWithEmailAndPassword(email, password) , este método es el encargado de loguear al usuario dentro de nuestra app con el email y contraseña proporsionados. Luego, el presenter será quien derive la lógica de loguin al interactor, pero eso lo vamos a ver en el siguiente video.

Lo que tiene que quedar en claro en este módulo es como la vista interactua con el presenter.

Presenter

Como anteriormente mensionamos, el propósito de este es el de llevar a cabo lógica simple para la vista como chequear por campos vacios, chequear expresiones regulares para comprobaciones, y demas. 

Su propósito principal es invocar lógida del dominio para poder decirle a la vista cuando esa información este lista para mostrarla o realizar una acción.

LoginPresenter.kt

/*
 *
 *  * Copyright (C) 2019 Gastón Luis Saillén.
 *  *
 *  * Licensed under the Apache License, Version 2.0 (the "License");
 *  * you may not use this file except in compliance with the License.
 *  * You may obtain a copy of the License at
 *  *
 *  *      http://www.apache.org/licenses/LICENSE-2.0
 *  *
 *  * Unless required by applicable law or agreed to in writing, software
 *  * distributed under the License is distributed on an "AS IS" BASIS,
 *  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  * See the License for the specific language governing permissions and
 *  * limitations under the License.
 *
 */

package com.gaston.cleanfirestorelogin.presentation.login.presenter

import com.gaston.cleanfirestorelogin.presentation.login.LoginContract

/**
 * Created by Gastón Saillén on 04 May 2019
 */
class LoginPresenter() : LoginContract.LoginPresenter {

    var view: LoginContract.LoginView? = null


    override fun attachView(view: LoginContract.LoginView) {
        this.view = view
    }

    override fun dettachView() {
        view = null
    }

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

    override fun signInUserWithEmailAndPassword(email: String, password: String) {
        view?.showProgressBar()
        view?.showError("HOLA DESDE PRESENTER.")

    }

    override fun checkEmptyFields(email: String, password: String): Boolean {
        return email.isEmpty() || password.isEmpty()
    }


}

Como podemos observar dentro de signInUserWithEmailAndPassword(..) vamos a ejecutar acciones de la vista, por ejemplo, antes de loguear a un usuario vamos a querer mostrar un progressBar que le indique al usuario que esta ingresando al sistema, luego debajo vamos a aplicar la logica que va a realizar el interactor en un próximo artículo.

Como la vista, nuestro presenter tambien dispone de su interfaz con sus métodos.

LoginContract.kt

/*
 *
 *  * Copyright (C) 2019 Gastón Luis Saillén.
 *  *
 *  * Licensed under the Apache License, Version 2.0 (the "License");
 *  * you may not use this file except in compliance with the License.
 *  * You may obtain a copy of the License at
 *  *
 *  *      http://www.apache.org/licenses/LICENSE-2.0
 *  *
 *  * Unless required by applicable law or agreed to in writing, software
 *  * distributed under the License is distributed on an "AS IS" BASIS,
 *  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  * See the License for the specific language governing permissions and
 *  * limitations under the License.
 *
 */

package com.gaston.cleanfirestorelogin.presentation.login

/**
 * Created by Gastón Saillén on 04 May 2019
 */
interface LoginContract {

    interface LoginView {
        fun showError(msgError: String)
        fun showProgressBar()
        fun hideProgressBar()
        fun signIn()
        fun navigateToMain()
        fun navigateToRegister()
    }

    interface LoginPresenter {
        fun attachView(view: LoginView)
        fun dettachView()
        fun isViewAttached(): Boolean
        fun signInUserWithEmailAndPassword(email: String, password: String)
        fun checkEmptyFields(email: String, password: String): Boolean
    }
}

Como podemos observar nuestra interfaz de LoginPresenter tiene los metodos que va a implementar nuestra clase LoginPresenter.kt 

Sus primeros 3 metodos, attachView(view:LoginView) que funciona para añadir la vista de donde lo estamos ejecutando, dettachView() para quitar la vista del presenter a la hora de por ejemplo destruir la Actividad que lo ejecutó y por último isViewAttached(): Boolean, que nos devuelve verdadero en caso de que la vista este atachada al presenter o falso en caso contrario, esto último nos asegura que si una llamada asyncrona termina luego de que nuestar vista haya sido destruida, podamos manejar si se puede o no actualizar.

Para finalizar, deberemos llamar al Presenter desde nuestra view para poder invocar sus métodos y utilizarlos. Reemplazaremos la instancia del Presenter en nuestra Vista mas adelante con Dagger 2, pero por ahora, lo dejaremos así.

LoginActivity.kt

/*
 *
 *  * Copyright (C) 2019 Gastón Luis Saillén.
 *  *
 *  * Licensed under the Apache License, Version 2.0 (the "License");
 *  * you may not use this file except in compliance with the License.
 *  * You may obtain a copy of the License at
 *  *
 *  *      http://www.apache.org/licenses/LICENSE-2.0
 *  *
 *  * Unless required by applicable law or agreed to in writing, software
 *  * distributed under the License is distributed on an "AS IS" BASIS,
 *  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  * See the License for the specific language governing permissions and
 *  * limitations under the License.
 *
 */

package com.gaston.cleanfirestorelogin.presentation.login.view

import android.os.Bundle
import android.view.View
import com.gaston.cleanfirestorelogin.R
import com.gaston.cleanfirestorelogin.base.BaseActivity
import com.gaston.cleanfirestorelogin.presentation.login.LoginContract
import com.gaston.cleanfirestorelogin.presentation.login.presenter.LoginPresenter
import kotlinx.android.synthetic.main.activity_main.*

/**
 * Created by Gastón Saillén on 04 May 2019
 */

class LoginActivity : BaseActivity(), LoginContract.LoginView {

    lateinit var presenter: LoginPresenter


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        presenter = LoginPresenter()
        presenter.attachView(this)
        btn_signIn.setOnClickListener {
            signIn()
        }
    }

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

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

    override fun showProgressBar() {
        progressBar_signIn.visibility = View.VISIBLE
    }

    override fun hideProgressBar() {
        progressBar_signIn.visibility = View.GONE
    }

    override fun signIn() {
        val email = etxt_email.text.toString().trim()
        val password = etxt_password.text.toString().trim()
        if (presenter.checkEmptyFields(email, password)) {
            toast(this, "Uno o ambos campos son vacios")
        } else {
            presenter.signInUserWithEmailAndPassword(email, password)
        }
    }

    override fun navigateToMain() {
        //startActivity(Intent(this,MainActivity::class.java))
    }

    override fun navigateToRegister() {
        //startActivity(Intent(this,RegisterActivity::class.java))
    }

}

 

Dentro de nuestra Activity, podemos ver como instanciamos al Presenter y como atachamos la vista que lo invocó , en este caso this

presenter = LoginPresenter()
presenter.attachView(this)

Y para finalizar, podemos ver como desde nuestro método implementado por la interfaz de la vista ( signUp() ) hacemos referencia a una función del presenter que nos va a servir para loguearnos.

override fun signIn() {
    val email = etxt_email.text.toString().trim()
    val password = etxt_password.text.toString().trim()
    if (presenter.checkEmptyFields(email, password)) {
        toast(this, "Uno o ambos campos son vacios")
    } else {
        presenter.signInUserWithEmailAndPassword(email, password)
    }
}

Con esto finalizamos como conectar el presenter con nuestra vista y actualizar su estado. En el próximo artículo veremos como enlazar el Presenter con su Interactor y así obtener una respuesta mediante un CallBack si el usuario pudo o no loguearse, y de esta forma poder actualizar la vista como sea necesario.

Video Tutorial

 

Links Útiles

Este código tambien puede ser encontrado en el repositorio en GitHub