android testing mvp presenter

Android MVP: ¿Debería evitar el uso de las referencias de R.string en el presentador?



testing presenter (4)

Considero que no hay razón para llamar a ningún código de Android en Presenter (pero siempre puedes hacerlo).

Así que en tu caso:

Ver / actividad en las llamadas aCreate () -> presenter.onCreate ();

Presenter onCreate () llama -> view.setTextLabel () o lo que quieras en la vista.

Siempre desacoplar el SDK de Android de los presentadores.

En Github, puedes encontrar algunos ejemplos sobre MVP :

En un intento de desacoplar completamente el SDK de Android de mis clases de presentadores, estoy tratando de descubrir la mejor manera de evitar el acceso a las ID de recursos para las que normalmente usamos R. Pensé que solo podía crear una interfaz para acceder a elementos como los recursos de cadena, pero todavía necesito ID para hacer referencia a las cadenas. Si tuviera que hacer algo como ...

public class Presenter { private MyView view = ...; private MyResources resources = ...; public void initializeView() { view.setLabel(resources.getString(LABEL_RES_ID); } }

Todavía tengo que tener LABEL_RES_ID y luego R.string.label a R.string.label en mi puente de recursos. Es genial porque podría cambiarlo cuando la unidad esté probando con otra cosa, pero no quiero administrar otra asignación al valor de la cadena.

Si me doy por vencido y solo uso los valores de la cadena R., mi presentador estará vinculado a mi vista nuevamente. Eso no es lo ideal? ¿Hay una solución más fácil que las personas usen para evitar esto y así mantenerlos fuera del presentador? No quiero administrar cadenas de manera que no sea lo que ofrece Android, porque todavía quiero incluirlas en los archivos de diseño y obtener el beneficio de la internacionalización, etc. Quiero realizar una prueba de unidad tonta que pueda funcionar con este presentador sin tener que tener el SDK de Android generar los archivos R.java. ¿Es esto mucho pedir?


Esta será una publicación larga sobre cómo estructurar el proyecto de MVP antes de resolver su problema al final de mi respuesta.

Acabo de informar la estructura de MVP aquí cómo estructurar el proyecto de MVP a partir de mi propia respuesta.

A menudo pongo el código de lógica de negocios en Capa de modelo (no confunda con el modelo en la base de datos). A menudo renombro a XManager para evitar confusiones (como ProductManager , MediaManager ...) para que la clase presentadora solo use para mantener el flujo de trabajo.

La regla de oro es no o al menos limitar el paquete de Android de importación en la clase presentadora. Esta práctica recomendada lo apoya más fácilmente en la prueba de la clase de presentador porque el presentador ahora es solo una clase Java simple, por lo que no necesitamos el marco de Android para probar esas cosas.

Por ejemplo aquí está mi flujo de trabajo mvp.

Clase de vista : Este es un lugar donde almacena toda su vista, como un botón, vista de texto ... y configura todos los oyentes para los componentes de vista en esta capa. También en esta vista, usted define una clase de escucha para implementos del presentador más adelante. Los componentes de su vista llamarán a los métodos en esta clase de oyente.

class ViewImpl implements View { Button playButton; ViewListener listener; public ViewImpl(ViewListener listener) { // find all view this.listener = listener; playButton.setOnClickListener(new View.OnClickListener() { listener.playSong(); }); } public interface ViewListener { playSong(); } }

Clase de presentador: Aquí es donde almacena la vista y el modelo dentro para llamar más tarde. También la clase presentadora implementará la interfaz ViewListener que se ha definido anteriormente. El punto principal del presentador es el flujo de trabajo de la lógica de control.

class PresenterImpl extends Presenter implements ViewListener { private View view; private MediaManager mediaManager; public PresenterImpl(View, MediaManager manager) { this.view = view; this.manager = manager; } @Override public void playSong() { mediaManager.playMedia(); } }

Clase de administrador: Aquí está el código de lógica de negocios principal. Tal vez un presentador tendrá muchos gerentes (depende de cuán complicada sea la vista). A menudo obtenemos clase de Context través de un marco de inyección como Dagger .

Class MediaManagerImpl extends MediaManager { // using Dagger for injection context if you want @Inject private Context context; private MediaPlayer mediaPlayer; // dagger solution public MediaPlayerManagerImpl() { this.mediaPlayer = new MediaPlayer(context); } // no dagger solution public MediaPlayerManagerImpl(Context context) { this.context = context; this.mediaPlayer = new MediaPlayer(context); } public void playMedia() { mediaPlayer.play(); } public void stopMedia() { mediaPlayer.stop(); } }

Finalmente: Ponga esas cosas juntas en Actividades, Fragmentos ... Aquí es el lugar donde inicializa la vista, el administrador y asigna todo al presentador.

public class MyActivity extends Activity { Presenter presenter; @Override public void onCreate() { super.onCreate(); IView view = new ViewImpl(); MediaManager manager = new MediaManagerImpl(this.getApplicationContext()); // or this. if you use Dagger MediaManager manager = new MediaManagerImpl(); presenter = new PresenterImpl(view, manager); } @Override public void onStop() { super.onStop(); presenter.onStop(); } }

Verá que cada presentador, modelo, vista está envuelto por una interfaz. Esos componentes serán llamados a través de la interfaz. Este diseño hará que su código sea más robusto y más fácil de modificar más adelante.

En definitiva, en su situación, propongo este diseño:

class ViewImpl implements View { Button button; TextView textView; ViewListener listener; public ViewImpl(ViewListener listener) { // find all view this.listener = listener; button.setOnClickListener(new View.OnClickListener() { textView.setText(resource_id); }); } }

En caso de que la vista lógica sea complicada, por ejemplo, algunas condiciones para establecer el valor. Así que pondré la lógica en DataManager para recuperar el texto. Por ejemplo:

class Presenter { public void setText() { view.setText(dataManager.getProductName()); } } class DataManager { public String getProductName() { if (some_internal_state == 1) return getResources().getString(R.string.value1); if (some_internal_state == 2) return getResources().getString(R.string.value2); } }

Así que nunca pones algo relacionado con Android en la clase de presentador. Debes moverlo a la clase View o la clase DataManager dependiendo del contexto.

Este es un post muy largo, discuta en detalle sobre MVP y cómo resolver su problema concreto. Espero que esto ayude :)


Su presenter NO debería necesitar saber cómo mostrar los detalles de cómo mostrar la interfaz de usuario y, como tal, las referencias de la R.string

Supongamos que tiene un problema de red y desea mostrarle al usuario un mensaje de error de red.

Lo primero (IMO incorrecto) sería obtener el contexto de la view y llamar a un método como este en su presenter :

public void showNetworkError(){ presenter.showMessage(view.getResources().getString(R.string.res1)); }

En el que está utilizando el context desde su view , que es una Activity o un Fragment .

Ahora, ¿qué R.string.res1 si se le R.string.res1 que cambie el contenido de la copia de R.string.res1 a R.string.res2 ? ¿Qué componente debes cambiar?

La view ¿Pero es eso necesario?

Creo que no, porque lo importante para el presenter es que la view muestre un mensaje con un error de red, ya sea "Error de red. Inténtelo de nuevo" o "Hay un error de red. Inténtelo más tarde".

Entonces, ¿cuál es la mejor manera?

Cambie su presenter a lo siguiente:

public void showNetworkError(){ view.showNetworkErrorMessage(); }

y dejar los detalles de implementación a la view :

public void showNetworkErrorMessage(){ textView.setText(R.string.resX) }

He escrito un artículo completo sobre MVP here , por si acaso.


es mejor no usar el contexto y todos los objetos que dependen de Android sdk en el presentador. Envío ID de la cadena y la vista lo convierte en cadena. como esto->

getview().setTitle(R.string.hello);

y obtener esto en la vista como esta

@Override public void setTitle(int id){ String text=context.getString(id); //do what you want to do }

Con este enfoque puedes probar tu método en presentador. Depende del objeto R pero está bien. todas las clases de MVP ubicadas en la capa de presentación en la https://github.com/android10/Android-CleanArchitecture para que pueda usar objetos de Android como la clase R. pero en la capa de dominio solo tienes que usar objetos regulares de Java

Actualizar

Para aquellos que quieran reutilizar su código en otras plataformas, puede usar una clase contenedora para asignar los tipos de identificación o enumeración a los recursos y obtener la cadena.

getView().setTitle(myStringTools.resolve(HELLO));

El método de resolución de cadenas es así y la clase puede ser proporcionada por View y DI en los presentadores.

Public String resolve(int ourID){ return context.getString(resourceMap.getValue(ourID)); }

¡Pero no lo recomiendo en la mayoría de los casos debido a una ingeniería excesiva! en la mayoría de los casos, nunca necesita el código de presentación exacto en otras plataformas, por lo que: una mejor solución sería algo así como burlarse de esa clase R en otras plataformas porque la clase R ya es como un contenedor. Deberías escribir tu propia R en otra plataforma.