Clean Architecture – View

Clean Architecture

Clean Architecture

Cuando estamos desarrollando una aplicación ya sea cual sea, tener una idea de arquitectura de desarrollo nos va a servir para poder escalar y mantener nuestro proyecto con el tiempo.

Imaginemos que tenemos un proyecto con mas de 100 clases que no estan estructuradas, añadir una nueva clase podria destruir el proyecto por completo o podria generarnos errores en un monton de otras clases que no hemos tocado, pero que , por razon alguna, dejaron de funcionar.

Otra ventaja es la de poder hacer uso de Unit Tests para probar las nuevas funcionalidades de nuestra app y asi asegurarnos que cada modulo cumple con lo que debe hacer, estos Unit Tests se nos facilitan muchisimo si dividimos nuestra app en capas.

Este artículo se basa en brindar una herramienta fundamental a la hora de desarrollar una app. Su Arquitectura.

mvp clean

Capas

Para empezar, vamos a dividir el proyecto en 3 capas o modulos importantes, estos son la capa de Data , la capa de Domain (Dominio) y la capa de Presentation (Presentación).

En esta sección nos vamos a enfocar en la capa de vista.

Vista

Esta capa es la encargada de mostrar información en la pantalla que ve el usuario, NO EXISTE lógica en esta capa, su único proposito es la de mostrar dialogos, textos y navegar entre pantallas. 

Una o mas de sus acciones pueden invocar métodos del Presenter que van a servir para actualizar su estado, pero de esto vamos a hablar en la próxima parte.

Estructura del proyecto

Para tener una organizacion de las capas, las vamos a dividir en Data, Domain y Presentation, en esta primera parte vamos a trabajar en la capa Presentation que es donde se aloja el MVP de cada feature, dentro de esta capa vamos a crear dentro del sub paquete view nuestra activity, en este caso LoginActivity.kt

Clean Architecture

LoginActivity.kt

LoginActivity solo va a implementar los metodos de su interfaz, y va a extender los metodos de BaseActivity()

/*
 *
 *  * 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))
    }

}

BaseActivity.kt

Básicamente esta Actividad la usamos para implementar sus metodos abstractos ya que la usamos en LoginActivity como padre (Heredamos sus metodos al extenderla) la usamos con la finalidad de Extension Functions, donde vamos a tener codigo repetitivo que podemos esquivar en nuestras Actividades y asi hacer mas limpio el codigo principal.

/*
 *
 *  * 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.base

import android.content.Context
import android.os.Bundle
import android.os.PersistableBundle
import android.view.Window
import android.view.WindowManager
import android.widget.Toast
import androidx.annotation.LayoutRes
import androidx.appcompat.app.AppCompatActivity

/**
 * Created by Gastón Saillén on 04 May 2019
 */
abstract class BaseActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        requestWindowFeature(Window.FEATURE_NO_TITLE)
        window.setFlags(
            WindowManager.LayoutParams.FLAG_FULLSCREEN,
            WindowManager.LayoutParams.FLAG_FULLSCREEN
        )
        super.onCreate(savedInstanceState)
        setContentView(getLayout())
    }

    @LayoutRes
    abstract fun getLayout(): Int

    fun Context.toast(context: Context = applicationContext, message: String, duration: Int = Toast.LENGTH_SHORT) {
        Toast.makeText(context, message, duration).show()
    }

}

Por último vamos a tener una interfaz con la firma de los métodos que utilizará nuestro LoginActivity. 

Utilizamos un Contrato en donde vamos a tener tanto una interfaz para la vista como para el presentador, esto nos ahorra crear una interfaz para cada uno y usar un contrato que maneje lo dos.

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()
    }
}

De esta forma terminamos con la implementación de la vista para nuestro proyecto, en la próxima parte vamos a implementar el presenter.

 

Video Tutorial

Links Útiles

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