Patrón de arquitectura MVP (Model View Presenter) en Android con ejemplo

En las etapas iniciales del desarrollo de Android , los alumnos escriben códigos de tal manera que eventualmente crean una clase MainActivity que contiene toda la lógica de implementación (lógica comercial del mundo real) de la aplicación. Este enfoque de desarrollo de aplicaciones lleva a que la actividad de Android se acople estrechamente tanto a la interfaz de usuario como al mecanismo de procesamiento de datos de la aplicación. Además, provoca dificultades en el mantenimiento y escalado de dichas aplicaciones móviles. Para evitar estos problemas de mantenibilidad, legibilidad, escalabilidad y refactorización de aplicaciones, los desarrolladores prefieren definir capas de código bien separadas. Al aplicar patrones de arquitectura de software , se puede organizar el código de la aplicación para separar las preocupaciones.La arquitectura MVP (Model — View — Presenter) es uno de los patrones de arquitectura más populares y es válido para organizar el proyecto.

MVP (Modelo — Vista — Presentador) entra en escena como una alternativa al patrón de arquitectura tradicional MVC (Modelo — Vista — Controlador) . Usando MVC como arquitectura de software, los desarrolladores terminan con las siguientes dificultades:

  • La mayor parte de la lógica empresarial central reside en Controller. Durante la vida útil de una aplicación, este archivo crece y se vuelve difícil mantener el código.
  • Debido a los mecanismos de acceso a datos y la interfaz de usuario estrechamente acoplados, tanto la capa del controlador como la de la vista se encuentran en la misma actividad o fragmento. Esto causa problemas al realizar cambios en las características de la aplicación.
  • Se vuelve difícil realizar pruebas unitarias de las diferentes capas, ya que la mayoría de las partes que se están probando necesitan componentes SDK de Android.

El patrón MVP supera estos desafíos de MVC y proporciona una manera fácil de estructurar los códigos del proyecto. La razón por la que MVP es ampliamente aceptado es que proporciona modularidad, capacidad de prueba y una base de código más limpia y fácil de mantener. Está compuesto por los siguientes tres componentes:

  • Modelo: Capa de almacenamiento de datos. Es responsable de manejar la lógica del dominio (reglas comerciales del mundo real) y la comunicación con la base de datos y las capas de red.
  • Vista: capa UI (interfaz de usuario). Proporciona la visualización de los datos y realiza un seguimiento de la acción del usuario para notificar al Presentador.
  • Presentador: obtenga los datos del modelo y aplique la lógica de la interfaz de usuario para decidir qué mostrar. Administra el estado de la Vista y realiza acciones de acuerdo con la notificación de entrada del usuario desde la Vista.

MVP (Model — View — Presenter) Architecture Pattern in Android

Puntos clave de la arquitectura MVP

  1. La comunicación entre View-Presenter y Presenter-Model ocurre a través de una interfaz (también llamada Contrato) .
  2. Una clase de presentador administra una vista a la vez, es decir, existe una relación uno a uno entre el presentador y la vista.
  3. La clase Model y View no tienen conocimiento sobre la existencia de cada uno.

Ejemplo de Arquitectura MVP

Para mostrar la implementación del patrón de arquitectura MVP en proyectos, aquí hay un ejemplo de una aplicación de Android de actividad única. La aplicación mostrará algunas strings en la Vista (Actividad) al hacer una selección aleatoria del Modelo. La función de la clase Presenter es mantener la lógica comercial de la aplicación alejada de la actividad. A continuación se muestra la implementación completa paso a paso de esta aplicación de Android. Tenga en cuenta que vamos a implementar el proyecto utilizando el lenguaje Java y Kotlin .

Nota: Los siguientes pasos se realizan en Android Studio versión 4.0

Paso 1: Crear un nuevo proyecto

  1. Haga clic en Archivo, luego en Nuevo => Nuevo proyecto.
  2. Elija Actividad vacía
  3. Seleccionar idioma como Java/Kotlin
  4. Seleccione el SDK mínimo según su necesidad.

Paso 2: modificar el archivo String.xml

Todas las strings que se utilizan en la actividad se enumeran en este archivo.

XML

<resources>
    <string name="app_name">GfG | MVP Architecture</string>
    <string name="buttonText">Display Next Course</string>
    <string name="heading">MVP Architecture Pattern</string>
    <string name="subHeading">GeeksforGeeks Computer Science Online Courses</string>
    <string name="description">Course Description</string>
</resources>

Paso 3: trabajar con el archivo activity_main.xml

Abra el archivo activity_main.xml y agregue un botón, un TextView para mostrar la string y una barra de progreso para darle una sensación dinámica a la aplicación. A continuación se muestra el código para diseñar un diseño de actividad adecuado.

XML

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#168BC34A"
    tools:context=".MainActivity">
  
    <!-- TextView to display heading of the activity -->
    <TextView
        android:id="@+id/textView3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:fontFamily="@font/roboto"
        android:text="@string/heading"
        android:textAlignment="center"
        android:textColor="@android:color/holo_green_dark"
        android:textSize="30sp"
        android:textStyle="bold"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.498"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.060000002" />
  
    <!-- TextView to display sub heading of the activity -->
    <TextView
        android:id="@+id/textView2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:fontFamily="@font/roboto"
        android:text="@string/subHeading"
        android:textAlignment="center"
        android:textColor="@android:color/holo_green_dark"
        android:textSize="24sp"
        android:textStyle="bold"
        app:layout_constraintBottom_toTopOf="@+id/button"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="1.0"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.356" />
  
    <!-- TextView to display the random string -->
    <TextView
        android:id="@+id/textView"
        android:layout_width="411dp"
        android:layout_height="wrap_content"
        android:fontFamily="@font/roboto"
        android:gravity="center"
        android:padding="8dp"
        android:text="@string/description"
        android:textAlignment="center"
        android:textAppearance="?android:attr/textAppearanceSearchResultTitle"
        app:layout_constraintBottom_toTopOf="@+id/button"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/textView2"
        app:layout_constraintVertical_bias="0.508" />
  
    <!-- Button to display next random string -->
    <Button
        android:id="@+id/button"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_margin="@android:dimen/notification_large_icon_height"
        android:background="#4CAF50"
        android:text="@string/buttonText"
        android:textAllCaps="true"
        android:textColor="@android:color/background_light"
        android:textSize="20sp"
        android:textStyle="bold"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.79" />
  
    <!-- Progress Bar to be displayed before displaying next string -->
    <ProgressBar
        android:id="@+id/progressBar"
        style="?android:attr/progressBarStyleLarge"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:visibility="gone"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
  
    <ImageView
        android:id="@+id/imageView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/button"
        app:layout_constraintVertical_bias="1.0"
        app:srcCompat="@drawable/banner" />
  
</androidx.constraintlayout.widget.ConstraintLayout>

Paso 4: definición del archivo de interfaz de contrato para el modelo, la vista y el presentador

Para establecer comunicación entre View-Presenter y Presenter-Model, se necesita una interfaz. Esta clase de interfaz contendrá todos los métodos abstractos que se definirán más adelante en las clases Vista, Modelo y Presentador.

Java

public interface Contract {
    interface View {
        // method to display progress bar
        // when next random course details
        // is being fetched
        void showProgress();
  
        // method to hide progress bar
        // when next random course details
        // is being fetched
        void hideProgress();
  
        // method to set random
        // text on the TextView
        void setString(String string);
    }
  
    interface Model {
  
        // nested interface to be
        interface OnFinishedListener {
            // function to be called
            // once the Handler of Model class
            // completes its execution
            void onFinished(String string);
        }
  
        void getNextCourse(Contract.Model.OnFinishedListener onFinishedListener);
    }
  
    interface Presenter {
  
        // method to be called when
        // the button is clicked
        void onButtonClick();
  
        // method to destroy
        // lifecycle of MainActivity
        void onDestroy();
    }
}

Kotlin

interface Contract {
    interface View {
        // method to display progress bar
        // when next random course details
        // is being fetched
        fun showProgress()
  
        // method to hide progress bar
        // when next random course details
        // is being fetched
        fun hideProgress()
  
        // method to set random
        // text on the TextView
        fun setString(string: String?)
    }
  
    interface Model {
        // nested interface to be
        interface OnFinishedListener {
            // function to be called
            // once the Handler of Model class
            // completes its execution
            fun onFinished(string: String?)
        }
  
        fun getNextCourse(onFinishedListener: OnFinishedListener?)
    }
  
    interface Presenter {
        // method to be called when
        // the button is clicked
        fun onButtonClick()
  
        // method to destroy
        // lifecycle of MainActivity
        fun onDestroy()
    }
}

Paso 5: Creando la clase Modelo

Cree una nueva clase llamada Modelo para separar todos los datos de string y los métodos para obtener esos datos. Esta clase no conocerá la existencia de View Class.

Java

import android.os.Handler;
  
import java.util.Arrays;
import java.util.List;
import java.util.Random;
  
public class Model implements Contract.Model {
  
    // array list of strings from which
    // random strings will be selected
    // to display in the activity
    private List<String> arrayList = Arrays.asList(
            "DSA Self Paced: Master the basics of Data Structures and Algorithms to solve complex problems efficiently. ",
            "Placement 100: This course will guide you for placement with theory,lecture videos, weekly assignments " +
                    "contests and doubt assistance.",
            "Amazon SDE Test Series: Test your skill & give the final touch to your preparation before applying for " +
                    "product based against like Amazon, Microsoft, etc.",
            "Complete Interview Preparation: Cover all the important concepts and topics required for the interviews. " +
                    "Get placement ready before the interviews begin",
            "Low Level Design for SDE 1 Interview: Learn Object-oriented Analysis and Design to prepare for " +
                    "SDE 1 Interviews in top companies"
    );
  
    @Override
    // this method will invoke when
    // user clicks on the button
    // and it will take a delay of
    // 1200 milliseconds to display next course detail
    public void getNextCourse(final OnFinishedListener listener) {
        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                listener.onFinished(getRandomString());
            }
        }, 1200);
    }
  
    // method to select random
    // string from the list of strings
    private String getRandomString() {
        Random random = new Random();
        int index = random.nextInt(arrayList.size());
        return arrayList.get(index);
    }
}

Kotlin

import android.os.Handler
import java.util.*
  
  
class Model : Contract.Model {
    // array list of strings from which
    // random strings will be selected
    // to display in the activity
    private val arrayList =
        Arrays.asList(
            "DSA Self Paced: Master the basics of Data Structures and Algorithms to solve complex problems efficiently. ",
            "Placement 100: This course will guide you for placement with theory,lecture videos, weekly assignments " +
                    "contests and doubt assistance.",
            "Amazon SDE Test Series: Test your skill & give the final touch to your preparation before applying for " +
                    "product based against like Amazon, Microsoft, etc.",
            "Complete Interview Preparation: Cover all the important concepts and topics required for the interviews. " +
                    "Get placement ready before the interviews begin",
            "Low Level Design for SDE 1 Interview: Learn Object-oriented Analysis and Design to prepare for " +
                    "SDE 1 Interviews in top companies"
        )
  
    // this method will invoke when
    // user clicks on the button
    // and it will take a delay of
    // 1200 milliseconds to display next course detail
    override fun getNextCourse(onFinishedListener: Contract.Model.OnFinishedListener?) {
        Handler().postDelayed({ onFinishedListener!!.onFinished(getRandomString) }, 1200)
    }
  
  
    // method to select random
    // string from the list of strings
    private val getRandomString: String
        private get() {
            val random = Random()
            val index = random.nextInt(arrayList.size)
            return arrayList[index]
        }
}

Paso 6: Creación de la clase Presentador

Los métodos de esta clase contienen una lógica comercial central que decidirá qué mostrar y cómo mostrarlo. Activa la clase View para realizar los cambios necesarios en la interfaz de usuario.

Java

public class Presenter implements Contract.Presenter, Contract.Model.OnFinishedListener {
  
    // creating object of View Interface
    private Contract.View mainView;
  
    // creating object of Model Interface
    private Contract.Model model;
  
    // instantiating the objects of View and Model Interface
    public Presenter(Contract.View mainView, Contract.Model model) {
        this.mainView = mainView;
        this.model = model;
    }
  
    @Override
    // operations to be performed
    // on button click
    public void onButtonClick() {
        if (mainView != null) {
            mainView.showProgress();
        }
        model.getNextCourse(this);
    }
  
    @Override
    public void onDestroy() {
        mainView = null;
    }
  
    @Override
    // method to return the string
    // which will be displayed in the
    // Course Detail TextView
    public void onFinished(String string) {
        if (mainView != null) {
            mainView.setString(string);
            mainView.hideProgress();
        }
    }
}

Kotlin

// instantiating the objects of View and Model Interface
// creating object of View Interface
// creating object of Model Interface
class Presenter(
    private var mainView: Contract.View?,
    private val model: Contract.Model) : Contract.Presenter,
    Contract.Model.OnFinishedListener {
  
    // operations to be performed
    // on button click
    override fun onButtonClick() {
        if (mainView != null) {
            mainView!!.showProgress()
        }
        model.getNextCourse(this)
    }
  
    override fun onDestroy() {
        mainView = null
    }
  
    // method to return the string
    // which will be displayed in the
    // Course Detail TextView
    override fun onFinished(string: String?) {
        if (mainView != null) {
            mainView!!.setString(string)
            mainView!!.hideProgress()
        }
    }
  
}

Paso 7: Defina las funcionalidades de View en el archivo MainActivity

La clase View es responsable de actualizar la interfaz de usuario de acuerdo con los cambios activados por la capa Presenter. Los datos proporcionados por el Modelo serán utilizados por View y se realizarán los cambios oportunos en la actividad. 

Java

import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;
  
import static android.view.View.GONE;
  
public class MainActivity extends AppCompatActivity implements Contract.View {
  
    // creating object of TextView class
    private TextView textView;
  
    // creating object of Button class
    private Button button;
  
    // creating object of ProgressBar class
    private ProgressBar progressBar;
  
    // creating object of Presenter interface in Contract
    Contract.Presenter presenter;
  
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
  
        // assigning ID of the TextView
        textView = findViewById(R.id.textView);
  
        // assigning ID of the Button
        button = findViewById(R.id.button);
  
        // assigning ID of the ProgressBar
        progressBar = findViewById(R.id.progressBar);
  
        // instantiating object of Presenter Interface
        presenter = new Presenter(this, new Model());
  
        // operations to be performed when
        // user clicks the button
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                presenter.onButtonClick();
            }
        });
    }
  
    @Override
    protected void onResume() {
        super.onResume();
    }
  
    @Override
    protected void onDestroy() {
        super.onDestroy();
        presenter.onDestroy();
    }
  
    @Override
    // method to display the Course Detail TextView
    public void showProgress() {
        progressBar.setVisibility(View.VISIBLE);
        textView.setVisibility(View.INVISIBLE);
    }
  
    @Override
    // method to hide the Course Detail TextView
    public void hideProgress() {
        progressBar.setVisibility(GONE);
        textView.setVisibility(View.VISIBLE);
    }
  
    @Override
    // method to set random string
    // in the Course Detail TextView
    public void setString(String string) {
        textView.setText(string);
    }
}

Kotlin

import android.os.Bundle
import android.view.View
import android.widget.Button
import android.widget.ProgressBar
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
  
  
class MainActivity : AppCompatActivity(), Contract.View {
    // creating object of TextView class
    private var textView: TextView? = null
  
    // creating object of Button class
    private var button: Button? = null
  
    // creating object of ProgressBar class
    private var progressBar: ProgressBar? = null
  
    // creating object of Presenter interface in Contract
    var presenter: Presenter? = null
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
  
        // assigning ID of the TextView
        textView = findViewById(R.id.textView)
  
        // assigning ID of the Button
        button = findViewById(R.id.button)
  
        // assigning ID of the ProgressBar
        progressBar = findViewById(R.id.progressBar)
  
        // instantiating object of Presenter Interface
        presenter = Presenter(this, Model())
  
        // operations to be performed when
        // user clicks the button
        this.button!!.setOnClickListener(View.OnClickListener { presenter!!.onButtonClick() })
    }
  
    override fun onResume() {
        super.onResume()
    }
  
    override fun onDestroy() {
        super.onDestroy()
        presenter!!.onDestroy()
    }
  
    // method to display the Course Detail TextView
    override fun showProgress() {
        progressBar!!.visibility = View.VISIBLE
        textView!!.visibility = View.INVISIBLE
    }
  
    // method to hide the Course Detail TextView
    override fun hideProgress() {
        progressBar!!.visibility = View.GONE
        textView!!.visibility = View.VISIBLE
    }
  
    // method to set random string
    // in the Course Detail TextView
    override fun setString(string: String?) {
        textView!!.text = string
    }
}

Producción

 Ventajas de la Arquitectura MVP

  • Sin relación conceptual en los componentes de Android.
  • Fácil mantenimiento y prueba del código, ya que el modelo, la vista y la capa del presentador de la aplicación están separados.

Desventajas de la arquitectura MVP

  • Si el desarrollador no sigue el principio de responsabilidad única para descifrar el código, entonces la capa de Presentador tiende a expandirse a una gran clase que todo lo sabe.

Publicación traducida automáticamente

Artículo escrito por RISHU_MISHRA y traducido por Barcelona Geeks. The original can be accessed here. Licence: CCBY-SA

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *