Windows Phone. Técnicas para compartir código. 2º Parte

Introducción

En la primera parte sobre las técnicas para compartir código ya comentamos las técnicas que tenemos a nuestra disposición:

  • Ctrl-C, Ctrl-V: Más que un patrón a utilizar para compartir código es un evidente anti-patrón. NO debemos realizar copy & paste bajo ningun motivo. Siempre que que estemos realizando esta operación debemos pararnos a analizar la situación. Con toda probabilidad existirá alguna otra técnica mucha más efectiva que nos facilitará la gestión y mantenimiento del código.
  • Enlazado de ficheros: En proyectos Visual Studio podemos utilizar un mismo fichero sin necesidad de copiar (duplicando) el fichero en cada proyecto, es decir, utilizando el mismo fichero. Esta fácil y su vez potente estrategia para compartir código, nos permite utilizar ficheros en un proyecto vinculados a otro. Sólo trabajamos con un fichero y además trabajamos con el de manera natural, de modo que, un cambio en el fichero en cualquiera de los proyectos donde se utilice afecta al resto.
  • Compilación Condicional: Mediante directivas de compilación podemos elegir que parte del código ejecutar dependiendo de la plataforma donde se ejecute. Se debe utilizar sólo para pequeños trozos de código.
  • Componente Windows Runtime: Es un componente pensado para agrupar y exponer funcionalidad. Es compatible con Windows Phone 8 y con Windows 8.
  • Portable Class Library:  Permiten realizar el desarrollo multiplataforma de aplicaciones .NET. Podemos crear librerías portables con códiog compartido entre Windows 8, Windows Phone, Xbox 360 y otras plataformas .NET.
tc01

Técnicas para reutilizar código

para poder aplicar correctamente un conjunto de las técnicas anteriores hemos mencionado que necesitamos tener como base el patrón MVVM (Model-View-ViewModel).

El patrón MVVM

Model-View-ViewModel (MVVM) es un patrón de diseño de aplicaciones que permite desacoplar el código de interfaz de usuario del código que no sea de interfaz de usuario.

El patrón MVVM consta de 3 partes:

  • La vista (View) contiene la interfaz de usuario y su lógica.
  • El vista-modelo (ViewModel) contiene la lógica de presentación.
  • El modelo (Model) contiene la lógica de negocio junto a los datos.

La vista interactúa con el vista-modelo mediante enlace a datos (Data Binding) y mediante comandos:

tc02

Las ventajas conseguidas utilizando el patrón son significativas:

  • Nos permite dividir el trabajo de manera muy sencilla (diseñadores – desarrolladores)
  • El mantenimiento es más sencillo.
  • Permite realizar Test a nuestro código.
  • Permite una más fácil reutilización de código.

En la primera parte del artículo facilitamos el código de nuestra aplicación de recetas (sin implementar el patrón MVVM):

Tomando como base el ejemplo vamos a realizar una implementación del patrón MVVM:

  • La vista. Tenemos una página principal (MainPage) que cuenta con una lista de grupos de recetas. al seleccionar un grupo en concreto navegaremos a una segunda página que mostrará los detalles del grupo seleccionado (GroupDetailPage). Esta página cuenta con un Pivot con dos PivotItem. El primer PivotItem muestra información detallada del grupo mientras que el segundo PivotItem muestra el listado de recetas correspondiente al grupo. Al seleccionar una receta navegaremos a los detalles de la misma, otra página llamada RecipeDetailPage. Esta página está compuesta por un Pivot que muestra en distintas secciones la información detallada de la receta (pasos a a seguir – ingredientes). El mayor esfuerzo en esta sección del patrón con respecto a lo que ya teníamos se basa en la supresión de eventos que será sustituidos por comandos en los casos posibles junto a un uso más intenso de enlace a datos.
  • El vista-modelo. Si esta parte del patrón está bien implementada podemos lograr hacer muy fácil su reutilización. Para ello debemos mantener la independencia con la plataforma, es decir, debemos evitar realizar llamadas a cualquier API específica de Windows Phone desde el ViewModel. Contamos con tres clases que abastecen a las tres páginas que componen la vista. Una clase llamada MainViewModel que cargará el grupo de recetas que se muestra en la vista. Otro viewmodel llamado GroupDetailViewModel utilizado en la vista GroupDetailView que contiene una propiedad con el grupo de recetas seleccionado y un último viewmodel llamado RecipeDetailView con la receta seleccionada un comandos utilizados en el ApplicationBar (crear recordatorio, compartir receta y crear tile secundario).
  • El modelo. Hemos adaptado la clase RecipeDataSource del proyecto que partíamos a varias clases (RecipeDataGroup, RecipeDataItem, etc) implementando la interfaz INotifyPropertyChanged.

A vista de pájaro ya hemos analizado la estructura del proyecto, sin embargo, creo que merece la pena que pronfundizemos más:

Dado que nuestro principal objetivo es compartir la mayor cantidad de código posible, y todos nuestros viewmodels son candidatos perfectos a migrar a una clase portable, tenemos que evitar añadir código específico de la plataforma. No podemos utilizar directamente APIs de Windows Phone en nuestros viewmodels.

¿Cómo lo conseguimos?

Las operaciones que necesitemos en nuestra aplicación que requieran acceder a las APIs de Windows Phone las implementaremos en servicios. En nuestra aplicación hemos creado los siguientes servicios:

  • DialogService. Lo utilizamos para mostrar notificaciones en nuestra aplicación mediante Messagebox.
  • LiveTileService. El nombre deja pocas dudas. Lo utilizamos para la gestión de tiles en la aplicación, crear y eliminar tiles secundarios.
  • LocalDataService. Nos permite leer y acceder a información local como archivos json o xml.
  • NavigationService. Utilizamos el servicio NavigationService para realizar la navegación entre páginas.
  • ReminderService. Nos permite crear recordatorios.
  • ShareService. Nos permite compartir información (Lanzador EmailComposeTask).
  • StorageService. Nos permite leer y guardar información en el IsolatedStorage.

Nuestros viewmodels implementan los servicios gracias al uso de Ioc (Inversion of Control) por DI (Dependency Injection). Creamos un contenedor donde registramos todos los servicios que vamos a utilizar junto a los viewmodels que utilizarán las vistas y que accederán a los servicios utilizando interfaces.

Para ello, utilizamos Unity v2.1, el contenedor IoC de Patterns & Practices.

public class Container : IContainer
{
     private IUnityContainer _currentContainer;

     public Container()
     {
          // Crea el contenedor de inyección de dependencias
          this._currentContainer = new UnityContainer();

          // Añade los Services de los que dependen los ViewModel
          this._currentContainer.RegisterType<ILiveTileService, LiveTileService>();
          this._currentContainer.RegisterType<IStorageService, StorageService>();
          this._currentContainer.RegisterType<IDialogService, DialogService>();
          this._currentContainer.RegisterType<ILocalDataService, LocalDataService>();
          this._currentContainer.RegisterType<INavigationService, NavigationService>();
          this._currentContainer.RegisterType<IReminderService, ReminderService>();
          this._currentContainer.RegisterType<IShareService, ShareService>();

          // Añade los ViewModels y asociales los Services que le correspondan (especificados en el constructor de cada ViewModel)
          this._currentContainer.RegisterType<IMainViewModel, MainViewModel>(new ContainerControlledLifetimeManager());
          this._currentContainer.RegisterType<IGroupDetailViewModel, GroupDetailViewModel>(new ContainerControlledLifetimeManager());
          this._currentContainer.RegisterType<IRecipeDetailViewModel, RecipeDetailViewModel>(new ContainerControlledLifetimeManager());
     }

     // Resuelve una dependencia
     public T Resolve<T>()
     {
          return _currentContainer.Resolve<T>();
     }
}

Una vez creado el contenedor de Ioc, vamos a crear un service locator (ViewModelLocator) que utilizará el contenedor creado. Lo instanciaremos en App.xaml y lo utilizaremos para que cada vista pueda acceder a la instancia de su viewmodel correspondiente cada vez que lo necesite.

public class ViewModelLocatorService
{
     // IoC container
     private IContainer _container;

     // Constructor
     public ViewModelLocatorService()
     {
          this._container = new Container();
     }

     // ViewModel principal para la colección de cartas
     public IMainViewModel MainViewModel
     {
          get
          {
               return this._container.Resolve<MainViewModel>();
          }
     }

     public IGroupDetailViewModel GroupDetailViewModel
     {
          get
          {
               return this._container.Resolve<GroupDetailViewModel>();
          }
     }

     public IRecipeDetailViewModel RecipeDetailViewModel
     {
          get
          {
               return this._container.Resolve<RecipeDetailViewModel>();
          }
     }
}

En App.xaml:

<services:ViewModelLocatorService x:Key="ViewModelLocator"/>

Donde services es el namespace donde hemos declarado al ViewModelLocator:

xmlns:services="clr-namespace:RecetarioWP7.Services"

Asignaremos el DataContext de nuestra vista a su correspondiente viewmodel directamente en su XAML utilizando el ViewModelLocator (en MainPage.xaml):

DataContext="{Binding Path=MainViewModel, Source={StaticResource ViewModelLocator}}"

Como ya hemos comentado nuestros viewmodels accederán a los servicios utilizando interfaces:

// Servicios dependientes de la plataforma que necesitamos en este ViewModel
private IStorageService StorageService;
private ILocalDataService LocalDataService;
private INavigationService NavigationService;
private ILiveTileService LiveTileService;

public MainViewModel(ILocalDataService localDataService, IStorageService storageService, INavigationService navigationService,
ILiveTileService liveTileService)
{
     this.LocalDataService = localDataService;
     this.StorageService = storageService;
     this.NavigationService = navigationService;
     this.LiveTileService = liveTileService;
}

Llegamos a este punto tenemos nuestra aplicación ahora si correctamente preparada para comenzar el port de Windows Phone 7 a Windows Phone 8 reaprovechando la mayor cantidad de código posible. Os dejo a continuación el código completo de la aplicación con los cambios realizados:

Como suele siendo habitual cualquier pregunta o sugerencia será bienvenida.

Próximamente…

En la tercera y última entrega aplicaremos las técnicas para reutilizar código ya analizadas gracias a la implementación del patrón MVVM realizado en este artículo.

¿Te apuntas?

Más información

Técnicas para compartir código:

9 pensamientos en “Windows Phone. Técnicas para compartir código. 2º Parte

  1. Muy buenos artículos Javier.

    También considero interesante el tema de la compartición – reutilización de código en el mantenimiento de aplicaciones.

    Por ejemplo, este caso: se tiene una versión para .net 3.5 y .net 4.0. Un csproj compilado en .net 3.5 y otro csproj compilado en .net 4.0.

    El código fuente está en el proyecto de .net 4.0.
    El csproj de .net 3.5 tiene “Links” (enlazadoa de ficheros) a los ficheros de código fuente del csproj de .net 4.0.

    El problema viene en casos que se utilizan cosas específicas de .NET 4.0, como String.IsNullOrWhiteSpace.

    En este caso, ya no compilaría el csproj de .net 3.5.

    protected void BtnSSO_Click(object sender, EventArgs e)
    {
    var tok = new InfoToken();
    tok.Idempresa = Convert.ToInt16(this.txtempresa.Text);
    tok.IdApp = 4;
    tok.Login = (String.IsNullOrWhiteSpace(this.txtUsuario.Text) ? “loginTest” : this.txtUsuario.Text.Trim());
    tok.Url = CmbLstUrl.SelectedValue;


    }

    Estaría bien plantearse las alternativas a este caso.

    Saludos.

  2. Hola Javier, muy buen artículo, aunque al leerlo me ha surgido una duda, ¿hay alguna forma de compartir también los recursos (imágenes, sonidos, etc.) entre proyectos?

    Un saludo.

  3. Otra forma de compartir código es a través de interfaces en las PCL y en cada proyecto de plataforma implementar esta interface:

    En el proyecto PCL:

    public interface IFileStorage
    {
    Task SaveFileAsync(string filename, string contents);
    Task LoadFileAsync(string filename);
    }

    En proyecto WP8:

    public class WP8FileStorage : IFileStorage
    {
    public Task SaveFileAsync(string filename, string contents)
    {
    …………
    }

    public Task LoadFileAsync(string filename)
    {
    ………..
    }
    }

    En proyecto W8:
    public class WindowsAppFileStorage : IFileStorage
    {
    public async Task SaveFileAsync(string filename, string contents)
    {
    …………
    }

    public async Task LoadFileAsync(string filename)
    {
    …………
    }
    }

    En proyecto WPF:

    public class DesktopFileStorage : IFileStorage
    {
    public Task SaveFileAsync(string filename, string contents)
    {
    …….
    }

    public Task LoadFileAsync(string filename)
    {
    …….
    }
    }

    Si bien de esta forma no estamos reutilizando el código de almacenamiento si que estamos facilitando la utilización del almacenamiento en el ViewModel:

    En el proyecto PCL:

    public class ViewModel : NotifyPropertyChangedBase
    {
    IFileStorage _fileStorage;

    public ViewModel(IFileStorage fileStorage)
    {
    _fileStorage = fileStorage;
    ……
    }
    ……
    }

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s