GUÍA DE PATRONES DE ARQUITECTURA EN ANDROID

PATRONES DE ARQUITECTURA EN ANDROID: Introducción

La programación para Android es un campo de trabajo muy amplio que requiere muchos conocimientos. Una aplicación Android no es sólo una pantalla de interacción con el usuario (front-end), sino también una aplicación back-end, que puede incluir también la gestión de bases de datos, prestando al mismo tiempo mucha atención al rendimiento y la capacidad de respuesta, y por tanto al trabajo distribuido en los diferentes hilos (necesariamente). Lo que en una aplicación web de microservicios encontramos dividido en diferentes aplicaciones, quizás incluso ejecutándose en diferentes servidores, en Android lo encontramos todo en una única aplicación, ejecutándose en la palma de la mano. No es un mundo, sino un cosmos: un microcosmos que contiene galaxias de conceptos que hay que conocer y con los que hay que familiarizarse, y en el que también es fácil perderse. Pero no temas: en AIknow podemos darte algunos consejos para que tu nave espacial mantenga el rumbo, conquistando el universo Android, empezando por el primer planeta: los patrones arquitectónicos en Android, ergo, cómo configurar la arquitectura de tu aplicación de forma ordenada y eficiente.

CONCEPTOS BÁSICOS

Para aquellos que aún no están familiarizados con Android, presentamos una rápida visión general de los elementos básicos. El punto de partida de todo proyecto Android es la Activity. Por Actividad entendemos una funcionalidad de la aplicación, consistente en

  • una parte gráfica para interactuar con el usuario. Consiste en un archivo XML que también puede «diseñarse» visualmente en una sección especial de Android Studio.
  • una parte lógica, es decir, las operaciones que se realizarán al inicio y/o en respuesta a las acciones del usuario. Consiste en una clase Java o Kotlin que extiende la clase Android AppCompatActivity.

Cada aplicación Android contiene al menos una Actividad. Si la aplicación contiene varias actividades, una de ellas se etiqueta como Actividad principal y se ejecuta primero cada vez que se inicia, y desde ella se puede navegar (mediante botones o de otra forma) a las otras Actividades.
Cada Actividad puede subdividirse en varios sub-elementos, o fracciones de la misma funcionalidad, llamados Fragment. Exactamente igual que la Actividad, el Fragmento también se compone de un XML y una clase Java o Kotlin. Cada Actividad o Fragmento posee un ciclo de vida, es decir, una serie de métodos que se ejecutan como consecuencia de eventos del sistema operativo. Por citar algunos:

  • onStart(): la actividad (o fragmento) se ha iniciado
  • onResume(): la actividad (o fragmento) se ha «reanudado» tras un estado de pausa
  • onPause(): la actividad (o fragmento) se ha puesto en pausa (por ejemplo, porque ha entrado una llamada de teléfono o se ha bloqueado la pantalla).
  • onDestroy(): la actividad (o fragmento) se ha destruido (por ejemplo, porque se ha pasado a una nueva actividad o se ha cerrado la aplicación).

Para aquellos que intentan escribir su primera aplicación Android con Android Studio, después de haberse familiarizado con los conceptos de Actividad, Fragmento y ciclo de vida, puede resultar natural añadir a la Actividad todo el código Java (o Kotlin) necesario para recuperar datos de la base de datos, mostrarlos en pantalla, gestionar eventos, notificaciones, llamar a APIs… Esto es comprensible, pero el riesgo de este enfoque es que las Actividades y Fragmentos alcancen dimensiones astronómicas, y contengan muchas tareas diferentes, por no hablar de la dificultad de mantener el orden en medio de todo este maremágnum de código. Para remediar este problema, o al menos reducir estos riesgos, los patrones arquitectónicos vienen en nuestra ayuda.

PATRONES ARQUITECTURALES: QUÉ Y POR QUÉ

Los patrones de diseño arquitectónico (de Wikipedia) expresan patrones básicos para establecer la organización estructural de un sistema de software. Un patrón arquitectónico es, por tanto, un conjunto de trucos a seguir para mantener bien separadas las capas que componen nuestra app. No es necesario para el correcto funcionamiento de la app, sin embargo es una herramienta fundamental para:

  • mantener el orden en grandes aplicaciones: una aplicación limpia y ordenada es mucho más fácil de mantener y evolucionar, y por tanto menos costosa
  • establecer la arquitectura de una aplicación pequeña que podría ampliarse en el futuro: el uso de un patrón arquitectónico permite ampliarla con agilidad, sin tener que emprender una costosa refactorización total.
  • probar fácilmente partes individuales de la lógica de business o de la interfaz de usuario

En los siguientes párrafos describiremos, con ejemplos prácticos, uno de los patrones arquitectónicos más populares en Android, el patrón MVP.

MVP (Model – View – Presenter)

Hijo del conocido MVC (Model – View – Controller), uno de los patrones arquitectónicos más conocidos en programación orientada a objetos, MVP es un patrón muy similar utilizado en programación Android, en el que el Presentador ocupa el lugar del Controlador.

patrones de arquitectura en android

El objetivo de MVP (al igual que MVC) es separar la lógica de presentación de la lógica de negocio. MVP y MVC difieren en la forma en que los tres elementos se comunican entre sí (más sobre esto en un momento). Repasemos los tres componentes:

MODEL

El Modelo es la capa que se encarga de gestionar los datos, es decir, de proporcionarlos o actualizarlos, independientemente de cuál sea la fuente (base de datos, preferencias, api). La capa de la aplicación que solicita los datos (el Presentador) es agnóstica en cuanto a cómo se recuperan los datos: depende del Modelo aplicar la lógica necesaria para garantizar que se proporcionan los datos solicitados. Por ejemplo, una aplicación puede tener una clase StarshipsRepository con un método getStarships() que devuelva la lista de naves espaciales aplicando la siguiente lógica:

  • si el dispositivo está conectado a Internet, llama a una API externa para obtener la lista actualizada de naves espaciales
  • si el dispositivo está desconectado, muestra las naves espaciales guardadas en la base de datos.

El Modelo también debe consistir en clases que no extiendan las clases Android (Actividad, Fragmento, etc.), y no deben tener referencias directas a ellas. Sólo las clases Presenter pueden llamar directamente a las clases Model para obtener datos. En esto, el patrón MVP difiere del patrón MVC, en el que en cambio las clases Modelo pueden interactuar directamente con las clases Vista.

@Singleton
public class StarshipsRepository {

private ConnectivityManager connectivityManager;
private EngineManager engineManager;

public List getStarships() {

if (isNetworkAvailable()) {
return this.serverApiManager.getStarships();
} else {
return this.databaseManager.getStarships();
}
}

public void launch() {
// to implement: launch a starship
}

...

}
VIEW

En los patrones arquitectónicos de Android, una Vista, en cambio, es la capa que se ocupa de interactuar con el usuario. Así, la Vista incluye todas las Actividades, Fragmentos y, más en general, todas las clases que representan vistas, es decir, los elementos visuales que muestran datos y recogen las acciones del usuario (toques en la pantalla, gestos, uso de la cámara, etc.). Las vistas deben ser «tontas», en el sentido de que no deben contener lógica de negocio, sino únicamente gestionar la interacción del usuario y reenviar la entrada a la capa de aplicación más baja (el Presentador). para garantizar la separación de tareas (en la medida de lo posible, recuerde que los patrones deben ser una ayuda, no una limitación). La capa Vista tiene simplemente dos tareas:

  • Recibir las interacciones del usuario con el dispositivo y reenviarlas al Presentador
  • Métodos de visualización que reciben datos de entrada para mostrarlos al usuario.

Estos métodos deben ser utilizados por el Presentador. Por tanto, la Vista tiene un papel especial: no debe la lógica de la aplicación, pero al mismo tiempo, al estar sujeta al ciclo de vida, todo empieza y termina con ella, por lo que toda la aplicación depende de ella. Puede hacer lo suyo, y las demás capas de la aplicación sólo pueden adaptarse lo mejor que puedan.

 
public class StarshipFragment extends Fragment implements StarshipContract.View {

// Il Fragment (la View) ha un riferimento con il proprio Presenter
private StarshipContract.Presenter starshipPresenter;

private RecyclerView mRecyclerView;
protected RecyclerView.LayoutManager mLayoutManager;

private FloatingActionButton takeOffButton;

public StarshipFragment() {
this.starshipPresenter = new StarshipPresenter(this);
}

public static StarshipFragment newInstance() {
return new StarshipFragment();
}

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {

View root = inflater.inflate(R.layout.fragment_starship, container, false);

// ************************
// Launch button
// ************************
takeOffButton = root.findViewById(R.id.takeOffButton);
// Ad ogni click sul pulsante, il Fragment non fa altro che chiamare un metodo del Presenter
takeOffButton.setOnClickListener( click -> starshipPresenter.launchStarship());

// ************************
// Starships list
// ************************
mRecyclerView = (RecyclerView) root.findViewById(R.id.starshipOverview);
mLayoutManager = new LinearLayoutManager(getActivity());
setRecyclerViewLayoutManager();
mRecyclerView.setAdapter(starshipsListAdapter);

return root;
}

@Override
public void onResume() {
super.onResume();
// Ogni volta che il Fragement si avvia (o si riavvia) si esegue il metodo start() del Presenter
// come detto, ogni decisione (comprese le operazioni da fare all’avvio) sono delegate al Presenter
starshipPresenter.start();
}

// ###########################################################
// Override from contract
// ###########################################################

@Override
public void showStarshipsList(List starshipList) {
this.starshipsListAdapter.updateList(starshipList);
}

@Override
public void showErrorMessage(int error) {
snackbar.setText(getString(error));
snackbar.show();
}

...

}
PRESENTER

El Presentador es el titular de la lógica empresarial. Debemos imaginarlo como el capitán de una nave (espacial): asigna tareas a los distintos miembros de la tripulación y es responsable de tomar decisiones que afectan al destino de la nave. Interactúa tanto con el Modelo como con la Vista:

  • La Vista recibe la entrada del usuario y «decide» qué hacer con esta entrada, aplicando la lógica real de la aplicación.
  • A partir del Modelo decide qué datos recuperar, y los pasa a la Vista para que los muestre al usuario.

El presentador también tiene la importante tarea de orquestar los hilos correctamente. Sin entrar en demasiados detalles, debe evitar que el hilo principal (dedicado a la interfaz de usuario) se emplee en operaciones computacionalmente pesadas, lo que podría provocar una caída de la capacidad de respuesta (o incluso un bloqueo de la aplicación, la pesadilla de todo programador de Android). Para ello, asigna al hilo principal sólo las operaciones mínimas imprescindibles para que la interfaz de usuario funcione, mientras que el resto de operaciones (operaciones de lectura/escritura, llamadas a APIs externas, etc.) las asigna a hilos específicos. El Presentador, al no ser una extensión de una clase de Android, y por tanto no tener ciclo de vida, depende de la Vista. Su propia existencia depende de la Vista. Por tanto, el Presentador puede considerarse como una herramienta, a disposición de cada Vista, para quitarle toda la lógica de negocio, aligerarla y separar tareas.

public class StarshipPresenter implements StarshipContract.Presenter {

private StarshipsRepository starshipsRepository;
private EngineManager engineManager;
private final StarshipContract.View mView;

public Mag5Presenter(StarshipContract.View mView) {
this.mView = mView;
}

// ###########################################################
// Override from contract
// ###########################################################

@Override
public void start() {

GetMag5TransportItemsList_CB cb = new GetMag5TransportItemsList_CB();
Disposable d = this.starshipsRepository.getStarships()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
result -> mView.showStarshipsList(result),
error -> mView.showErrorMessage(R.string.error_fetching_item_mag5)
);

addDisposable(d);

}

@Override
public void launchStarship() {

if (this.engineManager.isEngineReady())
this.starshipsRepository.launchStarship();

}

...

}

LA INTERFAZ CONTRACT

El contrato es un elemento del patrón MVP que sirve principalmente para mantener el orden en el proyecto y garantizar el cumplimiento de una de las «reglas» del patrón:

Cada clase de Vista debe corresponder a una clase de Presentador, con una correspondencia 1:1

La idea es la siguiente: para cada clase Vista debe haber una y sólo una clase Presentador (y viceversa), y se pretende que ambas trabajen juntas, estipulando una especie de contrato: como si se tratara de un equipo en el que el Presentador es la mente, el que decide, la Vista es el brazo, el que ejecuta, pero las dos son complementarias y trabajan en equipo para implementar la misma funcionalidad. Los contratos son interfaces que sirven precisamente para consagrar este vínculo entre cada Vista y su Presentador. A su vez, contienen dos interfaces, una para la Vista y otra para el Presentador, que a su vez contienen las firmas de los métodos correspondientes. De este modo, al examinar una clase Contract, es posible ver de un vistazo lo que se ha implementado en un par Vista/Presentador, como si fueran una misma cosa.

public interface StarshipContract {

interface View {

void showErrorMessage(int messageCode);

void showStarshipsList(List starshipList);

}

interface Presenter {

void start();

void launch();

}
}

CONCLUSIÓN

Los patrones arquitectónicos en android son una herramienta fundamental para hacer tu aplicación android ordenada, mantenible y escalable. En este artículo exploramos el patrón arquitectónico MVP (Model View Presenter) con algunos ejemplos. ¡Y no te olvides de echar un vistazo a nuestras otras noticias del blog o nuestros casos de uso para ver qué tipo de trabajo hemos hecho en AIknow!

¿Necesita una aplicación para digitalizar un proceso? ¿Quiere saber más?