Pregunta ¿Cómo manejar la navegación basada en la IU en aplicaciones multiplataforma?


Supongamos que tiene una aplicación multiplataforma. La aplicación se ejecuta en Android y en iOS. Su idioma compartido en ambas plataformas es Java. Normalmente, escribiría su lógica empresarial en Java y toda su parte específica de UI en Java (para Android) y Objective-C (para iOS).

Típicamente cuando implementa el Patrón de MVP en una aplicación multiplataforma y en lenguaje cruzado, tendría el Modelo y el Presentador en Java y proporcionaría una interfaz Java para sus Vistas, que es conocida por sus presentadores. De esta forma, los presentadores de Java compartidos pueden comunicarse con la implementación de vista que use en la parte específica de la plataforma.

Supongamos que queremos escribir una aplicación de iOS con una parte de Java que podría compartirse más adelante con la misma aplicación de Android. Aquí hay una representación gráfica del diseño:

enter image description here

En el lado izquierdo está la parte de Java. En Java usted escribe sus modelos, controladores y sus interfaces de visualización. Usted hace todo el cableado usando inyección de dependencia. Entonces el código de Java se puede traducir a Objective-C utilizando J2objc.

En el lado derecho, tienes la parte Objective-C. Aquí tu UIViewController's puede implementar las interfaces de Java que se tradujeron en protocolos ObjectiveC.

Problema: 

Lo que estoy luchando es cómo se produce la navegación entre las vistas. Supongamos que está en UIViewControllerA y toca un botón que debería llevarlo a UIViewControllerB. ¿Qué harías?

Caso 1: 

enter image description here

Usted informa que el botón toca el controlador JavaA (1) de UIViewControllerA y Java ControllerA llama a Java ControllerB (2) que está vinculado a UIViewControllerB (3). Entonces tiene el problema de que no sabe, desde el lado del Controlador Java, cómo insertar UIViewControllerB en la jerarquía de Objective-C View. No puede manejar eso desde el lado de Java porque solo tiene acceso a las interfaces de View.

Caso 2 

enter image description here

Puede hacer la transición a UIViewControllerB ya sea modal o con un UINavigationController o lo que sea (1). Luego, primero necesita la instancia correcta de UIViewControllerB que se vincula al Controlador JavaB (2). De lo contrario, UIViewControllerB no podría interactuar con el Controlador JavaB (2,3). Cuando tenga la instancia correcta, debe decirle al Controlador JavaB que se ha revelado la Vista (UIViewControllerB).

Todavía estoy luchando con este problema de cómo manejar la navegación entre diferentes controladores.

¿Cómo puedo modelar la navegación entre diferentes Controladores y manejar la plataforma cruzada Ver los cambios de forma apropiada?


12
2018-05-15 19:32


origen


Respuestas:


Respuesta corta:

Así es como lo hacemos:

  • Para cosas simples "normales" (como un botón que abre la cámara del dispositivo o abre otra Activity/UIViewController sin ninguna lógica detrás de la acción) - ActivityA se abre directamente ActivityB. ActivityB ahora es responsable de comunicarse con la capa de lógica compartida de la aplicación si es necesario.
  • Para cualquier cosa más compleja o dependiente de la lógica, estamos usando 2 opciones:
    1. ActivityA llama un método de algunos UseCase que devuelve un enum o public static final int y toma alguna acción en consecuencia -O-
    2. Dijo UseCase puede llamar a un método de ScreenHandler nos registramos antes, que sabe cómo abrir Activities desde cualquier lugar de la aplicación con algunos parámetros proporcionados.

Respuesta larga:

Soy el desarrollador principal de una empresa que utiliza una biblioteca de Java para los modelos de aplicaciones, la lógica y las reglas comerciales que ambas plataformas móviles (Android e iOS) implementan utilizando j2objc.

Mis principios de diseño provienen directamente del Tío Bob y SOLID, realmente no me gusta el uso de MVP o MVC cuando diseño aplicaciones completas completas con comunicaciones entre componentes porque luego comienzas a vincular cada Activity con 1 y solo 1 Controller que a veces está bien, pero la mayoría de las veces terminas con un Objeto de Dios de un controlador que tiende a cambiar tanto como View. Esto puede conducir a serios olores de código.

Mi forma favorita (y la que encuentro más limpia) de manejar esto es dividir todo en UseCases cada uno de los cuales maneja 1 "situación" en la aplicación. Claro que puedes tener un Controller que maneja varios de esos UseCases pero todo lo que sabe es cómo delegar en aquellos UseCases y nada más.

Además, no veo una razón para vincular cada acción de un Activity a un Controller sentado en la capa lógica, si esta acción es un simple "llévame a la pantalla del mapa" o algo por el estilo. El papel de la Activity debería manejar el Views vale, y como la única cosa "inteligente" que vive en el ciclo de vida de la aplicación, no veo ninguna razón por la que no pueda llamar al inicio de la siguiente actividad en sí.

Además, Activity/UIViewController el ciclo de vida es demasiado complejo y demasiado diferente el uno del otro para ser manejado por la lib de java común. Es algo que veo como un "detalle" y no como "reglas comerciales", cada plataforma necesita implementarse y preocuparse, haciendo que el código en java lib sea más sólido y no propenso a cambios.

Una vez más, mi objetivo es que cada componente de la aplicación sea como SRP (Principio de Responsabilidad Individual) como sea posible, y esto significa vincular la menor cantidad posible de elementos.

Entonces un ejemplo de cosas "normales" simples:

(todos los ejemplos son totalmente imaginarios)

ActivityAllUsers muestra una lista de elementos de objetos modelo. Esos elementos provienen de llamadas AllUsersInteractor - un UseCase controlleren un hilo posterior (que a su vez también manejado por el lib de java con un envío al hilo principal cuando se completa la solicitud). El usuario hace clic en uno de los elementos de esta lista. En este ejemplo, ActivityAllUsers ya tiene el objeto modelo tan abierto ActivityUserDetail es una llamada directa con un paquete (u otro mecanismo) de este objeto de modelo de datos. La nueva actividad, ActivityUserDetail, es responsable de crear y usar el correcto UseCases si se necesitan acciones adicionales.

Ejemplo de llamada lógica compleja:

ActivityUserDetail tiene un botón titulado "Agregar como amigo" que al hacer clic llama al método de devolución de llamada onAddFriendClickeden el ActivityUserDetail:

public void onAddFriendClicked() {  
  AddUserFriendInteractor addUserFriend = new AddUserFriendInteractor();
  int result = addUserFriend.add(this.user);
  switch(result){
    case AddUserFriendInteractor.ADDED:
      start some animation or whatever
      break;
    case AddUserFriendInteractor.REMOVED:
      start some animation2 or whatever
      break;
    case AddUserFriendInteractor.ERROR:
      show a toast to the user
      break;
    case AddUserFriendInteractor.LOGIN_REQUIRED:
      start the log in screen with callback to here again
      break;

  }
}

Ejemplo de llamada aún más compleja

UN BroadcastReceiver en Android o AppDelegate en iOS recibe una notificación de inserción. Esto se envía a NotificationHandler que está en la capa lógica de java lib. En el NotificationHandler constructor que se construye una vez en el App.onCreate() toma un ScreenHandler  interface que implementó en ambas plataformas. Esta notificación de inserción se analiza y se llama al método correcto en el ScreenHandler para abrir el correcto Activity.

La conclusión es: mantener el View tan tonto como puedas, mantén el Activity lo suficientemente inteligente como para manejar su propio ciclo de vida y manejar sus propios puntos de vista, y comunicarse con su propio controllers (¡plural!), y todo lo demás debería escribirse (con suerte primero en la prueba;)) en la lib de java.

Con estos métodos, nuestra aplicación actualmente ejecuta alrededor del 60-70% de su código en java lib, con la próxima actualización debería llevarlo al 70-80% con suerte.


4
2018-05-19 18:28



En el desarrollo multiplataforma compartiendo lo que yo llamo un "núcleo" (el dominio de su aplicación escrita en Java), tiendo a dar al UI propiedad de qué vista mostrar a continuación. Eso hace que su aplicación sea más flexible, adaptándose al entorno según sea necesario (utilizando un UINavigationController en iOS, Fragmentos en Android y una sola página con contenido dinámico en la interfaz web).

Tu controllers no debe vincularse a una vista, sino que debe cumplir un rol específico (un accountController para inicios de sesión / logouts, un recipeController para mostrar y editar una receta, etc.).

Tu tendrías interfaces para tu controllers en lugar de tu views. Entonces podrías usar el Patrón de diseño de fábrica para crear su instancia controllers sobre el dominio lado (su código de Java), y el views sobre el UI lado. los view factory tiene una referencia a su dominioes controller factory, y lo usa para dar a la vista solicitada algunos controladores que implementan interfaces específicas.

enter image description here

Ejemplo: Después de tocar un botón de "inicio de sesión", homeViewController pregunta el ViewControllerFactory para loginViewController. Esa fábrica a su vez pregunta al ControllerFactory para un controlador que implementa el accountHandling interfaz. A continuación, crea una nueva instancia loginViewController, le da el controlador que acaba de recibir y devuelve el controlador de vista recién instanciado al homeViewController. los homeViewController luego presenta ese nuevo controlador de vista al usuario.

Dado que su "núcleo" es independiente del entorno y solo contiene su dominio y la lógica comercial, debe permanecer estable y menos propenso a modificaciones.

Podrías echarle un vistazo a este proyecto de demostración simplificado que hice que ilustra esta configuración (menos las interfaces).


0
2018-05-19 21:54



Yo recomendaría que uses algún tipo de mecanismo de ranura. Similar a lo que usan otros marcos MVP.

Definición: una ranura es parte de una vista donde se pueden insertar otras vistas.

En su presentador puede definir tantos espacios como desee:

GenericSlot slot1 = new GenericSlot();
GenericSlot slot2 = new GenericSlot();
GenericSlot slot3 = new GenericSlot();

Estas ranuras deben tener una referencia en la vista del presentador. Puedes implementar un

setInSlot(Object slot, View v);

método. Si implementa setInSlot en una vista, entonces la vista puede decidir cómo se debe incluir.

Eche un vistazo a cómo se implementan las máquinas tragamonedas aquí.


0
2018-05-23 11:50