[Windows10] Novedades para desarrolladores 1 de N

Hola a todos!

El pasado 23 de Marzo, Microsoft liberó una primera versión de sus herramientas para desarrolladores de Windows 10. Nos encontramos ante una versión muy temprana de las mismas, hay muchas cosas que hemos podido ver en vídeo que no están disponibles en la versión pública. Así mismo, tienes que estar preparado para pelear con ellas, desde la instalación hasta en el momento de usarlas. Al ser una versión alpha, existen muchos fallos y esquinas que pulir todavía.

Lista de artículos:

Como conseguir instalar las herramientas

En primer lugar, necesitas estar registrado como Windows Insider para poder acceder a todo el software relacionado con Windows 10.

Para poder instalarlas necesitarás unos requisitos mínimos de software:

  • Windows 10 Build 10041
  • Visual Studio 2015 CTP6

Tras tener todo instalado, puedes descargar un ISO de las herramientas o un instalador web, desde la página de herramientas técnicas.

Dada la naturaleza preview, tanto de Visual Studio 2015, como Windows 10 y las herramientas de desarrollo, lo más sensato es que no lo instales todo en tu PC principal. Puedes crear una máquina virtual con Hyper-V, VMWare o VirtualBox sin problema. Con unos 40Gb de espacio reservado podrás instalar Windows 10, Visual Studio 2015 y las developer tools par trabajar con ellas.

Problemas conocidos

Después de trabajar unos días con las herramientas de desarrollo, ya se conocen algunos problemas y sus posibles “workarounds”, cuando existe alguno:

  • La versión actual de Windows 10 mobile que liberó microsoft en febrero no es compatible con estas herramientas, por lo que es imposible desplegar a un dispositivo que tenga esa build pública.
  • Conflicto con el emulador de Android de Cordova tools: El emulador de Android usa la versión 8.0.12484 de XDE para ejecutarse, mientras que las dev tools de Windows 10 instalan la versión 8.0.12513, que no soporta el plugin de Android. Si tienes instalados ambos emuladores, al arrancar el de Windows obtendrás el error: “Failed to load SKU “WP””. Se soluciona desinstalando el emulador de Android de Visual Studio.
  • Por defecto el emulador de Windows 10 mobile usa 2 procesadores, si estás en una máquina virtual, asegurate de asignarle al menos 4, de lo contrario es posible que el emulador no cargue.

Seguro que mientras vayas avanzando en el uso de las herramientas, encuentras nuevos fallos, normal en una versión tan temprana, no olvides usar la herramienta “Windows Feedback” para informarlos a Microsoft. Tras instalar las nuevas herramientas tendrás una opción llamada “Developer Platform”:

001

Un poco de terminología primero…

Antes de continuar  y empezar a ver que podemos hacer con las nuevas herramientas, es bueno que conozcamos algunos nombres nuevos que se presentan con las herramientas:

  • Windows apps. Microsoft quiere que abandonemos “Windows Phone app” o “Windows Store app” para referirnos a las aplicaciones y las empecemos a llamar simplemente “Windows apps”. Vamos a seguirle el juego por ahora…
  • UAP: Windows universal app platform. La nueva plataforma de apps que viene con Windows 10 y que forma parte de Windows OneCore (El core unificado de Windows presente en todas las ediciones de Windows 10).
    • universal app platform se escribe en minúsculas porque no es una marca, es el nombre de la tecnología.
    • UAP es el acrónimo de la plataforma de aplicaciones. No vas a desarrollar un UAP. Vas a desarrollar una app Windows, que puede ejecutarse sobre el UAP.
  • Familias de dispositivos. Los distintos tipos de dispositivos sobre los que UAP funciona: Smartphones, tablets, desktop, IoT…

Con estos puntos explicados… vamos a jugar con las herramientas!

Nuestra primera Windows app

Ya es hora de empezar a ver un poco de código de verdad. Tras arrancar Visual Studio 2015 con las herramientas instaladas, en la ventana de nuevo proyecto, encontraremos un nodo llamado Windows 10, que contiene los nuevos proyectos:

image

No encontramos muchas sorpresas aquí:

  • Blank application (UAP): Una aplicación vacia que hace uso de la universal app platform.
  • Class library (UAP): Un proyecto de librería de clases.
  • Windows Runtime Component (UAP): un proyecto que nos permite crear un componente WinRT.
  • Unit Test App (UAP): Una aplicación para ejecutar tests sobre nuestra aplicación.

Donde veremos cambios es al crear un nuevo proyecto del tipo Blank application. Una vez creado veremos que ya no tenemos la estructura de proyectos que se creaban en  Windows 8.1, con un proyecto Windows Store y otro Windows Phone, más un proyecto Shared:

image

En Windows 10, tendremos un ejecutable para todos los dispositivos que soportemos, por lo que solo tendremos un proyecto y no necesitaremos usar el proyecto shared al estilo de Windows 8.1.

Al tener un solo proyecto para las distintas familias de dispositivos, ¿Como podemos usar features de una familia en concreto? Microsoft a resuelto esto mediante la creación de SDKs de extensión por familia de dispositivo. Para ver las extensiones disponibles, haz click derecho sobre el nodo References de tu proyecto y dentro de la pantalla “References Manager” ve al nodo “Universal app platform > Entensions”:

image

Pero, ¿Qué pasa si ejecutamos la app en un dispositivo que no soporta una extensión en concreto? Nuestra aplicación fallaría. Para evitarlo, siempre que usemos una API parte de un SDK de extensión, debemos comprobar primero si dicha API está disponible.

Un ejemplo muy sencillo y que tendrás que usar en tus apps, si o si, si quieres que se ejecuten correctamente en un smartphone: La gestión del botón atrás.

Las APIs necesarias, se encuentran en el SDK de extensión “Windows Mobile Extension SDK” que debemos referenciar en nuestro proyecto. A continuación, en el archivo App.xaml.cs debemos añadir el siguiente código:

using Windows.Phone.UI.Input;

public App()
{
    this.InitializeComponent();
    this.Suspending += OnSuspending;
    HardwareButtons.BackPressed += HardwareButtons_BackPressed;
}
private void HardwareButtons_BackPressed(object sender, BackPressedEventArgs e)
{
    //TODO: Back button management
}

En el emulador de Windows Phone, funcionará perfectamente. Pero si intentamos ejecutar la app en Windows 10, fallará porque no encontrará el API. Para evitar esto, tenemos que comprobar primero si el API está disponible o no:

using Windows.Phone.UI.Input;
using Windows.Foundation.Metadata;

public App()
{
    this.InitializeComponent();
    this.Suspending += OnSuspending;
    if (ApiInformation.IsTypePresent("Windows.Phone.UI.Input.HardwareButtons"))
    {
        HardwareButtons.BackPressed += HardwareButtons_BackPressed;
    }
}
private void HardwareButtons_BackPressed(object sender, BackPressedEventArgs e)
{
    //TODO: Back button management
}

Se que muchos os estaréis llevando las manos a la cabeza, yo incluido, por tener que usar el full qualified name… en un string. No hay nada más proclibe a errores que esto. Esperemos que reciban todo nuestro feedback y lo cambien por algún tipo de enumerado o constante que nos evite plagar nuestro código de “magic strings”.

Fundido a negro…

Y con esto llegamos al final de este primer artículo sobre Windows 10 para desarrolladores. Pronto una nueva entrega. Tenemos mucho de lo que hablar: AdaptativeVisualStates, Device families, cambios en XAML…

Puedes seguir los ejemplos que vaya publicando en este repo de GitHub exclusivo para Windows 10.

Un saludo y Happy Coding!

 

[XAMARIN.FORMS] Preview de Xamarin.Forms para Windows XAML

sticker-with-textPicture1dotnetconf-logo-300x290

El 19 de Marzo pasado, James Montemagno junto con Miguel de Icaza y Scott Hanselman presentaron durante la DotNetConf 2015 la versión preview de Xamarin.Forms para Windows Phone 8.1 y Windows Store. Puedes ver el video de la presentación en Channel9.

Este es un anuncio largamente esperado. Hasta ahora, Xamarin.Forms solo soportaba Windows Phone 8.0, lo que en ciertas ocasiones evitaba poder usarlo.

Aunque todavía está en modo preview, ya nos permite jugar con lo que vendrá en un futuro y empezar a añadir aplicaciones universales windows a nuestros proyectos Xamarin.Forms.

Probando la nueva preview

Para comenzar, necesitaremos crear un nuevo proyecto de Xamarin.Forms desde Visual Studio, en su versión Portable:

image

A continuación, aunque no es totalmente necesario, es recomendable actualizar las librerías de Xamarin.Forms a la última versión disponible. Para ello, solo tendremos que hacer click derecho sobre la solución en el explorador de solución y escoger la opción “Manage NuGet Packages for solution…”. Tenemos que cambiar el canal de “Stable only” a “Include prerelease”. En Updates veremos nuevas versiones de Xamarin.Forms y Xamarin Support Library y un botón “Update All”:

image

Tras tener la última versión de nuestras librerías para Xamarin.Forms, es hora de incluir el soporte a Windows Phone 8.1 y Windows Store.

Para ello, primero necesitamos añadir un nuevo proyecto universal Windows a nuestra solución Xamarin.Forms. Una vez que lo tengamos, podemos eliminar el proyecto de Windows Phone 8 que Xamarin.Forms incluye por defecto, ya no lo vamos a necesitar más.

Tras esto, tenemos que hacer click derecho sobre la solución y de nuevo lanzar el gestor de paquetes de NuGet. Buscaremos un paquete llamado Xamarin.Forms.Windows. Como estamos trabajando con una preview, tendremos que indicarle a NuGet que busque en software prerelease también:

XFW

Una vez seleccionado, NuGet nos preguntará en que proyectos queremos añadirlo. Por defecto nos mostrará los proyectos universales seleccionados:

xfw2

Tras aceptar, ya tendremos las librerías necesarias para que nuestro proyecto universal soporte Xamarin.Forms. Pero todavía nos quedan dos pasos, antes de empezar a escribir código.

Por defecto, la librería portable que crea Xamarin.Forms, no soporta Windows Phone 8.1 (Windows XAML):

image

Tenemos que añadir el soporte para Windows 8.1 y Windows Phone 8.1 y eliminar el soporte a Windows Phone Silverlight 8:

image

Al hacer esto, nos avisará de que al presionar OK se recargará el proyecto. Tras la recarga, ya podremos añadir una referencia al proyecto Portable en nuestros proyectos Windows y Windows Phone 8.1 (universal). En este punto, si compilamos, todo debería compilar sin problemas.

Para dejar todo funcionando, necesitamos indicarle a nuestra app universal que debe inicializar Xamarin.Forms, al igual que lo hacemos en el resto de proyectos, en la página principal.

Iniciando Xamarin.Forms en un proyecto Windows Universal

En el archivo App.xaml.cs, en el método OnLaunched, vamos a iniciar el sistema de Xamarin.Forms:

protected override void OnLaunched(LaunchActivatedEventArgs e)
{
    Frame rootFrame = Window.Current.Content as Frame;
    if (rootFrame == null)
    {
        rootFrame = new Frame();
        rootFrame.CacheSize = 1;

        //Initialize Xamarin.Forms
        global::Xamarin.Forms.Forms.Init(e);

        Window.Current.Content = rootFrame;
    }

En el archivo MainPage.xaml del proyecto Windows, tenemos que cambiar el tipo de la página, para usar el tipo WindowsBasePage definido en los ensamblados de Xamarin.Forms:

<forms:WindowsPage
    x:Class=“XamarinForWinStore.WinUniversal.MainPage”
    xmlns:forms=“using:Xamarin.Forms.Platform.WinRT”

En el code behind de esta misma página, tenemos que quitar la herencia de la clase Page y añadir una llamada a LoadApplication en el constructor:

public sealed partial class MainPage
{
    public MainPage()
    {
        this.InitializeComponent();

        this.NavigationCacheMode = NavigationCacheMode.Required;

        LoadApplication(new XamarinForWinStore.App());
    }
}

Repetimos el mismo proceso en el proyecto Windows Phone, solo que esta vez usaremos WindowsPhonePage en lugar de WindowsPage.

Y ya estamos listos!

Si marcamos el proyecto Windows Store como proyecto de inicio y ejecutamos, veremos que nos saludan Xamarin.Forms:

image

Todavía falta camino…

Esta preview es un gran avance y nos permite empezar a jugar con Xamarin.Forms en más plataformas, pero está lejos de estar completa. Algunas de las cosas que faltan:

  • Mapas: No están soportados en esta versión.
  • GridView: El control estrella de Windows Store, no está soportado.
  • Controles: No todos los renderizadores de controles están completos en esta versión.
  • Recursos: WinRT no permite cargar recursos desde un ensamblado diferente a no ser que lo marquemos correctamente con ReflectionPermission. Esto no es compatible con PCL.

Como decía, todavía falta camino por recorrer, pero es un gran paso en la dirección correcta. ¿Qué nos deparará el //Build 2015? Estaré allí en persona para contaros todas las novedades!

Como siempre, tenéis disponible el código en GitHub para que podáis empezar a trastear con él.

Un saludo y Happy Coding!

 

[EVENTO] WPSUG: Porqué no has terminado, cuando has terminado tu app

Hola a todos!

Logo-256

WPSUG Vuelve a la carga con un nuevo Hangout en directo. En esta ocasión, además de los sospechosos habituales, Rafa Serna, Javier Suarez, Santiago Porras y un servidor, contaremos con la presencia de Vanessa Estorach de e-growing, experta en ASO y “Mobile Sensei”

Este hangout va a estar centrado en el trabajo que viene cuando hemos terminado de desarrollar nuestra app. Promoción en las store, optimización de los resultados… como hacerla destacar y obtener de ella el mayor beneficio posible.

EL evento tendrá lugar el Jueves 26 de Marzo, a las 19:00 hora peninsular de España (GMT+1):

  • 13:00 en Colombia
  • 12:00 en México Centro
  • 13:30 en Venezuela
  • 15:00 en Chile continental

La url para ver el vídeo es esta:

https://www.youtube.com/watch?v=dspt9kyVLO8

Para los más organizados, os he creado una cita de calendario que podéis descargar aquí para que tengáis el evento bien localizado (y con recordatorio!!)

Un saludo y Happy coding!

[MATERIALES] .NET Spain Conference 2015: Introducción a Xamarin.Forms

Hola a todos!

El pasado 28 de Febrero, junto a Rafa Serna, tuve la suerte de poder dar una charla sobre Xamarin.Forms 1.3 en la .NET Spain Conference 2015.

WP_20150228_11_50_06_Pro

Aquí tenéis los materiales! Las Slides sobre las novedades de Xamarin Forms 1.3:

Y en GitHub tenéis el código de las demos sobre converters, animaciones, behaviors y triggers:

 ver-github1

Un saludo y Happy Coding!

[Xamarin.Forms] Tip: Añadir intellisense al editor XAML

Buenas!

Una de las cosas que Xamarin.Forms tiene pendiente en Visual Studio, quitando el tener un diseñador, es que al menos tengamos intellisense en el editor de XAML, de forma que sea má sencillo crear el contenido de nuestra pantalla.

Hasta ahora teníamos dos soluciones, lejos de ser ideales:

  • Abrir nuestro proyecto en Xamarin Studio, donde si tenemos intellisense, y usar Xamarin Studio para editar nuestro XAML.
  • Escribir la interfaz de usuario en C#, donde si tenemos intellisense en Visual Studio.

Pero por fin alguien se ha animado a ayudarnos un poco, creando un addin para Visual Studio (2013 y 2015) que nos permite tener intellisense en nuestro XAML. Tenemos que agradecer a Daniel Cazzulino por esta gran extensión.

Para hacerte con ella, solo tendrás que ir a las opciones de Visual Studio y agregar una nueva galería en el nodo “Extensions & Updates” de “Environment”:

image

Puedes poner lo que quieras como nombre y en la url debes introducir esta:

Ahora solo tienes que hacer click en “Apply” y “OK” y ya tendrás tu nueva galería añadida a Visual Studio.

Vuelve al menú “options” y abre la pantalla de “Extensions & Updates. En el nodo “ONLINE” encontrarás tu nueva galería, con el plugin de Intellisense para Xamarin.Forms:

image

Presiona sobre el botón install y, tras reiniciar Visual Studio, tendrás intellisense en tu XAML de Xamarin Forms.

Espero que te sea útil, desde luego es algo que llevaba mucho tiempo esperando personalmente.

Un saludo y happy coding!

 

[MATERIALES] Evento Xamarin Madrid, febrero de 2015

Hola a todos!

Ayer, de la mano de Microsoft y Bravent, tuve el placer de poder participar junto a Javier Suarez Ruiz en un evento sobre desarrollo multiplataforma con Xamarin.

Muy buena acogida, con la sala casi llena y unas 50 personas.

En mi caso me encargue de dos charlas: Introducción a Xamarin y Xamarin y Azure.

Para los que no pudisteis asistir, he subido las slides a SlideShare:

Y las demos de Azure a GitHub:

Azure Mobile Services y Xamarin

Azure Notifications Hub y Xamarin

Incluso tuvimos tiempo de hacernos un “Selfie”:

Además, si estáis en Sevilla o Barcelona, todavía tenéis una oportunidad de asistir en directo a los eventos que realizaremos allí el 9 y 16 de Febrero respectivamente. Más información aquí.

Un saludo y Happy Coding!

[XAMARIN] Animaciones en nuestras aplicaciones Xamarin y Xamarin.Forms

Hola a todos!

Ya he hablado anteriormente en otros artículos de este blog, lo sencillo que es crear animaciones en XAML para Windows y Windows Phone. Hoy quiero hablar de algo parecido, animaciones también, pero en nuestras aplicaciones Android e iOS, usando Xamarin.

Encontraremos dos aproximaciones distintas: Xamarin y Xamarin.Forms.

En Xamarin.Forms, al tener una vista unificada, dispondremos de mecanismos para crear animaciones en la vista directamente.

En Xamarin tenemos que partir de la base de que, al crear la interface de forma nativa, también tendremos que hacer lo mismo con las animaciones, por lo que cada plataforma tendrá su forma de realizarlas.

Vamos a verlo a continuación.

Xamarin.Forms

En XamarinForms, los controles que usamos para crear la interface de usuario heredan de la clase View.

Existen unos métodos extensores de esta clase, que nos permiten, mediante código, crear animaciones para nuestros controles. Por ejemplo podemos crear una página sencilla con una Label:

public MainPage()
{
    sampleLabel = new Label
    {
        Text = “Hello Xamarin.Forms animations!”,
        TextColor = Color.Blue,  
        XAlign = TextAlignment.Center,
        Opacity = 0
    };

    Content = new StackLayout
    {
        VerticalOptions = LayoutOptions.Center,
        Children =
        {
            sampleLabel
        }
    };
}

Como podemos ver, hemos establecido la opacidad de la Label a 0, por lo que no será inmediatamente visible.

A continuación podemos sobre escribir el método OnAppearing, que se lanza justo antes de mostrarse la página, para animar esta Label y crear un efecto de FadeIn:

protected override void OnAppearing()
{
    base.OnAppearing();

    sampleLabel.FadeTo(1, 750, Easing.Linear);
}

El método extensor FadeTo puede recibir tres parámetros:

  • Valor final
  • Tiempo de la animación
  • Función de easing. Este parámetro es opcionmal.

Existen otros métodos como ScaleTo, RotateTo, RotateXTo, RotateYTo, LayoutTo, RelRotateTo, TranslateTo, podemos verlos todos aquí.

Podemos crear animaciones complejas, combinando varios efectos, por ejemplo vamos a probar con FadeTo + ScaleTo para crear una animación más completa:

protected override async void OnAppearing()
{
    base.OnAppearing();

    sampleLabel.FadeTo(1, 750, Easing.Linear);

    await sampleLabel.ScaleTo(2, 1500, Easing.CubicInOut);
    await sampleLabel.ScaleTo(1, 500, Easing.Linear);
}

Lo que hemos hecho aquí, es crear una animación FadeTo lineal que tarda 750 milisegundos en completarse. Como no esperamos a que termine, al mismo tiempo se ejecuta una animación ScaleTo, que duplica el tamaño de la Label en 1 segundo, con  una función de easing cúbica. Cuando termina el escalado, se ejecuta otro ScaleTo en 500 milisegundos, que establece de nuevo el tamaño de la Label a su escala original.

Esta forma de crear animaciones nos da mucha flexibilidad a la hora de añadir ese “extra” a nuestras interfaces de usuario con Xamarin Forms.

Además, esto mismo que hemos usado con una Label, se puede llevar a cabo con cualquier otro control, usando exactamente el mismo código fuente.

Xamarin y Windows/Windows Phone

Vamos a ver un repaso rápido de como llevar a cabo esa misma animación, que vimos en Xamarin.Forms, en una aplicación universal para Windows y Windows Phone.

Empezamos por definir el XAML de nuestro TextBlock:

<Grid>
    <TextBlock x:Name=“TextHello”
                Text=”{Binding Hello}
                VerticalAlignment=“Center”
                HorizontalAlignment=“Center”
                Foreground=“Blue”
                Opacity=“0”
                RenderTransformOrigin=“.5,.5”>
        <TextBlock.RenderTransform>
            <CompositeTransform ScaleX=“1” ScaleY=“1”/>
        </TextBlock.RenderTransform>
    </TextBlock>
</Grid>

Además de definir las propiedades básicas del TextBlock, añadimos un CompositeTransform, para poder animar el escalado del elemento.

Ahora ya podemos crear la animación, usando un Storyboard:

<views:MvxWindowsPage.Resources>
    <Storyboard x:Key=“TextBlockAnimation” Duration=“0:0:1.5”>
        <DoubleAnimationUsingKeyFrames Storyboard.TargetName=“TextHello” Storyboard.TargetProperty=“Opacity”>
            <LinearDoubleKeyFrame KeyTime=“0:0:0.750” Value=“1”/>
        </DoubleAnimationUsingKeyFrames>
        <DoubleAnimationUsingKeyFrames Storyboard.TargetName=“TextHello” Storyboard.TargetProperty=“(UIElement.RenderTransform).(CompositeTransform.ScaleX)”>
            <LinearDoubleKeyFrame KeyTime=“0:0:1.0” Value=“2”/>
            <LinearDoubleKeyFrame KeyTime=“0:0:1.5” Value=“1”/>
        </DoubleAnimationUsingKeyFrames>
        <DoubleAnimationUsingKeyFrames Storyboard.TargetName=“TextHello” Storyboard.TargetProperty=“(UIElement.RenderTransform).(CompositeTransform.ScaleY)”>
            <LinearDoubleKeyFrame KeyTime=“0:0:1.0” Value=“2”/>
            <LinearDoubleKeyFrame KeyTime=“0:0:1.5” Value=“1”/>
        </DoubleAnimationUsingKeyFrames>
    </Storyboard>
</views:MvxWindowsPage.Resources>

Y ya solo nos queda lanzar la animación cuando se muestre la página, usando un Behavior o directamente desde código:

protected override void OnNavigatedTo(Windows.UI.Xaml.Navigation.NavigationEventArgs e)
{
    base.OnNavigatedTo(e);

    var animation = (Storyboard)this.Resources[“TextBlockAnimation”];
    animation.Begin();
}

Y ya hemos terminado, nada nuevo hasta ahora. Hemos creado una animación simple, como la que hicimos en Xamarin.Forms, pero esta vez usando XAML nativo para Windows y Windows Phone.

Xamarin y Android

Ahora vamos con Android y AXML, ¿Como creamos la misma animación? En primer lugar, creemos el layout de nuestra página inicial en AXML:

<?xml version=”1.0encoding=”utf-8“?>
<LinearLayout xmlns:android=”http://schemas.android.com/apk/res/android
    xmlns:local=”http://schemas.android.com/apk/res-auto
    android:orientation=”vertical
    android:layout_width=”fill_parent
    android:layout_height=”fill_parent“>
  <TextView
        android:id=”@+id/TextHello
        android:layout_width=”fill_parent
        android:layout_height=”wrap_content
        android:textSize=”40dp
        android:textColor=”#00F
        local:MvxBind=”Text Hello” />
</LinearLayout>

A continuación, necesitamos definir un set de animación. Para ello, crearemos un nuevo archivo AXML en la carpeta Drawable, que llamaremos TextAnimation:

<?xml version=”1.0encoding=”utf-8“?>
<set xmlns:android=”http://schemas.android.com/apk/res/android
      android:shareInterpolator=”false“>
  <alpha android:interpolator=”@android:anim/linear_interpolator
         android:toAlpha=”1
         android:startOffset=”0
         android:duration=”750“/>
  <scale android:interpolator=”@android:anim/linear_interpolator
         android:scaleGravity=”center_vertical
         android:toXScale=”2
         android:toYScale=”2
         android:pivotX=”50%
         android:pivotY=”50%
         android:startOffset=”0
         android:duration=”1000“/>
  <scale android:interpolator=”@android:anim/linear_interpolator
         android:scaleGravity=”center_vertical
         android:toXScale=”1
         android:toYScale=”1
         android:pivotX=”50%
         android:pivotY=”50%
         android:startOffset=”1000
         android:duration=”500“/>
</set>

En Android, si queremos ejecutar varias animaciones en un mismo objeto, podemos crear un Set. Se comporta de forma parecida al Storyboard de XAML. Para crear nuestra animación completa, primero realizamos una animación alpha y a continuación dos scale, el segundo de ellos con un startOffset de 1000 milisegundos (1 segundo).

Para iniciar la animación, vamos a la clase FirstView.cs, al método OnCreate:

protected override void OnCreate(Bundle bundle)
{
    base.OnCreate(bundle);
    SetContentView(Resource.Layout.FirstView);

    Animation textAnimation = AnimationUtils.LoadAnimation(Application.ApplicationContext,
                                                            Resource.Drawable.TextAnimation);
    TextView textHello = FindViewById<TextView>(Resource.Id.TextHello);
    textHello.StartAnimation(textAnimation);
}

Obtenemos la animación con el método LoadAnimation de la clase AnimationUtils, obtenemos el TextView al que se la queremos aplicar, buscándolo por ID y finalmente llamamos al método StartAnimation del elemento a animar. Voila! Nuestra animación está lista!

Xamarin y iOS

Y por último, pero no menos importante, iOS. Aquí nos vamos a aprovechar de que creamos la interface de usuario en C# directamente, para ver lo facil que es crear animaciones.

Primero lo primero, Crear la view inicial:

public override void ViewDidLoad()
{
    View = new UIView { BackgroundColor = UIColor.White };
    base.ViewDidLoad();

    // ios7 layout
    if (RespondsToSelector(new Selector(“edgesForExtendedLayout”)))
    {
        EdgesForExtendedLayout = UIRectEdge.None;
    }

    var label = new UILabel(new CGRect(10, 100, 300, 60));
    label.Alpha = 0f;
    label.TextColor = UIColor.Blue;
    Add(label);

    var set = this.CreateBindingSet<FirstView, Core.ViewModels.FirstViewModel>();
    set.Bind(label).To(vm => vm.Hello);
    set.Apply();
}

Tras crear nuestra UILabel, creamos los bindings y establecemos los datos para el texto de la etiqueta.

Ahora vamos a usar el método Animate de la clase UIView para crear nuestra animación, igual que el resto de las plataformas:

private void CreateLabelAnimation(UILabel label)
{
    UIView.Animate(0.75, 0, UIViewAnimationOptions.CurveLinear,
    () =>
    {
        label.Alpha = 1f;
    }, null);
    UIView.Animate(1, 0, UIViewAnimationOptions.CurveLinear,
    () =>
    {
        label.Transform = CGAffineTransform.MakeScale(2f, 2f);
    }, null);
    UIView.Animate(0.5, 1, UIViewAnimationOptions.CurveLinear,
    () =>
    {
        label.Transform = CGAffineTransform.MakeScale(1f, 1f);
    }, null);
}

El método Animate recibe en primer lugar la duración, en segundos, a continuación la espera en segundos antes de ejecutar la animación, el tipo de transición en el enumerado UIViewAnimationOptions, una función que ejecute la animación y por último otra función que será ejecutada al terminar la animación.

Se explica solo el código, es muy sencillo. usamos una expresión lambda para modificar el objeto UILabel que queremos animar. En el primer caso, simplemente cambiamos la propiedad Alpha. En los dos siguientes usamos la clase CGAffineTransform y el método MakeScale para realizar el escalado necesario.

En resumen

Al final, el crear pequeñas animaciones le aporta un extra de calidad a nuestra aplicación, la hace parecer más suave y ligera.

Cada plataforma tiene su forma de hacer las cosas, a no ser que usemos Xamarin.Forms, donde animamos una sola vez y funciona en todas las plataformas.

Puedes descargarte el código del ejemplo directamente desde GitHub:

https://github.com/josueyeray/XamarinAnimations

Espero que os sea útil.

Un saludo y Happy Coding!

[EVENTOS] Un febrero movidito

Hola a todos!

Este febrero va a ser movidito en cuanto a eventos de desarrollo para la comunidad .NET y los desarrolladores móviles.

Bravent Xamarin tour

Bravent, consultora especializada en soluciones Microsoft

Bravent ha organizado un pequeño tour por Madrid, Barcelona y Sevilla sobre desarrollo con Xamarin.

Cada uno de los eventos constará de 5 charlas, impartidas por Javier Suarez Ruiz y un servidor:

Servicios Xamarin: Analíticas, rendimiento, testing y más

9:00 – 9:55 Introducción al desarrollo de apps con Xamarin.
10:00 – 10:55 Arquitectura de proyectos multiplataforma con Xamarin.
11:10 – 12:05 Introducción a Xamarin.Forms
12:10 – 13:05 Aplicaciones multiplataforma y Microsoft Azure
13:10 – 14:15  

¿Fechas, lugares y registro? A continuación:

  • Madrid, 4 de Febrero. Oficinas de Microsoft Ibérica. Registro aquí.
  • Sevilla, 9 de Febrero. Clouding Point. Registro aquí.
  • Barcelona,  16 de Febrero. Oficinas de Microsoft Ibérica. Registro aquí

Pasaremos una mañana entretenida, hablando de desarrollo móvil multiplataforma y haciendo un poco de networking.

Microsoft .NET Spain Conference

image

EL EVENTO, así con mayúsculas. Dos días completos, 8 tracks con sesiones de más de 1 hora de duración, dedicadas a todo el ecosistema .NET:

  • ALM
  • Web
  • Cloud
  • Data
  • Enterprise
  • Apps
  • Games
  • IoT

A esto hay que sumarle hands on labs durante los dos días y talleres para niños.  Un evento que no te puedes perder. El precio de las entradas ahora mismo va desde los 5 a los 10 euros, vamos unos desayunos.

Más detalles e inscripciones, aquí. El evento se celebrará en Madrid, en el campus universitario de la universidfad Politécnica de Alcalá, los días 27 y 28 de febrero.

Espero poder veros a muchos en alguno de estos eventos.

Un saludo y Happy Coding!

Azure Notifications Hub y Xamarin

Uno de los mayores problemas al trabajar con múltiples plataformas móviles, es el envío de notificaciones desde nuestro backend. En Azure tenemos disponible un servicio llamado NotificationsHub, que nos permite simplificar esta tarea.

Configurando un hub de notificaciones

Para empezar, necesitaremos configurar un centro de notificaciones en nuestra cuenta de Azure, desde el portal de administración. El apartado de Notifications Hub se encuentra dentro de la parte de Service Bus:

image

Tendremos que indicar un nombre del servicio, la región en la que queremos ejecutarlo y el namespace que usaremos. Una vez que lo hayamos creado, podemos entrar a su configuración. Lo unico que necesitamos en primera instancia es configurar las credenciales de cada Store (Apple Store, Google Play y Microsoft Store). Cada una de ellas tiene su propia configuración:

  • Apple Store: Necesitaremos generar un certificado digital que debemos generar con nuestra cuenta de desarrollador.
  • Windows Phone (MPNS) Necesitaremos generar un certificado digital.
  • Windows y Windows Phone (WNS) Tenemos que indicar el SID del paquete de nuestra aplicación y el secreto.
  • Google Play: Tenemos que indicar la clave de GCM (Google Cloud Messaging).

Vamos a ver como obtener las credenciales necesarias para Windows Phone (WNS) y para Google Play a continuación.

WNS

Para obtener el SID y el secreto de una aplicación que use WNS (Windows 8.1 o Windows Phone 8.1) Debemos tenerla asociada con la tienda en primer lugar y con un nombre reservado. Esto no significa que tenga que estar publicada. Simplemente debemos crear el proceso de publicación y reservar un nombre. Una vez hecho esto, en los detalles de la aplicación, en el dashboard de Windows Store podremos ir al apartado servicios:

image

Donde encontraremos un enlace para visitar el sitio de servicios Live:

image

En el sitio de Servicio Live, encontraremos los datos que necesitamos, el SID de paquete y la clave secreta:

image

Esos dos datos son los que necesitamos configurar en nuestro Notifications Hub, en la parte de WNS:

image

Y listo, ya tenemos todo lo necesario para que nuestro NotificationHub envie notificaciones a nuestra app Windows Phone o Windows Store.

Google Play

Google Play también requiere que realicemos cierta configuración en las APIs de google. Tenemos que ir a console.developers.google.com y crear un nuevo proyecto para nuestra aplicación. En el ejemplo crearemos un proyecto llamado NSHubSample con id: nshubsample-xamarin.

Dentro del proyecto tendremos que ir a la sección de APIs y habilitar el API Google Cloud Messaging for Android. Después, en la sección de credenciales tendremos que crear una nueva clave de servidor (clave pública) y quedarnos con la clave de API:

image

Ya solo tenemos que volver a la configuración de nuestro Notification Hub y añadir la clave de API de Google Play en la sección de Google Cloud Messaging settings:

image

Bien, con esto ya tenemos configurado nuestro notification hub para enviar mensajes a aplicaciones Windows, Windows Phone y Android. Como último paso, necesitaremos las cadenas de conexión para comunicarnos desde el servidor que envíe las notificaciones y desde las apps cliente. Podemos encontrarlas en la página principal de nuestro Notifications Hub, en el link “View Connection Strings”:

image

Al pulsar, se abrirá un popup en el que se mostrarán dos cadenas de conexión:

image

Nos quedaremos con ambas guardadas para usarlas en los siguientes pasos.

Ahora vamos a por la parte de las apps cliente. Usando Xamarin, vamos a crear una app para Android, Windows y Windows Phone.

Obteniendo los canales de notificación

Aunque con Xamarin podemos compartir la mayoría del código, la parte de registrar nuestro dispositivo/app con la nube de notificaciones es única en cada plataforma, al igual que el registro que hemos tenido que hacer anteriormente.

Windows XAML

Para comenzar, necesitaremos instalar el paquete “Windows Azure Service Bus Managed for Windows Store and Windows Phone”, desde el gestor de paquetes de NuGet en nuestro proyecto universal. Este paquete nos facilitará la tarea de enviar a Notifications Hub nuestros canales de notificaciones.

Para obtener estos canales de notificaciones usaremos la clase PushNotificationChannelManager, del namespace Windows.Networking.PushNotifications. Para ello usaremos el método CreatePushNotificationChannelForApplicationAsync. Una vez que tengamos el canal de notificaciones, se lo enviaremos a nuestro notifications hub. Para ello crearemos una nueva instancia de la clase NotificationHub, pasándo como parámetro:

  • El nombre de nuestro Notification Hub creado anteriormente, en este caso: nhsampledemo.
  • La cadena de conexión DefaultListenSharedAccessSignature que hemos obtenido anteriormente.

Finalmente, llamamos al método RegisterNativeAsync, pasándole la Uri del canal obtenido al principio como parámetro.

private async void InitNotificationsAsync()
{
    var channel = await PushNotificationChannelManager.CreatePushNotificationChannelForApplicationAsync();

    var hub = new NotificationHub("nhsampledemo", "<Endpoint listen>");
    var result = await hub.RegisterNativeAsync(channel.Uri);
}

Y con esto ya hemos registrado el canal de notificaciones para Windows y Windows Phone.

Android

En Android, necesitaremos en primer lugar instalar el componente de Google Cloud Messaging Client. Para ello hacemos click derecho sobre la carpeta Components del proyecto Android y seleccionamos la opción “Get more components”. Esto nos mostrará la lista de componentes disponibles, solo tendremos que buscar “Google Cloud Messaging Client”, hacer click sobre él y a continuación sobre el botón verde “Add to App”:

image

Para registrar nuestra aplicación, necesitaremos además el ID de proyecto de google api console que creamos anteriormente. Lo podemos encontrar en la página principal del proyecto:

image

Ahora si, ya podemos escribir el código necesario para registrar nuestra app con la nube de mensajes de google. Por ejemplo, en el método OnCreate de nuestra primera vista:

private void RegisterWithGCM()
{
    GcmClient.CheckDevice(this);
    GcmClient.CheckManifest(this);
    GcmClient.Register(this, "<PROJECT ID>");
}

El método CheckDevice comprueba que el dispositivo soporta los servicios de google play, necesarios para tener notificaciones. CheckManifest comprueba que hemos establecido las configuraciones necesarias en el manifiesto de la aplicación. Por último registramos nuestra aplicación con el método Register, en el que usamos el id de proyecto que hemos obtenido anteriormente.

Ahora ya tenemos nuestra app de Android registrada, pero para poder recibir notificaciones necesitaremos crear un servicio en background que las procese cuando lleguen al dispositivo. Para ello necesitaremos crear dos nuevas clases:

  • MyBroadcastReceiver, que se registrará como receptor de Google Cloud Messaging
  • GcmService, que tendrá el código necesario para recibir y procesar nuestras notificaciones.

Necesitaremos decorar el namespace de estas clases con los atributos de permisos que necesitamos:

[assembly: Permission(Name = "notificationsHub.Android.permission.C2D_MESSAGE")]
[assembly: UsesPermission(Name = "notificationsHub.Android.permission.C2D_MESSAGE")]
[assembly: UsesPermission(Name = "com.google.android.c2dm.permission.RECEIVE")]

[assembly: UsesPermission(Name = "android.permission.GET_ACCOUNTS")]
[assembly: UsesPermission(Name = "android.permission.INTERNET")]
[assembly: UsesPermission(Name = "android.permission.WAKE_LOCK")]

namespace NotificationsHub.Android

La clase MyBroadfcastReceiver también tiene que estar decorada con algunos atributos para identificarla correctamente. Tiene que heredar ademas de la clase GcmBroadcastReceiverBase<>:

[BroadcastReceiver(Permission = Gcm.Client.Constants.PERMISSION_GCM_INTENTS)]
[IntentFilter(new string[] { Gcm.Client.Constants.INTENT_FROM_GCM_MESSAGE }, Categories = new string[] { "@PACKAGE_NAME@" })]
[IntentFilter(new string[] { Gcm.Client.Constants.INTENT_FROM_GCM_REGISTRATION_CALLBACK }, Categories = new string[] { "@PACKAGE_NAME@" })]
[IntentFilter(new string[] { Gcm.Client.Constants.INTENT_FROM_GCM_LIBRARY_RETRY }, Categories = new string[] { "@PACKAGE_NAME@" })]
public class MyBroadcastReceiver : GcmBroadcastReceiverBase<GcmService>
{
    public static string[] SENDER_IDS = new string[] { "<PROJECT ID>" };

    public const string TAG = "MyBroadcastReceiver-GCM";
}

GcmService, se decora con el atributo Service y hereda de GcmServiceBase. Además tendremos que agregar el paquete de NuGet Xamarin.NotificationHub al proyecto Android para poder crear el código de esta clase:

[Service] //Must use the service tag
public class GcmService : GcmServiceBase
{
    public static string RegistrationID { get; private set; }
    private NotificationHub Hub { get; set; }

    public GcmService() : base("<PROJECT ID>")
    {
        Log.Info(MyBroadcastReceiver.TAG, "GcmService() constructor");
    }

    protected override async void OnRegistered(Context context, string registrationId)
    {
        Log.Verbose(MyBroadcastReceiver.TAG, "GCM Registered: " + registrationId);
        RegistrationID = registrationId;

        createNotification("GcmService-GCM Registered…", "The device has been Registered, Tap to View!");

        Hub = new NotificationHub("nhsampledemo", "<listen endpoint>");
        try
        {
            await Hub.UnregisterAllAsync(registrationId);
        }
        catch (Exception ex)
        {
            Debug.WriteLine(ex.Message);
            Debugger.Break();
        }

        try
        {
            List<string> TAGS = new List<string>()
            {
            };
            var hubRegistration = await Hub.RegisterNativeAsync(registrationId, TAGS);
        }
        catch (Exception ex)
        {
            Debug.WriteLine(ex.Message);
            Debugger.Break();
        }
    }

    protected override void OnMessage(Context context, Intent intent)
    {
        Log.Info(MyBroadcastReceiver.TAG, "GCM Message Received!");

        var msg = new StringBuilder();

        if (intent != null && intent.Extras != null)
        {
            foreach (var key in intent.Extras.KeySet())
                msg.AppendLine(key + "=" + intent.Extras.Get(key).ToString());
        }

        string messageText = intent.Extras.GetString("msg");
        if (!string.IsNullOrEmpty(messageText))
        {
            createNotification("New hub message!", messageText);
            return;
        }

        createNotification("Unknown message details", msg.ToString());
    }

    void createNotification(string title, string desc)
    {
        var notificationManager = GetSystemService(Context.NotificationService) as NotificationManager;
        var uiIntent = new Intent(this, typeof(FirstView));
        var notification = new Notification(Android.Resource.Drawable.Icon, title);
        notification.Flags = NotificationFlags.AutoCancel;
        notification.SetLatestEventInfo(this, title, desc, PendingIntent.GetActivity(this, 0, uiIntent, 0));

        notificationManager.Notify(1, notification);
    }

    protected override void OnError(Context context, string errorId)
    {
        //Manage errors
    }

    protected override void OnUnRegistered(Context context, string registrationId)
    {
        //Manage unregistering services.
    }
}

Podemos encontrar cuatro métodos importantes en esta clase: OnRegistered, OnMessage, OnError y OnUnRegistered:

  • OnRegistered se ejecuta cuando, en el paso anterior, registramos la aplicación con el backend Google Cloud Messaging, recibe el contexto y el id de registro, creamos una nueva instancia de la clase NotificationHub, des registramos las notificaciones que usen ese mismo ID, si había alguna, y a continuación llamamos a RegisterNativeAsync, igual que en Windows XAML.
  • OnMessage es invocado cuando recibimos un mensaje desde Google Cloud Messaging, en este caso procesamos el mensaje y lo mostramos en pantalla creando una nueva notificación.
  • OnError se lanza si se produce un error.
  • OnUnRegistered se ejecuta si esta instancia activa es des registrada.

iOS

Para iOS trabajaremos sobre la clase AppDelegate. Pero en primer lugar, tendremos que añadir un componente, de la misma forma que hicimos en Android. En este caso necesitamos el componente de Azure Mobile Services. También tendremos que añadir una referencia a la librería de WindowsAzure.Messaging para iOS. Todavía no existe un paquete exclusivo para ella, se descarga junto a la de Android que instalamos anteriormente.

Una vez instalado Azure Mobile Services, en la clase AppDelegate tenemos que sobre escribir los métodos FinishedLaunching, RegisterForRemoteNotifications y ReceivedRemoteNotification. En FinishedLaunching tenemos que añadir la llamada al registro de la aplicación en el servicio de notificaciones:

UIApplication.SharedApplication.RegisterForRemoteNotificationTypes(notificationTypes);

Tras ésta llamada, se lanzará el método RegisteredForRemoteNotifications, donde recibiremos el token del dispositivo y lo registraremos en nuestro notifications hub:

public override async void RegisteredForRemoteNotifications(UIApplication application, NSData deviceToken)
{
    Hub = new NotificationHub(ConnectionString, NotificationHubPath);
    await Hub.UnregisterAllAsync(deviceToken.ToString());
    await Hub.RegisterNativeAsync(deviceToken.ToString());
}

Cuando recibamos una notificación, se llamará al método ReceivedRemoteNotification, con la información de la misma dentro de un diccionario:

public override void ReceivedRemoteNotification(UIApplication application, NSDictionary userInfo)
{
    ProcessNotification(userInfo, false);
}

void ProcessNotification(NSDictionary options, bool fromFinishedLaunching)
{
    // Check to see if the dictionary has the aps key.  This is the notification payload you would have sent
    if (options != null && options.ContainsKey(new NSString("aps")))
    {
        //Get the aps dictionary
        NSDictionary aps = options.ObjectForKey(new NSString("aps")) as NSDictionary;

        string alert = string.Empty;

        if (aps.ContainsKey(new NSString("alert")))
            alert = (aps[new NSString("alert")] as NSString).ToString();

        //If this came from the ReceivedRemoteNotification while the app was running,
        // we of course need to manually process things like the sound, badge, and alert.
        if (!fromFinishedLaunching)
        {
            //Manually show an alert
            if (!string.IsNullOrEmpty(alert))
            {
                UIAlertView avAlert = new UIAlertView("Notification", alert, null, "OK", null);
                avAlert.Show();
            }
        }
    }
}

En este caso, tenemos que diferenciar si el método se llama desde el FinishedLaunching o no, para decidir si debemos o no mostrar la notificación recibida.

Y con esto hemos terminado con las aplicaciones cliente. Ahora podemos crear nuestro Servicio que envíe las notificaciones al Notification Hub para que este las distribuya a los diferentes servicios.

Servidor

Como ejemplo, usaremos una aplicación WPF muy sencilla, con unos botones que nos permitan enviar una notificación a cada plataforma. Para ello necesitaremos dos paquetes de NuGet:

  • Microsoft.WindowsAzure.ConfigurationManager
  • WindowsAzure.ServiceBus

El paquete Microsoft.WindowsAzure.ConfigurationManager se instalará automáticamente al instalar WindowsAzure.ServiceBus, como una dependencia. Una vez instalado este paquete, podremos escribir el siguiente código para enviar una notificación WNS (Windows Notification Service):

private async void Send_Windows(object sender, RoutedEventArgs e)
{
    NotificationHubClient hub = NotificationHubClient.CreateClientFromConnectionString("<FULLSHAREDACCESS ENDPOINT>", "nhsampledemo");
    var toast = @"<toast><visual><binding template=""ToastText01""><text id=""1"">Hello from a .NET App!</text></binding></visual></toast>";
    await hub.SendWindowsNativeNotificationAsync(toast);
}

Para Android y Google Cloud Messaging, usaríamos este código:

private async void Send_Android(object sender, RoutedEventArgs e)
{
    NotificationHubClient hub = NotificationHubClient.CreateClientFromConnectionString("<FULLSHAREDACCESS ENDPOINT>", "nhsampledemo");
    var message = "{ \"data\" : {\"msg\":\"Hello from Azure!\"}}";
    var result = await hub.SendGcmNativeNotificationAsync(message);
}

Para iOS tendríamos el siguiente código:

private async void Send_iOS(object sender, RoutedEventArgs e)
{
    NotificationHubClient hub = NotificationHubClient.CreateClientFromConnectionString("<FULL ENDPOINT>", "nhsampledemo");
    var alert = "{\"aps\":{\"alert\":\"Hello from .NET!\"}}";
    await hub.SendAppleNativeNotificationAsync(alert);
}

En ambos casos, el proceso es el mismo. Primero creamos una nueva instancia de la clase NotificationHubClient, pasándo la connection string de full access y el nombre de nuestro Notification hub. A continuación creamos el “payload”, la notificación que deseamos enviar. En este paso, tenemos que usar el formato nativo de cada plataforma de notificaciones. Para conocer como escribir esta “payload”, podemos visitar la página de cada plataforma:

Por último, para cada plataforma tenemos un método de envío de la notificación. En este ejemplo usamos SendWindowsNativeNotificationAsync para WNS y SendGcmNativeNotificationAsync para GCM.

Ya solo nos queda ejecutar las aplicaciones cliente para que se registren y lanzar la aplicación WPF para que envíe la notificación a cada una de ellas. La mejor parte de Notifications Hub, es no tener que pelearnos en nuestro servidor con tres APIs de notificaciones totalmente distintas. Nos permite centrarnos en crear las notificaciones y enviarlas de forma sencilla.

Puedes ver el código del ejemplo en GitHub, pero recuerda sustituir <LISTEN ENDPOINT>, <FULL ENDPOINT>, <PROJECT ID> y “nhsampledemo” por los valores correctos de tú Notification Hub, para hacerlo funcionar.

Un saludo y Happy Coding!

 

[FORENSIC] StorageFile vs IsolatedStorage

Hola!

Durante el pasado Codemotion 2014 celebrado en Madrid, Sergio Navarro me comentó que tenía un grave problema de rendimiento en una aplicación Windows Store. Sergio usa en sus aplicaciones una estupenda base de datos NoSQL multi plataforma llamada SiaqoDb. Es una base de datos estupenda, que facilita el almacenamiento de datos en aplicaciones destinadas a múltiples plataformas.

El problema

El problema en cuestión es que SiaqoDb tenía un rendimiento muy pobre en las lecturas en las aplicaciones para Windows 8.1, usando WinRT, en comparación con las aplicaciones Windows Phone usando Silverlight. En concreto estimaron que el cambio de rendimiento era de cerca de 40 veces más lento en WinRT.

La gente de SiaqoDb incluso le enviaron un ejemplo en C#, implementando la interfaz de archivos que usan internamente, para demostrar que el problema venía del lado del sistema. Si tenéis curiosidad por reproducir el problema, aquí tenéis el código original. La mayor peculiaridad que tiene, es que no leen el archivo completo de una sola vez, lo hacen en pequeñas partes. En el ejemplo, 5000 lecturas.

La verdad es que me picó mucho la curiosidad, pero en un primer momento pensé que la diferencia de rendimiento se podría deber a un tema puramente de hardware. Los smartphones llevan memoria solida y quizás si comparas su rendimiento con un disco duro normal, la lectura se puede resentir. Pero, ¿40 veces? Es una gran diferencia.

Por otro lado, Juan Manuel Montero también estaba dandole un vistazo al código y parecio que resolvio el problema, cambiando la forma en la que se leía el archivo. Pasó de leerlo por trozos a leerlo de una sola vez. Esto resolvía el problema de velocidad, pero no era aplicable en el contexto de SiaqoDb, que necesita realizar la lectura por partes.

Así que más intrigado todavía, me puse a darle un vistazo al código. Empecé por ejecutar el código original, para descartar el hardware, ejecutándolo en un PC de escritorio con disco duro magnético y en una Surface Pro 3 con disco SSD, los resultados en WinRT fueron igual de malos: unos 6.4 segundos en leer un archivo de 5Mb. A continuación probé la ejecución en un smartphone, en concreto en mi Lumia 1520. Resultado: 0.3 segundos en leer el mismo archivo de 5Mb.

Silverlight

Estaba muy claro que algo pasaba con las lecturas en WinRT, veamos el código original del método de lectura para Windows Phone (Silverlight):

Read – Silverlight
public virtual int Read(long pos, byte[] buf)
{
    file.Seek(pos, SeekOrigin.Begin);
    return file.Read(buf, 0, buf.Length);
}

Simple y efectivo 🙂 el objeto file es de tipo IsolatedStorageFileStream. Simplemente nos movemos a la posición deseada dentro del archivo y leemos la información dentro de un array de bytes.

WinRT

Veamos ahora el método ReadAsync de WinRT:

ReadAsync – WinRT
public virtual async Task<int> ReadAsync(long pos, byte[] buf)
{
    fileStream.Seek((ulong)pos);
    if (buf.Length > 0)
    {
        var buffer = Windows.Security.Cryptography.CryptographicBuffer.CreateFromByteArray(buf);
        IBuffer rd = await fileStream.ReadAsync(buffer, (uint)buf.Length, InputStreamOptions.None);
        rd.CopyTo(buf);
    }
    return (int)buf.Length;
}

Mmm, sencillo, pero podemos empezar a ver la causa del problema, ¿Lo puedes ver? En este caso, el código es asíncrono como todas las APIs de acceso a disco de WinRT. Necesitamos crear una instancia de IBuffer, tipo que usa el método ReadAsync de la clase FileRandomAccessStream. A continuación, ser hace una copia del IBuffer resultante del método ReadAsync al array de bytes para devolver su tamaño.

Desde luego realizamos muchas más operaciones, creamos objetos, hacemos copias… ¿Cuanto tiempo nos penalizan estas acciones? Midiéndolas, de una forma muy burda, podemos ver lo siguiente:

  • El método CreateFromByteArray de la clase CryptographicBuffer, consume en total 0.6 segundos. Esto por sí solo ya es el doble que la lectura completa del archivo en Silverlight.
  • El método CopyTo de IBuffer, consume otros 0.3 segundos.

En total, solo en convertir los datos que necesitamos, hemos invertido casi 1 segundo, tres veces el tiempo necesario para toda la operación en Silverlight. Hasta 6.4 segundos, 5.4 segundos los consume el método ReadAsync de FileRandomAccessStream.

La solución

Aquí entramos en un terreno interesante. Está claro que la forma de trabajar con async/await es mucho más costosa que en un código secuencial y síncrono como tenemos en Silverlight. El método ReadAsync internamente, además de realizar la operación de lectura propiamente dicha, devuelve el progreso de la operación. Todo esto conlleva una sobrecarga en el tiempo de ejecución.

¿Y porqué si leemos el archivo de una sola vez, el rendimiento mejora?

Para verlo claro, veamos cuantos milisegundos tarda en ejecutar la lectura Silverlight. Silverlight tarda 75 milisegundos en realizar los 5000 lecturas. El problema que tenemos, en mi opinión, es que la operación de lectura individual es tan rápida, que la sobrecarga de cada lectura introducida por la infrastructura de async/await es notoria. Cuando leemos el archivo de una sola vez, el rendimiento mejora porque esa sobre carga solo se produce una vez.

Cuando el equipo de ingeniería detrás de WinRT planeo las APIs asíncronas, tuvo en mente un objetivo claro: Cualquier operación que tardase, o pudiese tardar más de 30 milisegundos, sería asíncrona. Pero este no es nuestro caso. ¿Y si nos deshacemos de la asíncronía en WinRT, que pasa? Pero, ¿Como lo hacemos si las APIs de disco de WinRT son todas asíncronas?

El objeto FileRandomAccessStream tiene un método extensor llamado AsStream, que nos devuelve la instancia de Stream que usa internamente para trabajar. y este objeto Stream contiene los métodos Read y Write síncronos que usamos tradicionalmente en Silverlight. En el constructor de nuestra clase de archivo, vamos a guardarnos la referencia a esa Stream y a continuación, podemos reescribir el método ReadAsync, de forma síncrona, así:

Read – WinRT
public virtual int Read(long pos, byte[] buf)
{
    syncfileStream.Seek(pos, SeekOrigin.Begin);
    if (buf.Length > 0)
    {
        return syncfileStream.Read(buf, 0, buf.Length);
    }
    return 0;
}

Usando la instancia de Stream que nos guardamos en el constructor, simplemente realizamos un Seek para posicionarnos en el lugar de la Stream que queremos leer y a continuación leemos el archivo. Tiempo total de lectura usando este método síncrono: 0.25 segundos. ¡Eso ya está mejor! Ya nos movemos en los mismos tiempos que con Silverlight, básicamente porque estamos usando el mismo código que en Windows Phone 8.0, por lo que el resultado no puede  ser muy diferente.

Conclusión

Y hasta aquí este pequeño análisis, después de hablar con Sergio y con la gente de SiaqoDb, han probado la solución y la van a implementar en su código base, han realizado la certificación de una aplicación usando esta solución y ha pasado a la store sin problemas.

Además de darme la oportunidad de escribir este pequeño artículo, esta situación enseña lo importante de la comunidad. Entre todos, hemos llegado a resolver un problema latente, que podría impedir que algunas aplicaciones tuviesen el rendimiento deseado. Así que es una victoria de todos los que creemos en compartir un poco de nuestro tiempo y conocimientos para ayudar a los demas.

Un saludo y HappyCoding!