Hola a todos!
Una de las grandes novedades, como ya vimos anteriormente, en la nueva versión de Windows Phone 8.1 son las aplicaciones universales. Este nuevo tipo de proyecto nos promete ser capaces de compartir la gran mayoría de código entre una versión Windows Store y otra Windows Phone de la misma aplicación. ¿Pero realmente es así? Antes de empezar a repasar nuevas APIs, XAML y otras cosas, creo que es importante ver como podemos usar MVVM en nuestras aplicaciones universales para compartir gran parte del código.
Implementando MVVM en proyectos universales
Realmente la implementación de MVVM no cambia en absoluto, con respecto a la que podríamos hacer en Windows Phone 8.0 o Windows Store. Lo único que cambia es el proyecto donde colocamos nuestros archivos.
Mientras que anteriormente implementábamos MVVM en el propio proyecto de la aplicación o una PCL, en las apps universales usaremos el proyecto Shared para almacenar nuestros archivos. El objetivo es llegar a tener solo el XAML de las páginas en cada proyecto de plataforma y que toda la lógica de la aplicación esté centralizada en el proyecto Shared. ¿Es esto posible? Si en la mayoría de los casos. Siempre nos encontraremos con casos en que las APIs de Phone y Store no son exactamente iguales, incluso donde su valor de retorno no es el mismo. En estos casos tendremos dos opciones: Crear una ViewModel específica para cada proyecto o usar bloques condicionales de compilación. ¿Cual usar en cada caso? Pues dependerá un poco de nuestras necesidades particulares en cada momento y de lo que necesitemos hacer. Si es poco el código particular de cada plataforma y mucho el código compartido, quizás usar un bloque condicional nos ayude a compartir código y no tener que reescribirlo todo. Sin embargo, si el código particular es la mayoría de la ViewModel, quizás sea mejor crear una por cada plataforma.
Vamos a comenzar por las bases. En todo proyecto XAML con MVVM, me gusta crear dos clases base: PageBase y ViewModelBase.
PageBase
PageBase la usaré como clase base de todas mis páginas. Esto me permite varias cosas:
-
Definir comportamientos comunes a todas las páginas, como detección de internet.
-
Definición de transiciones comunes.
-
En conjunto con la ViewModelBase, “rutear” los metodos de navegación hacia las ViewModels.
-
Establecer en la ViewModelBase el Frame activo de cada página, de forma que sea más facilmente accesible.
Con esta funcionalidad, la clase PageBase tendría este aspecto:
{
private ViewModelBase vm;
public PageBase()
{
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
vm = (ViewModelBase)this.DataContext;
vm.SetAppFrame(this.Frame);
vm.OnNavigatedTo(e);
}
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
base.OnNavigatedFrom(e);
vm.OnNavigatedFrom(e);
}
protected override void OnNavigatingFrom(NavigatingCancelEventArgs e)
{
base.OnNavigatingFrom(e);
vm.OnNavigatingFrom(e);
}
}
Aquí podemos observar una de las ventajas de Windows Phone 8.1 y las aplicaciones universales. Ya no tenemos un objeto PhoneApplicationPage. Ahora todas las páginas, tanto de Windows Phone como Windows Store, heredan del objeto Page. Esto permite que de igual forma, nuestra página base pueda ser compartida entre las dos plataformas.
Podéis ver que el código no es nada de otro mundo, controlamos los métodos de navegación y en el método OnNavigateTo obtenemos la ViewModel asignada a la página y establecemos el Frame activo en la misma. Por último en todos los métodos de navegación invocamos un método que se llama igual en la ViewModelBase. De esta forma no necesitamos usar code behind para controlar la navegación de nuestras páginas.
ViewModelBase
Nuestra ViewModelBase actuará como la clase base de las cuales heredarán todas nuestras ViewModels, lo que nos permitirá darles una funcionalidad básica y homogénea a todas:
-
Acceso al objeto Frame de la página asociada.
-
Acceso a los eventos de navegación para sobre escribirlos.
-
Notificación de cambios.
-
Notificación de carga a la UI.
El código de la ViewModelBase, sería parecido a este:
{
private Frame appFrame;
private bool isBusy;
public ViewModelBase()
{
}
public Frame AppFrame
{
get { return appFrame; }
}
public bool IsBusy
{
get { return isBusy; }
set
{
isBusy = value;
RaisePropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
public abstract Task OnNavigatedFrom(NavigationEventArgs args);
public abstract Task OnNavigatingFrom(NavigatingCancelEventArgs args);
public abstract Task OnNavigatedTo(NavigationEventArgs args);
public void RaisePropertyChanged([CallerMemberName]string propertyName = «»)
{
var Handler = PropertyChanged;
if (Handler != null)
Handler(this, new PropertyChangedEventArgs(propertyName));
}
internal void SetAppFrame(Frame viewFrame)
{
appFrame = viewFrame;
}
}
Hemos creado tres métodos abstractos: OnNavigatedFrom, OnNavigatingFrom y OnNavigatedTo que implementaremos en nuestras ViewModels para poder ejecutar código durante la navegación. Esto es muy útil cuando queremos recibir un parámetro de otra página, pero también al cargar datos. Si realizamos carga de datos en el constructor, podemos ralentizar la carga de la página. Sin embargo usando el método OnNavigatedTo, realizamos la carga una vez que la página está activa, no antes.
Ahora ya podemos empezar a crear nuestras páginas y ViewModels, usando las clases base que hemos definido.
Páginas
En cada página, tendremos que incluir el namespace de nuestra página base y cambiar su tipo, tanto en XAML como en C#:
x:Class=«Wp81MVVM.MainPage»
xmlns:base=«using:Wp81MVVM.Base»
xmlns=«http://schemas.microsoft.com/winfx/2006/xaml/presentation»
xmlns:x=«http://schemas.microsoft.com/winfx/2006/xaml»
xmlns:local=«using:Wp81MVVM»
xmlns:d=«http://schemas.microsoft.com/expression/blend/2008»
xmlns:mc=«http://schemas.openxmlformats.org/markup-compatibility/2006»
mc:Ignorable=«d»>
<Grid Background=»{ThemeResource ApplicationPageBackgroundThemeBrush}«>
</Grid>
</base:PageBase>
{
using Wp81MVVM.Base;
public sealed partial class MainPage : PageBase
{
public MainPage()
{
this.InitializeComponent();
}
}
}
Ahora solo tenemos que definir una ViewModel para esta página, implementando la ViewModelBase y sus métodos abstractos de navegación:
{
public MainViewModel()
{
}
public override Task OnNavigatedTo(Windows.UI.Xaml.Navigation.NavigationEventArgs args)
{
return null;
}
public override Task OnNavigatedFrom(Windows.UI.Xaml.Navigation.NavigationEventArgs args)
{
return null;
}
public override Task OnNavigatingFrom(Windows.UI.Xaml.Navigation.NavigatingCancelEventArgs args)
{
return null;
}
}
Ya solo nos queda unir nuestra página y nuestra ViewModel, de la forma que más nos guste: usando IoC, asignándola directamente en el constructor de la página… En este caso voy a usar Unity para resolver nuestra ViewModel y servicios. Para ello, lo primero que necesitaremos es añadir una referencia en nuestro proyecto universal a Unity. En este tipo de proyectos esto significa añadir la referencia a cada proyecto de plataforma (Phone y Store). Vamos a comenzar por el proyecto Store. Solo tenemos que hacer click derecho sobre el nodo “References” del proyecto y seleccionar la opción “Manage NuGet packages”, buscar “Unity” y presionar el botón instalar:
Ahora deberíamos repetir lo mismo con el proyecto Phone, pero el resultado será el siguiente:
El problema aquí es que, en la mayoría de los casos, todavía no se han actualizado los paquetes de NuGet para soportar los tipos de proyecto “WindowsPhoneApp, version=8.1”. ¿Quiere decir esto que no podemos usar Unity? Ni mucho menos. Lo unico que quiere decir es que no podemos instalarlo desde NuGet. Como Windows Phone 8.1 y Windows Store comparten ahora todo el core de desarrollo, “en teoría” podríamos usar el mismo ensamblado que en Windows Store. Solo tenemos que hacer click con el botón derecho sobre “References” en el proyecto Windows Phone y seleccionar “Add Reference”, presionar el botón “Browse” e ir a la carpeta de packages donde se ha descargado Unity para Windows Store previamente, seleccionando la carpeta NetCore45, que es la usada en Windows Store, seleccionamos la dll dentro de esa carpeta y presionamos “add” y “ok” y listo, ya tendremos Unity funcionando en nuestra aplicación Windows Phone 8.1.
Ahora en nuestro proyecto Shared, en la carpeta Base de ViewModels, vamos a añadir una nueva clase llamada ViewModelLocator, que haga uso de Unity para registrar y resolver nuestras ViewModels, exponiéndolas mediante propiedades públicas:
{
IUnityContainer container;
public ViewModelLocator()
{
container = new UnityContainer();
container.RegisterType<MainViewModel>();
}
public MainViewModel MainViewModel
{
get { return container.Resolve<MainViewModel>(); }
}
}
Como podemos ver, desde nuestro proyecto Shared podemos acceder a cualquier ensamblado referenciado en nuestros proyectos de plataforma, haciendo muy sencillo escribir código compartido. Ya solo nos queda añadir nuestro ViewModelLocator como una referencia en el archivo App.xaml, que también se encuentra compartido en el proyecto Shared:
x:Class=«Wp81MVVM.App»
xmlns=«http://schemas.microsoft.com/winfx/2006/xaml/presentation»
xmlns:x=«http://schemas.microsoft.com/winfx/2006/xaml»
xmlns:local=«using:Wp81MVVM»
xmlns:VMBase=«using:Wp81MVVM.Base»>
<Application.Resources>
<VMBase:ViewModelLocator x:Key=«Locator»/>
</Application.Resources>
</Application>
Y ya lo podemos usar en las páginas de ambas plataformas. De la misma forma que haríamos en un proyecto de Phone o Store tradicional:
x:Class=«Wp81MVVM.MainPage»
xmlns:base=«using:Wp81MVVM.Base»
xmlns=«http://schemas.microsoft.com/winfx/2006/xaml/presentation»
xmlns:x=«http://schemas.microsoft.com/winfx/2006/xaml»
xmlns:local=«using:Wp81MVVM»
xmlns:d=«http://schemas.microsoft.com/expression/blend/2008»
xmlns:mc=«http://schemas.openxmlformats.org/markup-compatibility/2006»
mc:Ignorable=«d»
DataContext=»{Binding MainViewModel, Source={StaticResource Locator}}«>
<Grid Background=»{ThemeResource ApplicationPageBackgroundThemeBrush}«>
</Grid>
</base:PageBase>
x:Class=«Wp81MVVM.MainPage»
xmlns:base=«using:Wp81MVVM.Base»
xmlns=«http://schemas.microsoft.com/winfx/2006/xaml/presentation»
xmlns:x=«http://schemas.microsoft.com/winfx/2006/xaml»
xmlns:local=«using:Wp81MVVM»
xmlns:d=«http://schemas.microsoft.com/expression/blend/2008»
xmlns:mc=«http://schemas.openxmlformats.org/markup-compatibility/2006»
mc:Ignorable=«d»
Background=»{ThemeResource ApplicationPageBackgroundThemeBrush}«
DataContext=»{Binding MainViewModel, Source={StaticResource Locator}}«>
<Grid>
</Grid>
</base:PageBase>
En este punto, puede parecer que tener una sola página es mejor y compartirla en el proyecto Shared. Si, podríamos hacerlo sin ninguna dificultad. Pero en la mayoría de los casos, haría que en Phone o Store, la UX fuese horrible. En mi caso soy un firme defensor de hacer una interface de usuario para cada plataforma, aprovechándonos de sus puntos fuertes. Sin embargo, si que es posible que nos encontremos compartiendo pequeños trozos de interface en forma de controles de usuario y en muchos casos usando las diferentes páginas solo para modificar la distribución de la información en pantalla para aprovechar mejor el área de trabajo de cada plataforma. Esto da para un próximo artículo.
Conclusión
Esto es todo por hoy, como hemos podido ver es muy fácil compartir código con los proyectos universales, sin necesidad de hacks ni cosas extrañas (seguro que en algún momento nos hacen falta jeje) y nos permite construir apps para la plataforma Windows en conjunto. Como siempre, podéis descargar todo el código de ejemplo aquí para jugar con él y usarlo como punto de partida para vuestras apps.
Sería un buen arranque para una app que queráis presentar en el Hackathon //Publish del 16 de Mayo en Madrid, Barcelona o Palma de Mayorca. Premios, asistencia para el diseño de tu app, y mentores para ayudarte a crear el próximo hit!
Un saludo y Happy Coding!