Uno de los retos básicos al tratar de hacer una buena arquitectura cross platform consiste en que la navegación en cada plataforma es diferente, si han intentado desarrollar en Windows Phone y Windows 8 van a notarlo facilmente, en Windows Phone por ejemplo el NavigationService es una clase que solo podemos usar a nivel de un proyecto de UI, así que, si recuerdan, nuestras Vistas Modelo en el proyecto que tenemos, están ubicadas en un proyecto diferente, persiguiendo el tema de ser lo más cross platform posible. Ese es un problema, además de que el NavigationService de Windows Phone de nada serviría compartido.
Para dejarles opciones quiero compartirles los enlaces que encontré con propuestas parar el tema, y en el transcurso de este post, explicaré la que elegí y como la incluí en CongresoVisible
- Alejandro Campos : Ejemplo de una implementación del patrón MVVM
- Sara Silva: Adding Navigation Service
- Josue Yeray: Cualquier código autogenerado por App Studio
La solución que elegí a este problema la plantea Nick Randolph, en su artículo Estados, Navegación y Testing en Clases Portables, allí pueden encontrarlo además de explicado detalladamente, libre de toda la arquitectura que estamos trabajando, por que ella nos pone en ciertas situaciones al intentar agregarla. Eso sí lo más importante es entenderla de forma básica, por que en nuestro proyecto quedará bastante dispersa.
Lo que más me gustó de esta solución es su sencillez pero que a la vez deja ser a cada plataforma lo que es sin complicarse demasiado y teniendo una estructura apta para test.
La primera parte que vemos de esta implementación es el Contrato del INavigationService
Es así de sencilla por que de hecho lo único que pretendemos reemplazar es justamente la opción de navegar, igual si tuviesemos otros tipo de necesidades en este lugar las añadiremos posteriormente.
Según nuestra arquitectura, esperaríamos que haya en el proyecto Services y en FakeServices alguien que implemente esta interfaz pero ahí viene la diferencia, como ya lo dijimos al ser un problema puntual de cada plataforma, esta clase se va a implementar en cada plataforma. Sin embargo el FakeService si puede implementarse para los test.
La implementación concreta propuesta para Windows Phone es la siguiente:
Esta es la implementación que cambiará para cada plataforma. Si intentamos entender este código, veremos como la ruta a la que se pretende navega es el nombre de una clase, esa clase serán nuestras vistas modelo, y claramente nuestras páginas no van a llamarse como nuestras vistas modelo, sin embargo, la propuesta de Nick si es hacerlas algo similares. Ella consiste en ubicar las páginas en una carpeta y nombralas según la Vista Modelo que representa su contexto.
Para que esta indicación cobre sentido, debemos continuar las instrucciones, la que sigue consiste en crear un Uri Mapper con una estrategia particular que Nick describe en otro de sus artículos.
Si observamos con detenimiento este UriMapper nos damos cuenta que se está usando una parte del nombre de la VistaModelo para ser mapeada contra una página, así es bastante sencillo de entender, claro está nos va a forzar a tener una Vista Modelo por cada vista a la que queramos navegar, pero eso no está tan mal, es más o menos lo típico. Para que esto termine de encajar debemos cambiar una línea en App.xaml.cs para establecer nuestro UriMapper en plena inicialización.
Ahora bien, si lo han estado notando, acompañando al NavigationService hay una clase más que teniamos pendiente por implementar para nuestras apps y es un Locator. Si recuerdan nuestros Test, en ellos la misma clase de Test inicializaba las vistas modelos, pues bien para teminar la implementación de nuestro ServiceLocator, necesitamos un Locator local que sirva como contexto a nuestras vistas, es aquí donde diferimos de la implementación que hace Nick en el artículo, debido a que nosotros tenemos un manejo de dependencias a través de nuestro ServiceLocator.
Pues bien, nuestro Locator quedaría implementado de la siguiente manera
Si analizamos la implementación vemos que este Locator es el responsable de inicializar las instancias particulares de los servicios y de las vistas modelo. ¿Pero como es que esta clase inicializa todo para la app? ¿En que momento lo hace?
Pues bien, el momento es cuando empieza a ejecutarse la aplicación, ya que si lo pasaron por alto, pueden observar que el Locator está definido como recurso de la aplicación en el App.xaml
Pero además siguiendo el patrón Service Locator, debemos hacer que cada una de nuestras vistas tenga como DataContext a este Locator que es un recurso, así:
Para terminar con nuestra implementación de navegación y si son lo suficientemente curiosos habrán notado que cuando el Locator instancia cada una de las vistas modelo y las registra en el Service Locator, además inicializa su Navigator.
Pues bien cada una de las VistaModelo que requiera usar el navigator ahora implementa la interfaz INavigateViewModel, con la que además hice un ajuste en el proyecto de contratos
Así pues de acuerdo a las capacidades que requieran las interfaces de nuestras vistas modelo tendrán que implementar estas interfaces
Pero entendiendo que las implementaciones concretas de estas son comunes a todas las vistas modelos, dejamos la implementación concreta en BindableBase de la cual tenemos certeza es heredada por todas nuestras vistas modelo
Estoy del desacople es literal como juga con legos. Ahora lo que queriamos ver desde el inicio es la navegación, estando todo listo para que pueda darse, ahora navegar desde nuestras vistas modelo es tan sencillo como esto.
Bastante bonito ¿eh? Para completar nuestra implementación haremos algo que Nick muestra en su post y es crear un proyecto de Test de Windows Phone, esto solo por el mero ejercicio ya que como saben tenemos nuestra estructura de pruebas listas para hacer lo mismo.
Lo primero que debemos hacer es crear un proyecto de Pruebas Unitarias para Windows Phone.
Como van a ver en el test que se implementa, no tiene nada particular como para dejarlo en un Test de este tipo, pero como les mencionaba en ese post, si su arquitectura no está tan segmentada como la mía, bien valdria la pena ver que tienen a la mano este tipo de herramientas.
Nuestro test usando comandos sería:
Incluí este test en nuestro proyecto de test general así:
Para quienes recién empiezan con los test observen como en cada uno de los que tenemos hasta ahora hay dos estrategias diferentes, uno es con un Callback y el otro con un contador, cada uno puede hacerlo como mejor lo entienda, lo importante es hacer los test.
Ese es el fin del ejercicio, espero no haber olvidado poner ningun paso, sin embargo el código está publicado. Estamos a punto de llegar al momento donde nuestra aplicación por fín hace algo y si que hemos trabajado. :)