¿Cómo manejar navegación basada en interfaz de usuario en aplicaciones de plataforma cruzada?
Suponga que tiene una aplicación de plataforma cruzada. La aplicación se ejecuta en Android y en iOS. Su idioma compartido en ambas plataformas es Java. Normalmente escribiría su lógica de negocio en Java y todo lo que UI parte específica en Java (para Android) y Objective-C (para iOS).
Normalmente, cuando se implementa el patrón MVP en una plataforma cruzada, la aplicación de idioma cruzado que tendría el modelo y el presentador en Java y proporcionar una interfaz Java para sus vistas que es conocido por sus presentadores. De esta forma, sus presentadores Java compartidos pueden comunicarse con cualquier implementación de vista que utilice en la parte específica de la plataforma.
- IOS cotrols ocultar y ido como android
- ¿Hay alguna función en Swift similar a R.java en Android?
- La codificación Base64 en Swift no se decodificará en Android
- ¿Existe una manera de pre-empaquetar la base de datos Realm (default.Realm) a través de React-Native (JS)
- Android cómo agrupar tareas asíncronas como en iOS
Supongamos que queremos escribir una aplicación de iOS con una parte de Java que podría compartirse posteriormente con la misma aplicación de Android. Aquí está una representación gráfica del diseño:
En el lado izquierdo está la parte Java. En Java escribes tus modelos, controladores así como tus interfaces de vista. Haces todo el cableado usando la inyección de dependencia. Entonces el código Java puede ser traducido a Objective-C usando J2objc .
En el lado derecho tiene la parte Objetivo-C. Aquí su UIViewController
's puede implementar las interfaces de Java que se traducen en los protocolos de ObjectiveC.
Problema:
Lo que estoy luchando es cómo la navegación entre las vistas tiene lugar. Suponga que está en UIViewControllerA y pulsa en un botón que debe llevarlo a UIViewControllerB. ¿Qué harías?
Caso 1:
Reporta el botón tocando en Java ControllerA (1) de UIViewControllerA y Java ControllerA llama a Java ControllerB (2) que está vinculado a UIViewControllerB (3). A continuación, tiene el problema de que no sabe desde el lado del controlador de Java cómo insertar el UIViewControllerB en la jerarquía de la vista de Objective-C. No puedes manejar eso desde el lado de Java porque solo tienes acceso a las interfaces de vista.
Caso 2:
Puede realizar la transición a UIViewControllerB si es modal o con un UINavigationController o lo que sea (1). Entonces, primero necesita la instancia correcta de UIViewControllerB que se enlaza con el Java ControllerB (2). De lo contrario el UIViewControllerB no podría interactuar que el Java ControllerB (2,3). Cuando tenga la instancia correcta, deberá informar a Java ControllerB que se ha revelado la Vista (UIViewControllerB).
Todavía estoy luchando con este problema de cómo manejar la navegación entre los diferentes controladores.
¿Cómo puedo modelar la navegación entre diferentes controladores y manejar la plataforma cruzada Ver cambios adecuadamente?
- Android fragmentos analógicos en iOS rápida
- IOS - Swift - Devolución de llamada cuando el teléfono se conecta
- ¿Cómo realizar pruebas sin conexión para aplicaciones que dependen de JSON?
- ¿Puedo hacer que Firebase utilice un proceso de inicio de sesión de nombre de usuario?
- ¿Puede el código Cocos2D-Swift escrito en Swift ser portado a Android?
Respuesta corta:
Aquí es cómo 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
abre directamenteActivityB
.ActivityB
ahora es responsable de comunicarse con la capa lógica de la aplicación compartida si es necesario. - Para cualquier cosa más compleja o dependiente de la lógica estamos usando 2 opciones:
-
ActivityA
llama a un método de algúnUseCase
que devuelve unenum
opublic static final int
y toma alguna acción en consecuencia -OR- - Dicho
UseCase
puede llamar a un método de unScreenHandler
que registramos anteriormente y que sabe cómo abrirActivities
comunes desde cualquier lugar de la aplicación con algunos parámetros suministrados.
-
Respuesta larga:
Soy el desarrollador principal de una empresa que utiliza una biblioteca java para los modelos de aplicaciones, la lógica y las reglas de negocio que ambas plataformas móviles (Android e iOS) implementan con j2objc.
Mis principios de diseño vienen directamente de Tío Bob y SOLID, realmente no me gusta el uso de MVP o MVC al diseñar aplicaciones completas con comunicaciones entre componentes, porque entonces empiezas a vincular cada Activity
con 1 y sólo 1 Controller
que a veces está bien, pero la mayoría de Las veces que terminas con un Objeto Divino de un controlador que tiende a cambiar tanto como una View
. Esto puede conducir a graves olores de código.
Mi manera favorita (y la que encuentro más limpia) de manejar esto es romper todo en UseCases
cada uno de los cuales maneja 1 "situación" en la aplicación. Seguro que usted puede tener un Controller
que maneja varios de esos UseCases
pero entonces todo lo que sabe es cómo delegar a esos UseCases
y nada más.
Además, no veo una razón de vincular cada acción de una Activity
a un Controller
sentado en la capa lógica, si esta acción es un simple "llevarme a la pantalla del mapa" o cualquier cosa de este tipo. El papel de la Activity
debe manejar las Views
que posee y como la única cosa "inteligente" que vive en el ciclo de vida de la aplicación, no veo ninguna razón por la cual no pueda llamar al inicio de la siguiente actividad en sí.
Además, el ciclo de vida de Activity/UIViewController
es demasiado complejo y demasiado diferente para ser manejado por el java lib común. Es algo que veo como un "detalle" y no realmente "reglas de negocio", cada plataforma necesita implementar y preocuparse, haciendo así que el código en la lib de java sea más sólido y no propenso a cambiar.
Una vez más, mi objetivo es que cada componente de la aplicación sea como SRP (Single Responsability Principle) como puede ser, y esto significa enlazar como pocas cosas juntos como sea posible.
Así que un ejemplo de cosas simples "normales":
(Todos los ejemplos son totalmente imaginarios)
ActivityAllUsers
muestra una lista de elementos de objetos de modelo. Esos elementos procedían de llamar a AllUsersInteractor
– un UseCase controller
en un subproceso (que a su vez también manejado por el java lib con un despacho 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 de modelo, de modo que abrir 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 las UseCases
correctas si se necesitan otras acciones.
Ejemplo de llamada lógica compleja:
ActivityUserDetail
tiene un botón titulado "Añadir como amigo" que cuando se hace clic llama al método de devolución de llamada onAddFriendClicked
en 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 recibirá una notificación push. Esto se envía a NotificationHandler
que está en la capa lógica java lib. En el constructor NotificationHandler
que se construye una vez en App.onCreate()
toma una interface
ScreenHandler
que implementó en ambas plataformas. Esta notificación push se analiza y se llama al método correcto en ScreenHandler
para abrir la Activity
correcta.
La conclusión es: mantener la View
tan tonta como puedas, mantener la Activity
lo suficientemente inteligente como para manejar su propio ciclo de vida y manejar sus propias vistas, y comunicarse con sus propios controllers
(plural!), Y todo lo demás debería escribirse ( Espero que pruebe primero;)) en el java lib.
Usando estos métodos de nuestra aplicación en la actualidad se ejecuta alrededor del 60-70% de su código en la biblioteca java, con la siguiente actualización debe llevar a la 70-80% esperamos.
En el desarrollo de plataforma cruzada compartiendo lo que yo llamo un "núcleo" (el dominio de su aplicación escrita en Java), tienden a dar a la UI la propiedad de qué vista para mostrar a continuación. Esto hace que su aplicación sea más flexible, adaptándose al entorno según sea necesario (utilizando un UINavigationController en iOS, Fragments en Android y una sola página con contenido dinámico en la interfaz web).
Sus controllers
no deben estar vinculados a una vista, sino que cumplen una función específica (un controlador de cuenta para logins / logouts, un recipeController para mostrar y editar una receta, etc.).
Tendrías interfaces
para tus controllers
lugar de tus views
. A continuación, puede utilizar el patrón de diseño de fábrica para instanciar sus controllers
en el lado del dominio (su código Java) y las views
en el lado de la interfaz de usuario . La view factory
tiene una referencia a la controller factory
su dominio y la utiliza para dar a la vista solicitada algunos controladores que implementan interfaces específicas.
Ejemplo: Siguiendo un toque en un botón de "inicio de sesión", un homeViewController
pregunta al ViewControllerFactory por un loginViewController
. A su vez, la fábrica solicita a ControllerFactory un controlador que implemente la interfaz accountHandling
. A continuación, instancia un nuevo loginViewController
, le da el controlador que acaba de recibir y devuelve el controlador de vista recién instanciado al homeViewController
. El homeViewController
presenta entonces el nuevo controlador de vista al usuario.
Dado que su "núcleo" es agnóstico del entorno y sólo contiene su dominio y lógica de negocio, debe permanecer estable y menos propenso a las ediciones.
Podría echar un vistazo a este proyecto de demostración simplificado que he hecho que ilustra esta configuración (menos las interfaces).
Yo recomendaría que utilice algún tipo de mecanismo de ranura. Similar a lo que otros marcos MVP utilizan.
Definición: Una ranura es una parte de una vista donde se pueden insertar otras vistas.
En su presentador puede definir tantas ranuras 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. Puede implementar un
setInSlot(Object slot, View v);
método. Si implementa setInSlot
en una vista, la vista puede decidir cómo debe incluirse.
Echa un vistazo a cómo se implementan las franjas horarias aquí .