[Windows Phone 8] Compartiendo codigo en Windows Phone 7 y Windows Phone 8

Hola a todos!

Hoy vamos a ver un tema del que se ha hablado mucho en twitter últimamente. Con la aparición de Windows Phone 8, nos encontramos ante la duda de hacer una versión de nuestra aplicación para el nuevo sistema, abandonar Windows Phone 7.X o no desarrollar para 8, pues las aplicaciones 7.X funcionan perfectamente en la nueva versión del sistema.

Creo que ahora mismo, la respuesta es una mezcla entre ambas. Windows Phone 8 incorpora muchas mejoras que podemos aprovechar en nuestra aplicación: Comandos de voz, nuevos mapas, asociación de archivos, Fast App Resume, etc. Es una pena tener todo este potencial a nuestra disposición y no ofrecerlo al usuario. Por otro lado, Hay muchos terminales Windows Phone 7.X en la calle y es una pena no darles soporte. Lo que tenemos que intentar es conseguir que la mayor parte del código entre 7.X y 8.0 sea compartido.

Para ello nos aprovecharemos de un tipo de proyecto conocido como Portable Class Libraries. Estas librerías permiten compatibilidad con Windows Phone 7, 7.1 o 8.0, Windows Store, .NET, Silverlight e incluso XBox 360. Lo que vamos a intentar es trasladar a un proyecto Portable Class Library todo el código posible, dejando en cada proyecto Windows Phone solo el código exclusivo de cada plataforma. Además, pensando en que podamos expandir la aplicación a Windows 8 en un futuro, haremos la Portable Class Library compatible con aplicaciones Windows Store también.

Creando nuestra solución.

Lo primero que vamos a hacer es crear la estructura de proyectos. En este caso tendremos un proyecto Portable Class Library, que podemos encontrar en el nodo “Windows” de la pantalla de creación de nuevo proyecto. Este se llamará Common, pues contendrá el código común entre el resto de clientes. Al crear esta librería, se nos preguntarán los frameworks que deseamos soportar. Tenemos que seleccionar Windows Phone 7.5 o superior y Windows Store Apps. A continuación añadiremos dos proyectos de Windows Phone: Uno con la versión 8.0 y otro con la versión 7.1, no busquéis un proyecto exclusivo de Windows Phone 7.8, no existe.

Una vez que tengamos nuestros proyectos creados, vamos a añadir una referencia en los clientes Windows Phone al proyecto Portable Class Library, (Botón derecho sobre el proyecto, Add Reference: Solution y marcamos el proyecto Common). Con esto tenemos el cableado de nuestros proyectos hecho. Ahora tenemos que ocuparnos de algún otro asunto.

Async / Await en Windows Phone.

Windows Phone 8 soporta bastante bien el desarrollo asíncrono mediante el uso de async / await, pero Windows Phone 7.1 no lo soporta en absoluto. Esto puede llevarnos a tener que hacer auténticas peripecias para poder usar el mismo código en ambas plataformas. Además, como en nuestro proyecto portable hemos indicado que se usa Windows Phone 7.5 o superior, también podemos tener problemas. Para ayudarnos con estos y otros casos, Microsoft ha liberado en NuGet un paquete llamado BCL Portability Pack. Para encontrarlo, tendremos que habilitar en el administrador de NuGet que se incluyan los paquetes no estables, pues todavía se encuentra en estado Release Candidate.

image

De esta forma, podremos usar async / await o CallerMemberName en proyectos Windows Phone 7.5 sin ningún problema. También tendremos que añadir el segundo paquete que vemos, Async for .NET Framework 4, para que funcionen las llamadas usando async / await. Tendremos que añadir estos dos paquetes, tanto en el proyecto de Windows Phone 7.5 como en la Portable Class Library.

Empezando a cablear.

Vamos a comenzar a ensuciarnos las manos. Dicen que siempre hay que tener un objetivo claro, así que el nuestro va a ser hacer una aplicación que centre un mapa en nuestra posición. Para ello, lo primero que vamos a hacer es crear la infraestructura básica de MVVM en nuestra librería portable: VMBase, DelegateCommand y VMLocator.

¿Nunca has hecho una VMBase o un DelegateCommand y mucho menos un VMLocator? He creado unos pequeños Snippets de Visual Studio: VMBaseClass, DCommandClass y VMLAuto que se encargarán de crear las clases por ti, solo tienes que añadirlos a Visual Studio, crear una nueva clase y escribir VMBaseClass para la clase VMBase, darle dos veces a TAB para que se cree todo el código necesario y a continuación añadir los namespaces que falten, lo mismo para el Delegate Command o para el VMLocator. Puedes descargarlos aquí. Para el VMLocator además necesitarás descargarte la última versión de Autofac para librerías portables de aquí.

NOTA: La última versión 3.0 de Autofac es compatible con Portable class libraries, pero en las pruebas he visto algunos bugs al usarla en Windows Phone 7, la versión que indico (2.6.3 beta) funciona perfectamente. Podéis ver una conversación con Alex Meyer de Autofac aquí sobre este problema, que parece que no será corregido.

Al final tienes el ejemplo completo, que ya incorpora todo esto. Cuando hayamos terminado esta parte, debemos tener en nuestro proyecto Portable Class Library una carpeta ViewModels, con una subcarpeta Base que contenga tres clases: VMBase, VMLocator y DelegateCommand. Ahora vamos a modificar la clase VMLocator, por defecto se crea registrando tipos en el constructor, pero en nuestro caso queremos y necesitamos crear tipos  desde los proyectos Windows Phone, para añadir las dependencias de cada plataforma, quedando así:

public class VMLocator
{
    IContainer container;
    ContainerBuilder builder;

    public VMLocator()
    {
        this.builder = new ContainerBuilder();
    }

    public void RegisterType(Type Tto, Type TFrom)
    {
        this.builder.RegisterType(Tto).As(TFrom);
    }

    public void BuildContainer()
    {
        this.builder.RegisterType<VMMainPage>();

        this.container = this.builder.Build();
    }
}

Más adelante, llamaremos a los métodos RegisterType y BuildContainer desde nuestra aplicación Windows Phone para construir las dependencias necesarias. Antes de empezar a trabajar en nuestra ViewModel, vamos a crear en la librería portable una carpeta llamada Entities en la que crearemos una clase llamada Coordinates:

public class Coordinates
{
    public double Latitude { get; set; }

    public double Longitude { get; set; }

    public double? Altitude { get; set; }
}

¿Para que sirve esta clase Coordinates? Bien, resulta que la librería portable permite acceder al framework de .NET que mejor se ajuste a todas las plataformas que hemos escogido al crearla, pero solo eso, .NET puro. Lamentablemente los tipos de datos GeoCoordinate, Geocoordinate y demás, son tipos especiales del framework de Windows Phone, por lo que no están accesibles en la librería. Lo que hemos hecho es crear nuestro propio objeto Coordinate, para recibir los datos.

A continuación crearemos una carpeta Services y dentro un interfaz llamado IGpsService con un método llamado GetCurrentPosition cuya implementación realizaremos más adelante:

public interface IGpsService
{
    Task<Coordinates> GetCurrentPosition();
}

Ahora podemos añadir este servicio a nuestra ViewModel y crear la lógica que, al pulsar un botón el usuario, obtenga las coordenadas actuales y las exponga en una propiedad GpsCoordinates:

public class VMMainPage : VMBase
{
    private IGpsService gpsService;

    private DelegateCommand getCoordinatesCommand;

    private Coordinates gpsCoordinates;

    public VMMainPage(IGpsService gpsService)
    {
        this.gpsService = gpsService;

        this.getCoordinatesCommand = new DelegateCommand(ExecuteGetCoordinatesCommand);
    }

    public ICommand GetCoordinatesCommand
    {
        get { return this.getCoordinatesCommand; }
    }

    public Coordinates GpsCoordinates
    {
        get { return this.gpsCoordinates; }
        set
        {
            this.gpsCoordinates = value;
            RaisePropertyChanged();
        }
    }

    private async void ExecuteGetCoordinatesCommand()
    {
        GpsCoordinates = await this.gpsService.GetCurrentPosition();
    }
}

De esta forma hemos completado el código de nuestra ViewModel. Pero, ¿Donde implementamos el código de la interfaz IGpsService? Pues lo haremos en cada una de las aplicaciones cliente. Esto tiene que ser así por qué, mientras Windows Phone 7 usa la clase GeoCoordinateWatcher para acceder al GPS, en Windows Phone 8 usamos la nueva clase GeoLocator de WinPRT. Lo que vamos a hacer es, en el proyecto Windows Phone 7, crear una carpeta Services y una clase GpsService que implemente la interfaz IGpsService:

public class GpsService : IGpsService
{
    public Task<Coordinates> GetCurrentPosition()
    {
        TaskCompletionSource<Coordinates> completitionTask = new TaskCompletionSource<Coordinates>();

        GeoCoordinateWatcher watcher = new GeoCoordinateWatcher(GeoPositionAccuracy.High);
        watcher.PositionChanged += (o, e) =>
            {
                watcher.Stop();
                if (e.Position != null)
                    completitionTask.SetResult(new Coordinates()
                    {
                        Latitude = e.Position.Location.Latitude,
                        Longitude = e.Position.Location.Longitude,
                        Altitude = e.Position.Location.Altitude
                    });

            };
        watcher.Start();

        return completitionTask.Task;
    }
}

Debemos recordar que en Windows Phone 7.X no existía async / await, por lo que tendremos que usar la clase TaskCompletitionSource<T> para envolver nuestras llamadas y cumplir la interfaz IGpsService. En Windows Phone 8, que ya soporta async / await, la implementación será más sencilla:

public class GpsService : IGpsService
{
    public async Task<Coordinates> GetCurrentPosition()
    {
        Coordinates result = new Coordinates();
        Geolocator locator = new Geolocator();
        locator.DesiredAccuracy = PositionAccuracy.High;
        locator.MovementThreshold = 10;

        var currentPosition = await locator.GetGeopositionAsync();
        if (currentPosition != null)
        {
            result = new Coordinates()
            {
                Latitude = currentPosition.Coordinate.Latitude,
                Longitude = currentPosition.Coordinate.Longitude,
                Altitude = currentPosition.Coordinate.Altitude
            };
        }

        return result;
    }
}

Con esto, hemos implementado de forma nativa en cada plataforma el acceso al GPS, pero ambas cumpliendo la interfaz que hemos definido en nuestra librería portable. Esto nos permitirá, usando Autofac, inyectar cada implementación cuando sea preciso. Para ello, en primer lugar, vamos a añadir nuestro VMLocator como un recurso en el archivo app.xaml de cada aplicación:

<Application.Resources>
    <locator:VMLocator x:Key="Locator"></locator:VMLocator>
</Application.Resources>

En segundo lugar, vamos a modificar el código de la clase App, app.xaml.cs. Exactamente la función InitializePhoneApplication, para recuperar el locator que hemos definido en xaml, registrar la implementación del GPS y construir el contenedor de dependencias. Todo esto debemos realizarlo antes de navegar a la primera página:

private void InitializePhoneApplication()
{
    if (phoneApplicationInitialized)
        return;

    VMLocator locator = (VMLocator)App.Current.Resources["Locator"];
    locator.RegisterType(typeof(GpsService), typeof(IGpsService));
    locator.BuildContainer();

    // Create the frame but don't set it as RootVisual yet; this allows the splash
    // screen to remain active until the application is ready to render.
    RootFrame = new PhoneApplicationFrame();
    RootFrame.Navigated += CompleteInitializePhoneApplication;

    // Handle navigation failures
    RootFrame.NavigationFailed += RootFrame_NavigationFailed;

    // Handle reset requests for clearing the backstack
    RootFrame.Navigated += CheckForResetNavigation;

    // Ensure we don't initialize again
    phoneApplicationInitialized = true;
}

Y ya solo nos queda construir la interfaz de usuario, muy simple en este ejemplo. Colocaremos un botón que se encargue de invocar a nuestro comando GetCoordinatesCommand y unos textblocks que muestren las coordenadas obtenidas:

<StackPanel x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
    <Button Content="Get coordinates"
            Command="{Binding GetCoordinatesCommand}"></Button>
    <TextBlock Text="Latitude:"></TextBlock>
    <TextBlock Text="{Binding GpsCoordinates.Latitude}"></TextBlock>
    <TextBlock Text="Longitude:"></TextBlock>
    <TextBlock Text="{Binding GpsCoordinates.Longitude}"></TextBlock>
    <TextBlock Text="Altitude:"></TextBlock>
    <TextBlock Text="{Binding GpsCOordinates.Altitude}"></TextBlock>
</StackPanel>

Si todo marcha como es debido, ya deberíamos poder ejecutar ambas versiones y verlas en funcionamiento como se muestra a continuación.

image

Conclusión

¿Es posible compartir código entre Windows Phone 7 y 8? Si, pero no todo. Con una buena implementación de MVVM, usando un contenedor de dependencias y pensando un poco en lo que deseamos hacer, podemos compartir la mayor parte de la lógica de la aplicación. También podríamos compartir parte del XAML, quizás, pero puede que el esfuerzo fuese mayor que el beneficio. En definitiva, hemos visto como poder compartir toda la infraestructura de MVVM, y nuestras ViewModels, así como los contratos de los servicios que implementará cada plataforma. Es muy importante que los servicios sean lo más “tontos” posible. Que simplemente devuelvan datos y cualquier tipo de lógica se lleve a cabo en la ViewModel, para evitar duplicar más código del necesario.

Como siempre, a continuación tenéis el ejemplo que hemos desgranado en el artículo para que lo descarguéis. Cualquier sugerencia, comentario, crítica será bien recibida.

Un saludo a todos y Happy Coding!!

Published 7/2/2013 6:49 por Josué Yeray Julián Ferreiro
Comparte este post:

Comentarios

# re: [Windows Phone 8] Compartiendo codigo en Windows Phone 7 y Windows Phone 8

Saturday, February 9, 2013 11:16 PM por Joaquín Sosa Martín

grande!!!

# re: [Windows Phone 8] Compartiendo codigo en Windows Phone 7 y Windows Phone 8

Saturday, March 16, 2013 9:00 PM por Juan Francisco

Muy bueno el post, pero tengo una duda, siguiendo la estructura que tiene este proyecto, ¿en qué parte se implementaría la navegación entre páginas?

Un saludo.

# Empieza el Megathon 2013, enlaces interesantes

Friday, April 12, 2013 1:21 PM por Josue Yeray

Hola a todos! Hoy 12 de Abril comienza el Megathon Windows 2013, en esta edición se pueden presentar

# Windows Phone Tip: Problemas al publicar para Windows Phone 7 y para Windows Phone 8

Tuesday, May 14, 2013 9:21 AM por Josue Yeray

Hola a todos! Hoy quiero hablar un poco de la publicación de aplicaciones para Windows Phone 7

# Tips & Tricks de desarrollo para Windows Phone

Wednesday, March 19, 2014 12:28 PM por MSDN España

Te presentamos nuestra colección de Tips & Tricks de desarrollo de apps para Windows Phone