Xamarin UITest

[Xamarin] Xamarin UITest y Test Recorder desde Visual Studio

Bug - 02La calidad en movilidad cuesta

La calidad en el software es algo innegociable. Un buen proceso en el desarrollo y gestión del proceso es fundamental para conseguir ese objetivo. Debemos entregar Apps móviles perfectamente adaptadas a cada plataforma, ofreciendo la mejor experiencia de usuario posible pero sobretodo, funcional. Una App funcional debe cubrir y cumplir unos mínimos exigentes de calidad.

Como desarrolladores, somo humanos y el código no estara libre de errores. Sin embargo, el proceso que apliquemos para la detección y corrección a los mismos, es vital.

Podemos crear dos tipos de pruebas diferenciadas:

  • Pruebas unitarias: Pruebas de pequeñas unidades funcionales de nuestra App. Utilizaremos NUnit para realizar estas pruebas unitarias generalmente de ViewModels, Helpers y Servicios.
  • Pruebas de interfaz de usuario: Pruebas sobre la interfaz de usuario, escritura en cajas de texto, pulsaciones de botones, etc. Utilizaremos Xamarin UITest para estas pruebas.

Xamarin UITest

Xamarin.UITest es un framework de testing que permite realizar pruebas de comportamiento de la aplicación automatizando interacciones con la misma.

Integrado con Xamarin.iOS y Xamarin.Android, aunque puede usarse con proyectos iOS y Android escritos con Objective-C y Java, permite automatizar la gestión con la interfaz con pulsación de botones, introducir textos, gestos, tomar capturas, etc.

Cada UITest es un método escrito en C# que sigue el patrón Arrange-Act-Assert:

  • Arrange: El test inicializa todo lo necesario para que pueda ser lanzado.
  • Act: La interacción automatizada con la aplicación, introducir textos, pulsar botones, etc.
  • Assert: El test verifica resultados de las acciones realizadas en Act. Por ejemplo, verifica que tras introducir un valor incorrecto en una caja de texto, aparece un mensaje de error.

Nuestra aplicación:

Calculadora
Calculadora

Para poder analizar y profundizar en las pruebas, vamos a utilizar una aplicación realmente sencilla, una calculadora. Vamos a ver y analizar el proyecto de tipo Xamarin UITest de nuestra aplicación.

Todas las interacciones automatizadas con la aplicación ocurren mediante una instancia de Xamarin.UITest.IApp. Esta interfaz define los métodos utilizados para realizar la interacción. Cuenta con dos implementaciones:

  • Xamarin.UITest.iOS.iOSApp: Automatiza en aplicaciones iOS.
  • Xamarin.UITest.Android.AndroidApp: Automatiza en aplicaciones Android.

Ambos objetos, iOSApp y AndroidApp, se instancian utilizando la clase ConfigureApp. Esta clase se asegura de instanciar correctamente la aplicación en cada caso.

Tras instanciar la aplicación, la interacción con la aplicación se realiza utilizando querys. Los métodos de Xamarin.UITest esperan en la mayoría de casos un parámetro de tipo Func<AppQuery, AppQuery> para localizar los elementos visuales.

En nuestra aplicación, queremos realizar una prueba sencilla de suma, donde accederemos al botón numérico dos, al operador de suma y al símbolo igual. Esto nos permitirá verificar que el comportamiento de sumar dos más dos es el esperado.

Utilizando querys accedemos a cada elemento visual:

static readonly Func<AppQuery, AppQuery> TwoButton = c => c.Marked("Digit2");
static readonly Func<AppQuery, AppQuery> PlusButton = c => c.Marked("Operator+");
static readonly Func<AppQuery, AppQuery> EqualsButton = c => c.Marked("OperatorEquals");

Por supuesto, antes de lanzar el test, debemos realizar el proceso de inicialización de IApp. Normalmente, en una clase de tests tendremos agrupados múltiples tests. Cada uno de ellos debe correr en una condiciones limpias, es decir, se debe realizar la inicialización de IApp en cada caso. Este proceso se suele realizar en el método SetUp.

private IApp _app;

[SetUp]
public void SetUp()
{
     switch (TestEnvironment.Platform)
     {
          case TestPlatform.Local:
               var appFile =
                    new DirectoryInfo(Path.Combine("..", "..", "testapps"))
                        .GetFileSystemInfos()
                        .OrderByDescending(file => file.LastWriteTimeUtc)
                        .First(file => file.Name.EndsWith(".app") || file.Name.EndsWith(".apk"));

                _app = appFile.Name.EndsWith(".app")
                        ? ConfigureApp.iOS.AppBundle(appFile.FullName).StartApp() as IApp
                        : ConfigureApp.Android.ApkFile(appFile.FullName).StartApp();
                break;
           case TestPlatform.TestCloudiOS:
                _app = ConfigureApp.iOS.StartApp();
                break;
           case TestPlatform.TestCloudAndroid:
                _app = ConfigureApp.Android.StartApp();
                break;
     }
}

Por último, tras tener la inicialización (creada automáticamente al crear nuevo proyecto de tipo Xamarin UITest) y las querys, nuestro test de suma de dos más dos será:

[Test]
public void TheTwoPlusTwoIsFourTest()
{
     _app.WaitForElement(c => c.Marked("OperatorEquals"));

     _app.Tap(TwoButton);
     _app.Tap(PlusButton);
     _app.Tap(TwoButton);
     _app.Tap(EqualsButton);
     _app.Screenshot("When I get the result value");

     AppResult[] results = _app.WaitForElement(c => c.Marked("DisplayValue").Text("4"));

     Assert.IsTrue(results.Any());
}

Pulsamos en cada botón, tomamos captura del resultado y finalmente verificamos el resultado.

Corriendo Xamarin UITests desde Visual Studio

Para lanzar pruebas unitarias y Xamarin.UITests desde Visual Studio, utilizaremos el explorador de pruebas disponible desde el menu Test.

Explorador de pruebas
Explorador de pruebas

En nuestro proyecto con la calculadora realizada en Xamarin.Forms con pruebas con Xamarin.UITests vemos lo siguiente…

¿Por qué no aparecen los tests?
¿Por qué no aparecen los tests?

No aparecen, ¿qué ocurre?. El explorador de pruebas solo muestra por defecto tests realizados con MSTests. En nuestro proyecto Xamarin utilizamos NUnit para las pruebas unitarias y además XAmarin UITests, hace uso de NUnit también.

Vamos a solucionar el problema añadiendo la posibilidad de lanzar tests realizados con NUnit. Desde el menu de Herramientas->Extensiones y actualizaciones…

Extensions
Extensiones y actualizaciones

Dentro del conjunto Online buscamos por NUnit Test Adapter. Debemos instalar el adaptador correspondiente a NUnit 2 a pesar de estar disponible la versión correspondiente a NUnit 3. Esto es así ya que Xamarin UITest hace uso de la versión 2 de NUnit.

NUnit Test Adapter
NUnit Test Adapter

Tras instalar la extensión, al compilar el proyecto:

Aparecen los tests!
Aparecen los tests!

Ahora podemos lanzar o depurar cualquiera de las pruebas (o todas) sencillamente haciendo clic derecho sobre las mismas.

Xamarin Test Recorder

Podemos crear Xamarin UITests a mano y haciendo uso del REPL, sin embargo, es mucho más sencillo hacer uso de Xamarin Test Recorder.

Xamarin Test Recorder es una herramienta que nos permite, seleccionar un paquete de aplicación, lanzar la aplicación, interaccionar con la interfaz de usuario capturando los movimientos necesarios y la herramienta generará automáticamente el test en base a la interacción realizada.

Para poder utilizar la herramienta desde Visual Studio debemos instalar la extensión Xamarin Test Recorder 2015.

NOTA: Existe una versión de la extensión preparada para usar desde Visual Studio 2013.

Xamarin Test Recorder Extension
Xamarin Test Recorder Extension

Tras instalar la extensión, para poder utilizarla debemos cumplir unos requisitos básicos:

  1. Tener abierto un proyecto de tipo Xamarin UITest. En caso de no contar con ninguno en el proyecto, crear uno.
  2. Tener abierto un emulador o bien conectado por USB un dispositivo.

Tras cumplir los requisitos, podemos ver el siguiente indicador visual:

Indicador visual
Indicador visual

Pulsando sobre el indicador visual, nos aparecerá una opción para grabar un nuevo test.

Grabar nuevo test
Grabar nuevo test

En este punto, podremos elegir el paquete de la aplicación que deseamos probar.

Seleccionar paquete
Seleccionar paquete

Tras seleccionar el paquete de la aplicación, se lanzará la misma en el emulador o dispositivo conectado además de crear un nuevo método de Test vacío en el editor.

Con la aplicación en ejecución, y la grabación del test, cualquier interacción realizada quedará reflejada en un nuevo paso del nuevo método de test creado por la herramienta.

Una vez completada la interacción, vamos a detener la grabación. Pulsamos sobre el icono de grabación y nos aparecerá un menu como el siguiente:

Detener grabación
Detener grabación

Bastará con elegir la primera de las opciones para detener la grabación. Tendremos el código C# resultante de las interacciones realizadas en un nuevo método creado por la herramienta.

En el proceso de grabación del test, podemos tomar capturas de pantalla en cualquier momento pulsando sobre el icono de grabación y utilizando la opción Take Screenshot.

Sencillo, ¿cierto?. Las pruebas creadas las podemos lanzar directamente en local en nuestros emuladores o dispositivos físicos o hacer uso de Xamarin Test Cloud.

Tenéis el código fuente del ejemplo utilizado disponible en GitHub:

Ver GitHub

Recordad que podéis dejar cualquier comentario, sugerencia o duda en los comentarios.

Más información

DataPages

[Xamarin.Forms] Utilizando DataPages

Forms-01-WFIntroducción a DataPages

En no pocas ocasiones nos planteamos realizar una aplicación sencilla para consultar información existente desde un servidor o infraestructura cloud.

En el marco del pasado Evolve 2016, Xamarin anunciaba la disponibilidad e la preview de Xamarin.Forms DataPages.

Los DataPages llegan para facilitar la creación de aplicaciones de tipo maestro-detalle con acceso a una fuente de información de la forma más rápida y sencilla posible. Se renderizarán listados y detalles automáticamente en base a los datos pudiendo personalizar con el uso de diferentes controles y el uso de temas.

DataPages se puede utilizar consumiendo el paquete NuGet Xamarin.Forms.Pages.

Permite acceder a diferentes fuentes de información con data sources prefabricados:

  • JsonDataSource: Una URL que contiene un JSON.
  • AzureDataSource (paquete NuGet separado): Azure Mobile Services.
  • AzureEasyTableDataSource (paquete NuGet separado).

Para mostrar la información correspondiente a la fuente de información contamos con una serie de páginas y controles.

Las páginas disponibles son:

  • ListDataPage: Muestra un listado de elementos.
  • DirectoryPage: Listado de elementos agrupados.
  • PersonDetailPage: Página de detalles que muestra la información correspondiente a un tipo de objeto.

En cuanto a controles:

  • ListItem: Vista prefabricada destinado a mostrar un elemento de un listado. Puede mostrar título, detalles e imagen.
ListItem
ListItem
  • CardView: Este control es similar al CardView nativo de Android. Permite mostrar un texto principal, uno secundario y una imagen.
CardView
CardView
  • HeroImage: Control que permite mostrar título, detalles e imagen, dándole peso a esta última. Tambien cuenta con una propiedad para modificar el aspecto.
HeroImage
HeroImage

Preparando la solución

Comenzamos creando una aplicación Xamarin.Forms utilizando una librería portable (PCL):

Nueva aplicación Xamarin.Forms
Añadir paquetes utilizando NuGet

Nueva aplicación Xamarin.Forms

Tras crear el proyecto debemos añadir la referencia a DataPages y Themes. Para ello, utilizaremos NuGet para añadir:

  • Xamarin.Forms.Pages
  • Xamarin.Forms.Theme.Base
  • Xamarin.Forms.Themes.Light o Xamarin.Forms.Themes.Dark
Añadir paquetes utilizando NuGet
Añadir paquetes utilizando NuGet

NOTA: Debemos añadir los paquetes NuGet tanto en la libería portable como en el proyecto específico de cada plataforma, tanto en iOS como en Android.

DataPages requiere el uso de temas para poder renderizar correctamente. Para ello, en App.xaml debemos añadir el namespace detema y añadirlo en los recursos de la aplicación.

Dependiendo del tema elegido, los espacios de nombre a utilizar son:

xmlns:light="clr-namespace:Xamarin.Forms.Themes;assembly=Xamarin.Forms.Theme.Light"    
xmlns:dark="clr-namespace:Xamarin.Forms.Themes;assembly=Xamarin.Forms.Theme.Dark"

Y para añadir el tema a utilizar dentro del conjunto de recursos de la aplicación:

<ResourceDictionary MergedWith="dark:DarkThemeResources" />        
<ResourceDictionary MergedWith="light:LightThemeResources" />

Por ahora, para completar el proceso de uso del tema, debemos añadir algo de código específico por cada plataforma para cargar las librerías correspondientes.

var x = typeof(Xamarin.Forms.Themes.DarkThemeResources);    
x = typeof(Xamarin.Forms.Themes.LightThemeResources);    
x = typeof(Xamarin.Forms.Themes.Android.UnderlineEffect);

En el caso de iOS código a ejecutar en el AppDelegate, en el de Android, en la actividad principal MainActivity.

Ejemplo básico

Añadimos la vista XAML principal de la aplicación. Incluimos el espacio de nombres correspondiente a DataPages para acceder y utilizar las páginas, controles y data sources incluidos.

xmlns:p="clr-namespace:Xamarin.Forms.Pages;assembly=Xamarin.Forms.Pages"

Añadimos:

<?xml version="1.0" encoding="UTF-8"?>
<p:ListDataPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:p="clr-namespace:Xamarin.Forms.Pages;assembly=Xamarin.Forms.Pages"
             x:Class="DataPagesDemo.SessionDataPage"
             Title="Sessions" StyleClass="Events">
    <p:ListDataPage.DataSource>
        <p:JsonDataSource Source="http://demo3143189.mockable.io/sessions" />
    </p:ListDataPage.DataSource>
</p:ListDataPage>

El resultado:

El resultado
El resultado

Increíble, ¿cierto?. Vamos a analizar el código  (escaso) añadido.

Hemos modificado el tipo de página de una ContentPage, página básica para añadir contenido en Xamarin.Forms por una ListDataPage. Xamarin.Forms DataPages nos ofrece este tipo de página que nos permite crear un maestro-detalle con suma facilidad en base una fuente de información.

Utilizando la propiedad DataSource de la página, creamos un JsonDataSource que se encargará de acceder a la información realizando la petición HTTP necesaria y parseando la información para permitir mostrarla en la UI realizando bindings.

¿Dónde realizamos el enlace de la UI con la información?. La propiedad StyleClass de la página permite acceder a un estilo. En este caso se utiliza “Events”, estilo prefabricado que ya define lo necesario para enlazar correctamente con la fuente de información (“title”, “image”, “presenter”).

Tenéis el código fuente del ejemplo básico disponible en GitHub:

Ver GitHub

Añadiendo personalización

Vamos a crear un segundo ejemplo donde personalizar aspectos como la fuente de información, el aspecto visual modificando controles utilizados y por supuesto, también los enlaces a la información.

Vamos a generar una fuente de información de pruebas utilizando Json generator. Reemplazamos la fuente utilizando el datasource de tipo JsonDataSource:

<p:ListDataPage.DataSource>
     <p:JsonDataSource Source="http://www.json-generator.com/api/json/get/clOPFoTImW?indent=2" />
</p:ListDataPage.DataSource>

A continuación, vamos a modificar la propiedad DefaultItemTemplate que permite especificar la apariencia de cada elemento dentro del listado. En lugar de utilizar el control CardView utilizado por defecto en el ejemplo anterior, vamos a utilizar un ListItem.

<p:ListDataPage.DefaultItemTemplate>
     <DataTemplate>
          <ViewCell>
               <p:ListItemControl
                    Title="{p:DataSourceBinding name}" 
                    Detail="{p:DataSourceBinding email}" 
                    ImageSource="{p:DataSourceBinding picture}"
                      DataSource="{Binding Value}">
               </p:ListItemControl>
          </ViewCell>
     </DataTemplate>
</p:ListDataPage.DefaultItemTemplate>

ListItem cuenta con propiedades Title, Detail e ImageSource para especificar sus valores, utilizando la palabra reservada DataSourceBinding enlazaremos con los campos deseados del archivo JSON.

Nuestra página quedaría finalmente:

<p:ListDataPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:p="clr-namespace:Xamarin.Forms.Pages;assembly=Xamarin.Forms.Pages"
             x:Class="DataPages.MainView"
                   StyleClass="Events" 
                   Title="Users">   
    <p:ListDataPage.DataSource>
        <p:JsonDataSource Source="http://www.json-generator.com/api/json/get/clOPFoTImW?indent=2" />
    </p:ListDataPage.DataSource>
    <p:ListDataPage.DefaultItemTemplate>
        <DataTemplate>
            <ViewCell>
            <p:ListItemControl
                Title="{p:DataSourceBinding name}" 
                Detail="{p:DataSourceBinding email}" 
                ImageSource="{p:DataSourceBinding picture}"
                  DataSource="{Binding Value}">
                </p:ListItemControl>
            </ViewCell>
        </DataTemplate>
    </p:ListDataPage.DefaultItemTemplate>
</p:ListDataPage>

Tras ejecutar:

Personalización
Personalización

Además de contar con el listado, tenemos también disponible la vista de detalles mostrando más información a partir de los diferentes datos del JSON.

Y hasta aquí el repaso a las posibilidades de Xamarin.Forms DataPages. Una forma bastante rápida y sencilla para crear aplicaciones sencillas o prototipos con posibilidades interesantes en personalización y extensión. Siempre repitiendo cuatro sencillos pasos:

  1. Instalar paquetes NuGet.
  2. Seleccionar tema.
  3. Añadir DataPage.
  4. Configurar data source y personalización.

Tenéis el código fuente disponible e GitHub:

Ver GitHub

Recordad que podéis dejar cualquier comentario, sugerencia o duda en los comentarios.

¿Y próximamente?

DataPages continuará evolucionando y madurando con varios objetivos en mente. Xamarin ya ha confirmado que:

  • En próximas versiones no necesitaremos añadir código específico de plataforma para realizar la carga de las librerías correspondientes a temas.
  • Se incluirá la posibilidad de CRUD (create-read-update-delete).

Más información

[VideoBlog] Un vistazo a XenForms

XenFormsIntroducción

Xamarin.Forms es un framework que añade una capa de abstracción sobre la parte de UI permitiendo crear aplicaciones multiplataforma nativas compartiendo tanto la lógica de negocio como la interfaz de usuario. En los últimos tiempos hemos ido recibiendo tanto por parte de Xamarin como por parte de la comunidad gran cantidad de mejoras para aumentar el rendimiento en el desarrollo como el editor visual, Xamarin.Forms Previewer o Gorilla Player.

XenForms

La comunidad sigue creciendo a un ritmo imparable ofreciendo cada vez más y mejores opciones. Hoy estamos ante XenForms, herramienta creada por Michael Davis destinada a editar interfaces en Xamarin.Forms. Permite crear interfaces partiendo desde cero añadiendo Layouts y Views.

Construyendo la UI desde cero
Construyendo la UI desde cero

Además de poder editar cada propiedad de cada elemento, o los eventos.

Modificación de propiedades
Modificación de propiedades

También podemos cargar un proyecto ya existente para previsualizar la interfaz, hacer cambios al vuelo con el editor visual editando propiedades, hacer cambios a nivel de XAML, etc.

Carga de proyectos existentes
Carga de proyectos existentes

Suena interesante, ¿cierto?. Si te lo parece, no te pierdas el siguiente recorrido por la herramienta en video.

También se puede trabajar con Apps utilizando MVVM realizando pruebas de enlaces a datos (interactuar con la aplicación a la par que se edita), uso de propiedades avanzadas y adjuntas, etc.

Una herramienta interesante y sin duda con mucho potencial, ¿y a ti, qué te parece?.

Más información

Renovado como Microsoft MVP!

MVP_BlueMicrosoft MVP 2016

Parece que fue ayer, pero fue hace un año cuando recibí un correo que comenzaba por…

Estimado/a Javier Suarez,

Enhorabuena. Nos complace presentarle el Premio MVP de Microsoft…

Van tres y tras otro año, volver a recibirlo hace la misma e incluso más ilusión!. Más aún si cabe si, además de pertener a la categoría Windows Development se incluye el área Visual Studio and Development Technologies. Un gran honor formar parte de un grupo de desarrolladores tan talentosos y apasionados además de grandes personas.

Agradecimientos

Llegados a este punto, siempre quieres agradecer a muchas partes que han ayudado en el camino. Comienzo por vosotros, si, si hablo de ti querido lector. Gracias por leer estos artículos técnicos y ayudarme a aprender intercambiando opiniones día a día, gracias a mis compañeros MVPs, en especial a los de desarrollo Windows y Visual Studio con quienes comparto la inmensa mayoría de eventos, proyectos personales y grandes momentos (soís grandes), a los grupos de usuario WPSUG, SVQXDG y CartujaDotNet donde colaboro y me permiten aprender más y más cada día con fantásticos compañeros y a Cristina González, MVP lead, por su increíble labor siempre pendiente de todos nosotros además de a los chicos de Microsoft España. Podría continuar nombrando a muchos amigos y compañeros, pero me temo que me extendería en exceso y al final y al cabo sabéis quienes sois😉.

Por último, no quisiera terminar sin agradecer a mi familia y sobretodo a mi pareja que siempre me apoya y me cede tiempo para que pruebe esa última versión de algun remoto SDK que acaba de salir o de participar en X evento, en definitiva,  probar, jugar con la tecnología y divertirme.

Y ahora que?

Ahora, a continuar aprendiendo y disfrutando compartiendo todo lo posible en blogs, comunidades técnicas y otras vías (y llegarán!) con más ganas y más ilusión si cabe.

Felicidades

También me gustaría felicitar a todos los MVPs veteranos que han sido renovados asi como aquellos que entran a formar parte desde este momento. Enhorabuena!

Boxing

[Xamarin] Comparativa de SQLite y Realm

Sword-01-WFIntroducción

Tras la llegada de Realm a Xamarin con la promesa de una opción de base de datos sencilla y sobretodo con mejor rendimiento que el resto de opciones (incluida SQLite, la opción más usada y extendida), la duda es obvia…¿cuál usar?. En este artículo vamos a realizar unas pruebas básicas con las operaciones de CRUD para medir en tiempos el rendimiento exacto de cada opción. Además verificaremos otros aspectos como la facilidad de uso, el nivel de documentación y otros aspectos.

¿Os apuntáis?

Realm

Realm es una base de datos gratuita pensada para aplicaciones móviles, tabletas o wearables siendo una alternativa interesante a SQLite. Llega con el gran objetivo en mente de conseguir un alto rendimiento manteniendo una alta facilidad de uso.

SQLite

SQLite es un motor de base de datos Open Source utilizado en todas las plataformas móviles y adoptado tanto por Apple como Google como Microsoft. El uso de SQLite en aplicaciones móviles es una gran opción ya que:

  • La base de datos es pequeña y fácil de portar.
  • La base de datos se concentra en un pequeño archivo.
  • Implementa la mayor parte del estándar SQL92.

La aplicación para realizar pruebas

En nuestra aplicación necesitamos una única vista donde contaremos con botones para realizar las acciones básicas del CRUD junto a un área donde mostrar los resultados.

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="SqliteVsRealm.Views.MainView"
             Title="SQLite VS Realm">
  <ContentPage.Content>
    <Grid>
      <Grid.RowDefinitions>
        <RowDefinition Height="20"/>
        <RowDefinition Height="50"/>
        <RowDefinition Height="50"/>
        <RowDefinition Height="50"/>
        <RowDefinition Height="20"/>
        <RowDefinition Height="*"/>
      </Grid.RowDefinitions>
      <Button Grid.Row="1"
              Text="Insert 1000 Items"
              Command="{Binding InsertCommand}"/>
      <Button Grid.Row="2"
              Text="Query 1000 Items"
              Command="{Binding QueryCommand}"/>
      <Button Grid.Row="3"
              Text="Delete 1000 Items"
              Command="{Binding DeleteCommand}"/>
      <Editor Grid.Row="5"
              Text="{Binding Log}"
              FontSize="10"/>
    </Grid>
  </ContentPage.Content>
</ContentPage>

El resultado visual:

Nuestra aplicación para realizar pruebas
Nuestra aplicación para realizar pruebas

Enlazamos la View con la ViewModel estableciendo una instancia de la ViewModel a la propiedad BindingContext de la página.

BindingContext = App.Locator.MainViewModel;

En cuanto a cada botón, cada uno de ellos estará enlazado a un comando:

private ICommand _insertCommand;
private ICommand _queryCommand;
private ICommand _deleteCommand;
 
public ICommand InsertCommand
{
     get { return _insertCommand = _insertCommand ?? new DelegateCommand(InsertCommandExecute); }
}
 
public ICommand QueryCommand
{
     get { return _queryCommand = _queryCommand ?? new DelegateCommand(QueryCommandExecute); }
}
 
public ICommand DeleteCommand
{
     get { return _deleteCommand = _deleteCommand ?? new DelegateCommand(DeleteCommandExecute); }
}
 
private async void InsertCommandExecute()
{
 
}
 
private async void QueryCommandExecute()
{
 
}
 
private async void DeleteCommandExecute()
{
 
}

Sencillo, ¿cierto?.

La comparativa

A continuación, vamos a utilizar la aplicación creada para medir rendimiento de ambas opciones además de valorar otros aspectos de peso a la hora de determinar el uso de una u otra opción. Vamos a valorar aspectos como:

  • Facilidad de uso
  • Rendimiento
  • Documentación
  • Mantenimiento

Facilidad de uso

En el arranque de uso de SQLite y Realm tenemos que utilizar paquetes NuGet. En el caso de Realm todo viene empaquetado en un único paquete mientras que en el caso de SQLite utilizaremos varios paquetes (SQLite.Net-PCL, SQLite.Net.Core-PCL y SQLite.Net.Async-PCL).

Tras añadir los paquetes debemos realizar la configuración básica. En el caso de Realm es realmente reducida mientras que en SQLite si que necesitamos un poco de trabajo para especificar la ruta a la base de datos en cada plataforma para poder establecer la conexión.

public interface ISQLite
{
     SQLiteAsyncConnection GetConnection();
}

La implementación de ISQLite nos permite establecer la conexión con la base de datos.

[assembly: Dependency(typeof(SQLiteClient))]
namespace TodoSqlite.Droid.Services
{
    public class SQLiteClient : ISQLite
    {
        public SQLiteAsyncConnection GetConnection()
        {
            var sqliteFilename = "Todo.db3";
            var documentsPath = System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal);
 
            var path = Path.Combine(documentsPath, sqliteFilename);
 
            var platform = new SQLitePlatformAndroid();
 
            var connectionWithLock = new SQLiteConnectionWithLock(
                                         platform,
                                         new SQLiteConnectionString(path, true));
 
            var connection = new SQLiteAsyncConnection(() => connectionWithLock);
 
            return connection;
        }
    }
}

Por último, el trabajo con cada base de datos es realmente similar. Tenemos métodos para cada acción básica, CRUD y tareas de gestión y mantenimiento directamente en C#. Veamos como obtener datos de una tabla.

En Realm:

public IList<TodoItem> GetAll()
{
     var result = _realm.All<TodoItem>().ToList();
 
     return result;
}

En SQLite:

public async Task<IList<TodoItem>> GetAll()
{
     var items = new List<TodoItem>();
     using (await Mutex.LockAsync().ConfigureAwait(false))
     {
          items = await _sqlCon.Table<TodoItem>().ToListAsync().ConfigureAwait(false);
     }
 
     return items;
}

El número de líneas y opciones en este caso estan realmente parejas. Sin embargo, la preparación inicial es practicamente nula en el caso de Realm.

Ganador: Realm

Rendimiento

Llegamos a uno de los puntos fuertes o generalmente de mayor preocupación. Desde Realm se habla del rendimiento como una de sus principales bazas, pero…¿es realmente más rápido?, ¿cuánto más?.

A continuación, vamos a centrarnos en la lógica de cada botón de nuestra interfaz. A la hora de insertar datos:

private async void InsertCommandExecute()
{
     var watch = System.Diagnostics.Stopwatch.StartNew();
     for (int i = 0; i < 1000; i++)
     {
          await _sqliteService.Insert(new SqliteTodoItem
          {
               Id = i + 1,
               Name = string.Format("Name {0}", i + 1),
               Notes = string.Format("Notes {0}", i + 1),
               Done = false
          });
     }
     watch.Stop();
 
     Log += $"SQLite: INSERT 1000 items in {watch.ElapsedMilliseconds} milliseconds\n";
 
     watch.Restart();
     for (int i = 0; i < 1000; i++)
     {
          _realmService.Insert(new RealmTodoItem
          {
               Id = i + 1,
               Name = string.Format("Name {0}", i + 1),
               Notes = string.Format("Notes {0}", i + 1),
               Done = false
          });
      }
      watch.Stop();
 
      Log += $"Realm: INSERT 1000 items in {watch.ElapsedMilliseconds} milliseconds\n";
}

Insertamos 1000 elementos en cada una de las base de datos con los siguientes resultados de media:

  • SQLite: 2400 milisegundos
  • Realm 1800 milisegundos

A la hora de obtener los 1000 registros almacenados:

private async void QueryCommandExecute()
{
     var watch = System.Diagnostics.Stopwatch.StartNew();
     var sqliteResult = await _sqliteService.GetAll();
     watch.Stop();
 
     Log += $"SQLite: QUERY 1000 items in {watch.ElapsedMilliseconds} milliseconds\n";
 
     watch.Restart();
     var realmResult = _realmService.GetAll();
     watch.Stop();
 
     Log += $"Realm: QUERY 1000 items in {watch.ElapsedMilliseconds} milliseconds\n";
}

Los tiempos de media son:

  • SQLite: 50 milisegundos
  • Realm: 2 milisegundos

Por último, llegamos al momenos de eliminar registros:

private async void DeleteCommandExecute()
{
     var sqliteResult = await _sqliteService.GetAll();
     var watch = System.Diagnostics.Stopwatch.StartNew();
     foreach (var item in sqliteResult)
     {
          await _sqliteService.Remove(item);
     }
     watch.Stop();
 
     Log += $"SQLite: DELETE 1000 items in {watch.ElapsedMilliseconds} milliseconds\n";
 
     var realmResult = _realmService.GetAll();
     watch.Restart();
     foreach (var item in realmResult)
     {
          _realmService.Remove(item);
     }
     watch.Stop();
 
     Log += $"Realm: DELETE 1000 items in {watch.ElapsedMilliseconds} milliseconds\n";
}

Los resultados son:

  • SQLite: 2300 milisegundos
  • Realm: 1300 milisegundos
Resultados rendimiento
Resultados rendimiento

Los tiempos en general en Realm son más reducidos que con SQLite, por lo que podemos decir que efectivamente el rendimiento en general es mejor. Optimizando (transacciones, etc) podemos mejorar ligeramente el rendimiento en SQLite dejando los resultados aún más cercanos. Realm hace mejor el trabajo sin nada especial adicional.

Ganador: Realm

Documentación

Realm ha creado una documentación específica para desarrolladores Xamarin bastante completa. Sin embargo, si comparamos con SQLite y debido fundamentalmente a la maduración y uso de cada uno de ellos, SQLite cuenta con mayor documentación. Xamarin cuenta con documentación y ejemplos utilizando SQLite sumado a la comunidad a nivel de artículos y ejemplos, hace que hoy por hoy, el acceso y aprendizaje de cada opción este mejor documentada en SQLite.

Ganador: SQLite

Mantenimiento

Ya hemos revisado aspectos importante relacionados con el arranque en cada uno de ellos, el acceso inicial y facilidad de uso. Sin embargo, creo bastante positivo el análisis del mantenimiento en cada caso en proyectos de peso.

El primer punto de impacto radica en la forma de trabajo con modelos de Realm. Dado que deben heredar de RealmObject y la creación debe realizarse utilizando Realm.CreateObject impidiendo la creación mediante un constructor simple. Igualmente en listas se utiliza RealmList en lugar de POCOs como hace SQLite que sencillamente añade ciertas etiquetas para saber como trabajar correctamente en cada caso. De cara a una correcta gestión de dependencias y testing podemos llegar a tener problemas con Realm necesitando una duplicidad de modelos con sencillos POCOs. Es posible aunque afecta sin duda al punto de facilidad de uso.

El segundo punto importante que puede afectar es la falta de soporte a Async en Realm. Damos por hecho que se añadirá soporte en futuras versiones de Realm, sin embargo, en la versión actual la falta de métodos asíncronos puede afectar en la gestión y mantenimiento de nuestro código.

Ganador: SQLite

Conclusiones

Llegados a este punto, cuatro evaluciones, dos con SQLite como vencedor, otras dos para Realm. ¿Qué usar?. La respuesta en este caso, como en la mayoría de casos, es depende. Realm se muestra como una interesante alternativa diría que perfectamente válida en pequeños proyectos con una cantidad baja de modelos. Su facilidad de arranque, uso y rendimiento lo hacen idóneo. Sin embargo, ante proyectos de peso con un gran mantenimiento la orientación al menos por ahora, seguiría decantándose por SQLite. El apoyo de los principales actores en el ambito móvil y la comunidad, hace que sea la opción más madura, sólida y documentada disponible.

Más información

nat-cropped

[Xamarin.Android] De VSTS a HockeyApp, despliegue continuo

Upload - 02Introducción

La calidad en el software es algo innegociable. Un buen proceso en el desarrollo y gestión del proceso es fundamental para conseguir ese objetivo. Debemos entregar Apps móviles perfectamente adaptadas a cada plataforma, ofreciendo la mejor experiencia de usuario posible pero sobretodo, funcional. Una App funcional debe cubrir y cumplir unos mínimos exigentes de calidad.

Como desarrolladores, somo humanos y el código no estara libre de errores. Sin embargo, el proceso que apliquemos para la detección y corrección a los mismos, es vital.

Realizar un proceso automático en cada nuevo checkin o de manera programada donde:

  • Compilar proyectos.
  • Pasar pruebas unitarias.
  • Pasar pruebas de interfaz de usuario.
  • Incluso publicar automáticamente paquetes y resultados.

Nos permitirá detectar problemas en el código de la forma más prematura posible, pudiendo ofrecer mayor calidad. En este artículos vamos a repasar todo lo necesario para realizar tanto integración continua como entrega continua utilizando VSTS y HockeyApp.

HockeyApp
HockeyApp

Integración Continua

La integración continua consiste en hacer integraciones automáticas de un Proyecto lo más a menudo possible para así detector fallos cuantos antes.

Entendemos por integración la compilación y ejecución de pruebas.

WorkFlow
WorkFlow

El flujo del proceso sería:

  1. El desarrollador trabaja en su equipo de desarrollo subiendo cambios al repositorio de código donde estaría el código además de las pruebas unitarias.
  2. De una forma automática, el repositorio de código envia el mismo al servidor de BUILD.
  3. El servidor de BUILD realizará la compilación de la solución o proyectos, ejecutará pruebas y recopilará la información de los resultados.
  4. Los resultados los obtiene el desarrollador para poder analizarlos y actuar en consecuencia.

“La integración continua no evitará que se produzcan bugs, pero si nos permite encontrarlos y solucionarlos de una forma dramáticamente más fácil”

Martin Flowler

Los beneficios de utilizar integración continua son:

  • Detectar errores con mayor rapidez y antelación. Esto provocará que sea más sencillo de corregir y por lo tanto más barato.
  • Consistencia. O lo que es lo mismo, tener Builds reproducibles. La App funcionará en cualquier entorno con las mismas condiciones. Evitamos la “contaminación de la Build” o lo que es lo mismo, tener la Build con parámetros “a fuego” y condiciones específicas de la máquina de Build.
  • Poder automatizar también la entrega consiguiendo una entrega continua.

La configuración en VSTS

Ahora, utilizando Visual Studio Team Services en combinación con HockeyApp, podemos conseguir tener no solo interación continua, también entrega continua de forma bastante sencilla.

En este artículo nos centraremos en el despliegue de aplicaciones Android realizadas con Xamarin. La configuración en iOS es realmente similar salvando las distancias de la necesidad de utilizar OSX para la compilación. En cuanto a aplicaciones UWP, tenemos ligeros cambios relacionados con el empaquetado de la aplicación.

Veremos otros tipos de aplicaciones en próximos artículos.

Los pasos necesarios

Comenzamos dirigiéndonos a la pestaña Build de VSTS y pulsamos sobre el botón para crear una nueva Build.

Crear nueva Build
Crear nueva Build

Tras pulsar el botón podremos elegir plantilla. VSTS cuenta con plantillas destinadas a diferentes tipos de proyectos con los pasos básicos necesarios en cada caso. En nuestro ejemplo, elegimos la plantilla Xamarin.Android.

Elegimos plantilla
Elegimos plantilla

A continuación, vamos a elegir las opciones básicas de configuración como el repositorio donde se accederá al código, la rama y la máquina a utilizar.

Configuración básica
Configuración básica

Tras añadir la plantilla, varios pasos se crearán por defecto.

Pasos por defecto
Pasos por defecto

El primer paso restaurará los paquetes NuGet utilizados en el proyecto. El siguiente paso lógico sería la compilación del proyecto. Sin embargo, para poder realizar la compilación necesitamos previamente validar la cuenta Xamarin. Tendremos pasos para activar y desactivar la misma.

NOTA: El campo donde añadir la contraseña es un texto plano. Si la configuración es compartida o se desea enseñar, se vería la clave. Para solucionar el problema podemos añadir una variable llamada por ejemplo “XamarinPassword” y utilizarla posteriormente accediendo a ella $(XamarinPassword).

Salvo que tengamos proyecto de pruebas de UI, borraremos el paso de compilación del proyecto de test junto al paso de pasar las pruebas en Xamarin Test Cloud.

Tenemos todo lo necesario para compilar el proyecto Android, pero nos falta lo necesario para compilar la librería portable donde tenemos todo el código común compartido. Para ello vamos a añadir más pasos.

Añadimos un primer paso para restaurar paquetes NuGet.

Restaurar paquetes NuGet
Restaurar paquetes NuGet

A continuación, añadimos un paso encargado de compilar la librería portable.

Compilar la librería portable
Compilar la librería portable

El orden de cada paso debe ser algo similar a la siguiente captura.

Orden de los pasos de la Build
Orden de los pasos de la Build

Cada paso en detalle

Comenzamos restaurando los paquetes NuGet. La única configuración necesaria en este paso será realizar la selección de la solución.

Restaurar paquetes
Restaurar paquetes

NOTA: Este primer paso podría ser innecesario si no usas paquetes NuGet.

En la compilación de la librería portable (PCL) debemos establecer la configuración de compilación $(BuildConfiguration) además de seleccionar el .csproj de la PCL.

Compilar PCL
Compilar PCL

Llegamos al paso de mayor importancia, la compilación del proyecto Android. Debemos asegurarnos de tener establecida la configuración de compilación $(BuildConfiguration) junto al directorio de salida $(build.binariesdirectory)\$(BuildConfiguration).

Compilación Android
Compilación Android

Todo listo. Podemos enviar una Build a cola y obtendremos la compilación del proyecto, la creación del APK y su firma. El siguiente paso sería la subida y distribución utilizando HockeyApp.

La configuración de los triggers

En la pestaña de Triggers podemos configurar cuándo se lanza la Build. Tenemos la opción de establecer una opción de modo de integración continua, cada vez que se añadan cambios a la rama utilizada se lanzaría la Build o bien programar una Build a una fecha y/o hora específica.

Configuración de Triggers
Configuración de Triggers

HockeyApp

Llegamos al segundo paso de peso, la distribución continua con HockeyApp.

HockeyApp es una herramienta que facilita la gestión y distribución de aplicaciones móviles para iOS, Android y Windows. Ofrece además la gestión de analíticas, errores y feedback de parte de los usuarios. Comprada por Microsoft, es la opción idóneo tanto para la gestión de analíticas como para la distribución de betas a clientes.

La configuración en HockeyApp

Tras crear una cuenta si no tenías una ya disponible (es un proceso rápido y gratuito hasta dos aplicaciones) procedemos a crear una nueva aplicación.

Nueva App en HockeyApp
Nueva App en HockeyApp

En ese momento nos pedirá paquete. Sin embargo, subiremos el paquete automáticamente desde VSTS, por lo tanto, elegimos la opción de subida manual. A continuación, establecemos el nombre de la aplicación, la plataforma Android en este caso y el canal de distribución, normalmente beta aunque podría ser otra opcion.

Tras crear la aplicación, tendremos acceso a la información que necesitamos para subir el paquete, su identificador.

Identificador App
Identificador App

Para poder subir automáticamente el paquete de la aplicación a la aplicación creada en HockeyApp, necesitamos de alguna forma enlazar una herramienta con otra. Es un proceso sencillo.

API token
API token

Usaremos ese Token desde VSTS. Primero vamos a instalar la extensión de HockeyApp disponible en el marketplace en VSTS.

Extensión VSTS HockeyApp
Extensión VSTS HockeyApp

Tras instalar la extensión nos dirigimos a la configuración de nuestro VSTS. Concretamente a la pestaña de servicios para añadir un nuevo endpoint.

Endpoint HockeyApp
Endpoint HockeyApp

Añadimos un nombre descriptivo junto al API Token que generamos en HockeyApp y todo listo.

Despliegue continuo

Regresamos a VSTS para configurar el despliegue continuo. Nos dirigimos a la pestaña Release. Creamos una nueva definición de Release.

Nueva Release
Nueva Release

En la pestaña de artefactos hacemos la vinculación con nuestra Build.

Artifacts
Artifacts

Por último, añadimos una tarea del tipo HockeyApp.

HockeyApp
HockeyApp

Configuramos la tarea, seleccionamos la conexión que creamos previamente en la configuración de VSTS, añadimos el identificador de la aplicación creada en HockeyApp y la ruta al binario.

Configuración de la tarea
Configuración de la tarea

Casi lo tenemos todo preparado. Bastará con dirigirnos a la pestaña de Triggers para seleccionar la opción Continuous Deployment. Cada vez que se compile y genere un paquete, subir a HockeyApp (es totalmente personalizable, se puede programar una fecha y hora, lanzar de forma manual, etc.).

Más información

SQLite370.svg

[Xamarin] Utilizando SQLite

Database-WFIntroducción

El trabajo con datos en dispositivos móviles se ha convertido ya en algo común y habitual en el desarrollo de aplicaciones. Existe una gran variedad de tipos de datos y formas de almacenamiento:

  • Archivos de texto. Texto plano o html cacheado en el espacio de almacenamiento aislado de la aplicación.
  • Imágenes. En el espacio de almacenamiento aislado de la aplicación o almacenadas en directorios conocidos del sistema.
  • Archivos serializados. Archivos XML o Json con objetos serializados.
  • Bases de datos. Cuando se requieren datos estructurados, obtener información más compleja con consultas avanzadas entre otro tipo de necesidades, la posibilidad de las bases de datos es la elección idónea.

Las ventajas de utilizar una base de datos son múltiples:

  • Almacenamiento estructurado con eficacia alta.
  • Posibilidad de utilizar consultas y aplicar filtros.
  • Posibilidad de reutilizar conocimientos de base de datos en la gestión de datos en nuestras aplicaciones móviles.

Introducción a SQLite

SQLite es un motor de base de datos Open Source utilizado en todas las plataformas móviles y adoptado tanto por Apple como Google como Microsoft. El uso de SQLite en aplicaciones móviles es una gran opción ya que:

  • La base de datos es pequeña y fácil de portar.
  • La base de datos se concentra en un pequeño archivo.
  • Implementa la mayor parte del estándar SQL92.

Preparando el entorno

Comenzamos creando una aplicación Xamarin.Forms utilizando una librería portable (PCL):

Nueva aplicación Xamarin.Forms
Nueva aplicación Xamarin.Forms

Tras crear la aplicación, añadimos las carpetas básicas para aplicar el patrón MVVM además del paquete NuGet de Unity para la gestión del contenedor de dependencias.

Estructura del proyecto
Estructura del proyecto

Con el proyecto y estructura base creada, vamos a añadir SQLite al proyecto. Realm esta disponible en NuGet. Vamos a añadir en cada proyecto de la solución la última versión disponible del paquete utilizando NuGet. El paquete a utilizar es SQLite.Net PCL, implementación Open Source compatible con librerías portables (PCL) con soporte a .NET y Mono.

SQLite.Net PCL
SQLite.Net PCL

Tras añadir la referencia vamos a crear una interfaz que defina como obtener la conexión con la base de datos y abstraer la funcionalidad específica de cada plataforma. Trabajando con SQLite, el único trabajo específico a implementar en cada plataforma es determinar la ruta a la base de datos y establecer la conexión.

public interface ISQLite
{
     SQLiteAsyncConnection GetConnection();
}

En Android, la implementación de ISQLite nos permite establecer la conexión con la base de datos.

[assembly: Dependency(typeof(SQLiteClient))]
namespace TodoSqlite.Droid.Services
{
    public class SQLiteClient : ISQLite
    {
        public SQLiteAsyncConnection GetConnection()
        {
            var sqliteFilename = "Todo.db3";
            var documentsPath = System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal);

            var path = Path.Combine(documentsPath, sqliteFilename);

            var platform = new SQLitePlatformAndroid();

            var connectionWithLock = new SQLiteConnectionWithLock(
                                         platform,
                                         new SQLiteConnectionString(path, true));

            var connection = new SQLiteAsyncConnection(() => connectionWithLock);

            return connection;
        }
    }

}

NOTA: Utilizamos el atributo assembly:Dependency para poder realizar la resolución de la implementación con DependencyService.

En iOS, la implementación de ISQLite nos permite establecer la conexión con la base de datos. El archivo de la base de datos lo situamos dentro de la carpeta Library dentro del espacio de almacenamiento de la aplicación.

[assembly: Dependency(typeof(SQLiteClient))]
namespace TodoSqlite.iOS.Services
{
    public class SQLiteClient : ISQLite
    {
        public SQLiteAsyncConnection GetConnection()
        {
            var sqliteFilename = "Todo.db3";
            var documentsPath = Environment.GetFolderPath(Environment.SpecialFolder.Personal);
            var libraryPath = Path.Combine(documentsPath, "..", "Library");
            var path = Path.Combine(libraryPath, sqliteFilename);

            var platform = new SQLitePlatformIOS();

            var connectionWithLock = new SQLiteConnectionWithLock(
                                          platform,
                                          new SQLiteConnectionString(path, true));

            var connection = new SQLiteAsyncConnection(() => connectionWithLock);

            return connection;
        }
    }
}

Todo listo para comenzar!

La definición de modelos

En nuestra aplicación, trabajaremos con elementos del listado ToDo, una única entidad sencilla.

public class TodoItem
{
     [PrimaryKey, AutoIncrement]
     public int Id { get; set; }
     public string Name { get; set; }
     public string Notes { get; set; }
     public bool Done { get; set; }
}

La gestión de campos especiales o relacionados las gestionamos mediante el uso de etiquetas. En nuestro ejemplo establecemos el campo Id como clave primaria gracias a la etiqueta PrimaryKey y además que autoincremente con el uso de AutoIncrement.

La interfaz de usuario

En nuestra aplicación contaremos con dos vistas, un listado de tareas y una vista de detalles para crear, editar o eliminar una tarea específica.

Comenzamos definiendo la vista principal. Tendremos un listado de tareas:

<ListView 
    ItemsSource="{Binding Items}" 
    SelectedItem="{Binding SelectedItem, Mode=TwoWay}">
    <ListView.ItemTemplate>
      <DataTemplate>
        <ViewCell>
          <ViewCell.View>
            <StackLayout 
              Padding="20,0,20,0"                       
              Orientation="Horizontal"       
              HorizontalOptions="FillAndExpand">
              <Label Text="{Binding Name}"
                     VerticalTextAlignment="Center"
                     HorizontalOptions="StartAndExpand" />
              <Image Source="check.png"
                     HorizontalOptions="End"
                     IsVisible="{Binding Done}"/>
            </StackLayout>
          </ViewCell.View>
        </ViewCell>
      </DataTemplate>
     </ListView.ItemTemplate>
</ListView>

A parte de definir como se visualizará cada elemento de la lista definiendo el DataTemplate establecemos la fuente de información, propiedad ItemsSource enlazada a propiedad de la ViewModel que obtendrá los datos de la base de datos.

Además del listado, debemos añadir en nuestra interfaz una forma de poder insertar nuevas tareas. Para ello, una de las opciones más habituales e idóneas es utilizar una Toolbar.

<ContentPage.ToolbarItems>
    <ToolbarItem Name="Add" 
                 Command="{Binding AddCommand}"  >
      <ToolbarItem.Icon>
        <OnPlatform x:TypeArguments="FileImageSource"
                    Android="plus"
                    WinPhone="Assets/add.png" />
      </ToolbarItem.Icon>
    </ToolbarItem>
</ContentPage.ToolbarItems>

Añadimos un ToolbarItem que permitirá añadir elementos.

La clase Device es muy importante en Xamarin.Forms ya que nos permite acceder a una serie de propiedades y métodos con el objetivo de personalizar la aplicación según dispositivo y plataforma. Además de permitirnos detectar el tipo de dispositivo, podemos detectar la plataforma gracias a la enumeración Device.OS o personalizar elementos de la interfaz gracias al método Device.OnPlatform entre otras opciones. En nuestro ejemplo, personalizamos el icono de añadir en base a la plataforma.

Nuestra interfaz:

Vista principal
Vista principal

Enlazamos la View con la ViewModel estableciendo una instancia de la ViewModel a la propiedad BindingContext de la página.

BindingContext = App.Locator.TodoItemViewModel;

En la ViewModel contaremos con una propiedad pública para definir el listado de tareas, además de la tarea seleccionada (utilizada para la navegación):

private ObservableCollection<TodoItem> _items;
private TodoItem _selectedItem;

public ObservableCollection<TodoItem> Items
{
     get { return _items; }
     set
     {
          _items = value;
          RaisePropertyChanged();
     }
}

public TodoItem SelectedItem
{
    get { return _selectedItem; }
    set
    {
          _selectedItem = value;
    }
}

Añadimos elementos con un comando disponible en la ViewModel.

private ICommand _addCommand;

public ICommand AddCommand
{
     get { return _addCommand = _addCommand ?? new DelegateCommand(AddCommandExecute); }
}

private void AddCommandExecute()
{

}     

Al pulsar y lanzar el comando, navegaremos a la vista de detalles.

_navigationService.NavigateTo<TodoItemViewModel>(_selectedItem);

Si creamos un nuevo elemento pasaremos como parámetro una nueva entidad de TodoItem, en caso de seleccionar una existente, pasaremos el seleccionado disponible en la propiedad SelectedItem.

Definimos la interfaz de la vista de detalles:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="TodoRealm.Views.TodoItemView"
             Title="{Binding Name}">
  <StackLayout 
    VerticalOptions="StartAndExpand" 
    Padding="20">
    <Label 
      Text="Name" />
    <Entry 
      Text="{Binding Name}"/>
    <Label 
      Text="Notes" />
    <Entry 
      Text="{Binding Notes}"/>
    <Label 
      Text="Done" />
    <Switch 
      x:Name="DoneEntry" 
      IsToggled="{Binding Done, Mode=TwoWay}"/>
    <Button 
      Text="Save"
      Command="{Binding SaveCommand}"/>
    <Button 
      Text="Delete"
      Command="{Binding DeleteCommand}"/>
    <Button 
      Text="Cancel"
      Command="{Binding CancelCommand}"/>
  </StackLayout>
</ContentPage>

Añadimos cajas de texto para poder editar toda la información de una tarea además de botones para poder guardar, borrar o cancelar y navegar atrás.

El resultado:

Detalle
Detalle

Para enlazar la información de un elemento seleccionado, debemos capturar la información enviada en la navegación. Creamos una propiedad pública para enlazar con la UI de la tarea:

private TodoItem _item;

public TodoItem Item
{
     get { return _item; }
     set { _item = value; }
}

¿Cómo capturamos el elemento seleccionado en la navegación?. Utilizamos el método OnAppearing para capturar el parámetro NavigationContext.

public override void OnAppearing(object navigationContext)
{
     var todoItem = navigationContext as TodoItem;

     if (todoItem != null)
     {
          Item = todoItem;
     }

     base.OnAppearing(navigationContext);
}

En cuanto a cada botón, cada uno de ellos estará enlazado a un comando:

private ICommand _saveCommand;
private ICommand _deleteCommand;
private ICommand _cancelCommand;

public ICommand SaveCommand
{
     get { return _saveCommand = _saveCommand ?? new DelegateCommand(SaveCommandExecute); }
}

public ICommand DeleteCommand
{
     get { return _deleteCommand = _deleteCommand ?? new DelegateCommand(DeleteCommandExecute); }
}

public ICommand CancelCommand
{
     get { return _cancelCommand = _cancelCommand ?? new DelegateCommand(CancelCommandExecute); }
}

private void SaveCommandExecute()
{

}

private void DeleteCommandExecute()
{

}

private void CancelCommandExecute()
{

}

Trabajando con SQLite

Para trabajar con la base de datos utilizaremos DependencyService para obtener la implementación de ISQLite y obtener una conexión.

private SQLiteAsyncConnection _sqlCon;
_sqlCon = DependencyService.Get<ISQLite>().GetConnection();

Para almacenar nuestras tareas, comenzamos creando la tabla necesaria en la base de datos.

public async void CreateDatabaseAsync()
{
     using (await Mutex.LockAsync().ConfigureAwait(false))
     {
          await _sqlCon.CreateTableAsync<TodoItem>().ConfigureAwait(false);
     }
}

Utilizamos el método CreateTableAsync<>() para crear la tabla.

Continuamos con las operaciones básicas de CRUD. Para obtener la información almacenada en una tabla podemos acceder a la tabla y obtener el listado utilizando el método ToListAsync.

public async Task<IList<TodoItem>> GetAll()
{
     var items = new List<TodoItem>();
     using (await Mutex.LockAsync().ConfigureAwait(false))
     {
          items = await _sqlCon.Table<TodoItem>().ToListAsync().ConfigureAwait(false);
     }

     return items;
}

NOTA: Podemos realizar consultar SQL utilizando el método QueryAync.

A la hora de insertar, verificamos si estamos ante un registro existente o no, para realizar el registro de un nuevo elemento o actualizar uno existente con los métodos InsertAsync o UpdateAsync respectivamente.

public async Task Insert(TodoItem item)
{
     using (await Mutex.LockAsync().ConfigureAwait(false))
     {
          var existingTodoItem = await _sqlCon.Table<TodoItem>()
                        .Where(x => x.Id == item.Id)
                        .FirstOrDefaultAsync();

          if (existingTodoItem == null)
          {
               await _sqlCon.InsertAsync(item).ConfigureAwait(false);
          }
          else
          {
               item.Id = existingTodoItem.Id;
               await _sqlCon.UpdateAsync(item).ConfigureAwait(false);
          }
     }
}

Eliminar es una acción sencilla realizada con el método DeleteAsync.

public async Task Remove(TodoItem item)
{
     await _sqlCon.DeleteAsync(item);
}

Tenéis el código fuente disponible e GitHub:

Ver GitHub

Recordad que podéis dejar cualquier comentario, sugerencia o duda en los comentarios.

Más información

hololens-vid-0

[Material CartujaDotNet] Bienvenidos al desarrollo holográfico!

HololensEl evento

Desde que Hololens apareció en nuestro horizonte, aparición a aparición, demo tras demo ibamos viendo posibilidades y un nuevo abanico de opciones abiertas de cara al desarrollo. Más tarde conocimos características, se liberó el SDK y comenzaron las primeras hornadas de dispositivos llegando a desarrolladores.

Aprovechando la llegada de los primeros dispositivos, la disponibilidad de SDK y herramientas además de opciones como Wave Engine, desde CartujaDotNet decidimos montar un evento, en esta ocasión online para que pudiesen participar la mayor cantidad de personas posibles.

Hololens
Hololens

El material

El objetivo de la sesión era realizar una introducción de la forma más completa y amena posible a Hololens, su desarrollo y posibilidades.

Abordamos aspectos como:

  • VR vs AR, diferencias.
  • ¿Qué son las Hololens?. Características técnicas y posibilidades.
  • Repaso a formas de interacción.
  • Un vistazo al funcionamiento y aplicacions fundamentales disponibles.
  • Desarrollo. Herramientas y opciones.

Utilizamos una presentación como guía.

El video del evento ha quedado grabado y esta disponible para su visionado en cualquier momento.

En cuanto a las demos técnicas vistas, las tenéis disponible en GitHub:

Ver GitHub

Esperamos que el formato guste y poder continuar con sesiones profundizando en aspectos como el desarrollo paso a paso. Cualquier comentario recordar que lo podéis dejar en los comentarios de la entrada.

Más información

RealmXamarinTNW-796x398

[Xamarin] Utilizando Realm

RealmSquareIntroducción

El trabajo con datos en dispositivos móviles se ha convertido ya en algo común y habitual en el desarrollo de aplicaciones. Existe una gran variedad de tipos de datos y formas de almacenamiento:

  • Archivos de texto. Texto plano o html cacheado en el espacio de almacenamiento aislado de la aplicación.
  • Imágenes. En el espacio de almacenamiento aislado de la aplicación o almacenadas en directorios conocidos del sistema.
  • Archivos serializados. Archivos XML o Json con objetos serializados.
  • Bases de datos. Cuando se requieren datos estructurados, obtener información más compleja con consultas avanzadas entre otro tipo de necesidades, la posibilidad de las bases de datos es la elección idónea.

Realm

SQLite, engine de base datos open source es la opción más extendida y usada en el desarrollo de aplicaciones móviles. Recientemente, se ha añadido soporte a Xamarin de Realm.

Realm es una base de datos gratuita pensada para aplicaciones móviles, tabletas o wearables siendo una alternativa interesante a SQLite. Con el gran objetivo en mente de conseguir un alto rendimiento manteniendo una alta facilidad de uso, a lo largo de este artículo, crearemos una aplicación real con Xamarin.Forms y Realm donde poder crear, editar y gestionar un listado To Do.

Arrancando el proyecto

Comenzamos creando una aplicación Xamarin.Forms utilizando una librería portable (PCL):

Nueva aplicación Xamarin.Forms
Nueva aplicación Xamarin.Forms

Tras crear la aplicación, añadimos las carpetas básicas para aplicar el patrón MVVM además del paquete NuGet de Unity para la gestión del contenedor de dependencias.

Estructura del proyecto
Estructura del proyecto

Añadiendo Realm

Con el proyecto y estructura base creada, vamos a añadir Realm al proyecto. Realm esta disponible en NuGet. Vamos a añadir en cada proyecto de la solución la última versión disponible del paquete utilizando NuGet.

Hacemos clic derecho sobre la solución, Administrar paquetes NuGet para la solución…

NuGet PackagesBuscamos por la pabara “Realm” e instalamos el paquete en todos los proyectos.

Realm
Realm

La definición de modelos

En nuestra aplicación, trabajaremos con elementos del listado ToDo, una única entidad sencilla.

public class TodoItem : RealmObject
{
     public int Id { get; set; }
     public string Name { get; set; }
     public string Notes { get; set; }
     public bool Done { get; set; }
}

Los modelos en Realm son clases tradicionales con propiedades. La clase debe heredar de RealmObject para definir los modelos de datos de Realm. El trabajo con el modelo es como con cualquier modelo tradicional. Se puede añadir variables y lógica personalizada, la única restricción es que solo podremos almacenar la información de las propiedades con get y set.

A pesar de que en nuestro ejemplo no tengamos la necesidad de establecer relaciones entre múltiples modelos, podemos realizarlas de forma sumamente sencilla mediante el uso de RealmList.

En cuanto a los tipos soportados, podemos usar todos los tipos básicos (char string, int, float, double, long) además de DateTimeOffset.

La interfaz de usuario

En nuestra aplicación contaremos con dos vistas, un listado de tareas y una vista de detalles para crear, editar o eliminar una tarea específica.

Vistas
Vistas

Comenzamos definiendo la vista principal. Tendremos un listado de tareas:

<ListView 
    ItemsSource="{Binding Items}"
    SelectedItem="{Binding SelectedItem, Mode=TwoWay}">
    <ListView.ItemTemplate>
      <DataTemplate>
        <ViewCell>
          <ViewCell.View>
            <StackLayout 
              Padding="20,0,20,0"                       
              Orientation="Horizontal"       
              HorizontalOptions="FillAndExpand">
              <Label Text="{Binding Name}"
                     VerticalTextAlignment="Center"
                     HorizontalOptions="StartAndExpand" />
              <Image Source="check.png"
                     HorizontalOptions="End"
                     IsVisible="{Binding Done}"/>
            </StackLayout>
          </ViewCell.View>
        </ViewCell>
      </DataTemplate>
     </ListView.ItemTemplate>
</ListView>

A parte de definir como se visualizará cada elemento de la lista definiendo el DataTemplate establecemos la fuente de información, propiedad ItemsSource enlazada a propiedad de la ViewModel que obtendrá los datos de la base de datos.

Enlazamos la View con la ViewModel estableciendo una instancia de la ViewModel a la propiedad BindingContext de la página.

BindingContext = App.Locator.TodoItemViewModel;

En la ViewModel contaremos con una propiedad pública para definir el listado de tareas, además de la tarea seleccionada (utilizada para la navegación):

private ObservableCollection<TodoItem> _items;
private TodoItem _selectedItem;
 
public ObservableCollection<TodoItem> Items
{
     get { return _items; }
     set
     {
          _items = value;
          RaisePropertyChanged();
     }
}
 
public TodoItem SelectedItem
{
    get { return _selectedItem; }
    set
    {
          _selectedItem = value;
    }
}

Además del listado, debemos añadir en nuestra interfaz una forma de poder insertar nuevas tareas. Para ello, una de las opciones más habituales e idóneas es utilizar una Toolbar.

<ContentPage.ToolbarItems>
    <ToolbarItem Name="Add"
                 Command="{Binding AddCommand}"  >
      <ToolbarItem.Icon>
        <OnPlatform x:TypeArguments="FileImageSource"
                    Android="plus"
                    WinPhone="Assets/add.png" />
      </ToolbarItem.Icon>
    </ToolbarItem>
</ContentPage.ToolbarItems>

Añadimos un ToolbarItem que permitirá añadir elementos.

La clase Device es muy importante en Xamarin.Forms ya que nos permite acceder a una serie de propiedades y métodos con el objetivo de personalizar la aplicación según dispositivo y plataforma. Además de permitirnos detectar el tipo de dispositivo, podemos detectar la plataforma gracias a la enumeración Device.OS o personalizar elementos de la interfaz gracias al método Device.OnPlatform entre otras opciones. En nuestro ejemplo, personalizamos el icono de añadir en base a la plataforma.

Nuestra interfaz:

Vista principal
Vista principal

Añadimos elementos con un comando disponible en la ViewModel.

private ICommand _addCommand;
 
public ICommand AddCommand
{
     get { return _addCommand = _addCommand ?? new DelegateCommand(AddCommandExecute); }
}
 
private void AddCommandExecute()
{
 
}  

Al pulsar y lanzar el comando, navegaremos a la vista de detalles.

_navigationService.NavigateTo<TodoItemViewModel>(_selectedItem);

Si creamos un nuevo elemento pasaremos como parámetro una nueva entidad de TodoItem, en caso de seleccionar una existente, pasaremos el seleccionado disponible en la propiedad SelectedItem.

Definimos la interfaz de la vista de detalles:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="TodoRealm.Views.TodoItemView"
             Title="{Binding Name}">
  <StackLayout 
    VerticalOptions="StartAndExpand"
    Padding="20">
    <Label 
      Text="Name" />
    <Entry 
      Text="{Binding Name}"/>
    <Label 
      Text="Notes" />
    <Entry 
      Text="{Binding Notes}"/>
    <Label 
      Text="Done" />
    <Switch 
      x:Name="DoneEntry"
      IsToggled="{Binding Done, Mode=TwoWay}"/>
    <Button 
      Text="Save"
      Command="{Binding SaveCommand}"/>
    <Button 
      Text="Delete"
      Command="{Binding DeleteCommand}"/>
    <Button 
      Text="Cancel"
      Command="{Binding CancelCommand}"/>
  </StackLayout>
</ContentPage>

Añadimos cajas de texto para poder editar toda la información de una tarea además de botones para poder guardar, borrar o cancelar y navegar atrás.

El resultado:

Detalle
Detalle

Para enlazar la información de un elemento seleccionado, debemos capturar la información enviada en la navegación. Creamos una propiedad pública para enlazar con la UI de la tarea:

private TodoItem _item;
 
public TodoItem Item
{
     get { return _item; }
     set { _item = value; }
}

¿Cómo capturamos el elemento seleccionado en la navegación?. Utilizamos el método OnAppearing para capturar el parámetro NavigationContext.

public override void OnAppearing(object navigationContext)
{
     var todoItem = navigationContext as TodoItem;
 
     if (todoItem != null)
     {
          Item = todoItem;
     }
 
     base.OnAppearing(navigationContext);
}

En cuanto a cada botón, cada uno de ellos estará enlazado a un comando:

private ICommand _saveCommand;
private ICommand _deleteCommand;
private ICommand _cancelCommand;
 
public ICommand SaveCommand
{
     get { return _saveCommand = _saveCommand ?? new DelegateCommand(SaveCommandExecute); }
}
 
public ICommand DeleteCommand
{
     get { return _deleteCommand = _deleteCommand ?? new DelegateCommand(DeleteCommandExecute); }
}
 
public ICommand CancelCommand
{
     get { return _cancelCommand = _cancelCommand ?? new DelegateCommand(CancelCommandExecute); }
}
 
private void SaveCommandExecute()
{
 
}
 
private void DeleteCommandExecute()
{
 
}
 
private void CancelCommandExecute()
{
 
}

Trabajando con Realm

Llegados a este punto, tenemos la estructura, vistas y lógica básica necesaria de toda la aplicación. Sin embargo, aún nos falta la parte clave de la aplicación, el trabajo con Realm.

Trabajaremos con la base de datos en Realm definiendo una instancia de tipo Realm utilizando el método Realm.GetInstance(), no es más que un objeto que encapsula la base de datos.

private Realms.Realm _realm;
_realm = Realms.Realm.GetInstance();

Para hacer consultas tenemos disponible el método Realm.All<>(). Con Realm podemos utilizar LINQ para realizar consultas de la información (filtros, ordenaciones, etc.).

public IList<TodoItem> GetAll()
{
     var result = _realm.All<TodoItem>().ToList();
 
     return result;
}

De esta forma obtenemos el listado de tareas almacenadas en la base de datos para enlazar con el listado de la vista principal.

RealmObject permite auto refrescar la información, es decir, un cambio en una propiedad de un objeto tiene un efecto inmediato en cualquier otra instancia haciendo referencia.

Cualquier cambio sobre un objeto (insertar, actualizar o eliminar) debe realizarse usando una transacción

Tenemos dos formas sencillas de crear transacciones, utilizando Realm.BeginWrite() y con Realm.Write(). Ambos devueven una Transaction e implementan el patrón Dispose.

public void Insert(TodoItem item)
{
     var items = _realm.All<TodoItem>().ToList();
     var existTodoItem = items.FirstOrDefault(i => i.Id == item.Id);
             
     if (existTodoItem == null)
     {
          _realm.Write(() =>
          {
               var todoItem = _realm.CreateObject<TodoItem>();
               todoItem.Id = items.Count + 1;
               todoItem.Name = item.Name;
               todoItem.Notes = item.Notes;
               todoItem.Done = item.Done;
          });
     }
     else
     {
          using (var trans = _realm.BeginWrite())
          {
               existTodoItem.Name = item.Name;
               existTodoItem.Notes = item.Notes;
               existTodoItem.Done = item.Done;
 
               trans.Commit();
          }
     }
}

NOTA: En el caso de utilizar BeginWrite es importante tener en cuenta que debemos hacer Commit de la transacción.

Eliminar es una acción muy sencilla, basta con seleccionar el elemento a eliminar, crear una transacción y eliminar utilizando el métoto Remove.

public void Remove(TodoItem item)
{
     var items = _realm.All<TodoItem>().ToList();
     var existTodoItem = items.FirstOrDefault(i => i.Id == item.Id);
 
     if (existTodoItem != null)
     {
          using (var trans = _realm.BeginWrite())
          {
               _realm.Remove(existTodoItem);
               trans.Commit();
          }
     }
}

NOTA: También se pueden eliminar todos los objetos almacenados e Realm.

Tenéis el código fuente disponible e GitHub:

Ver GitHub

A tener en cuenta

La facilidad de uso, potencia y opciones de Realm hace que ya sea una opción interesante en el uso de datos estructurados en nuestra aplicación. Sin embargo, su nivel ed maduración es inferior al de SQLite. Contamos con algunas limitaciones como:

  • No se puede borrar en cascada.
  • Consultas asíncronas.
  • Notificaciones en colecciones.
  • Migraciones.
  • Etc.

One more thing

Exactamente el mismo ejemplo, con las mismas entidades, vistas y lógica realizo con SQLite lo tenéis también disponible en GitHub:

Ver GitHub

Recordad que podéis dejar cualquier comentario, sugerencia o duda en los comentarios.

Más información

[Material SVQXDG] Xamarin University Spring Fling 2016

600_449053644.jpegEl evento

El pasado 24 de Mayo, desde el grupo SVQXDG, grupo de desarrolladores Xamarin de Sevilla, organizábamos el Xamarin University Spring Fling 16.

Xamarin University Sprint Fling 2016
Xamarin University Sprint Fling 2016

Evento celebrado a nivel global por las comunidades Xamarin a lo largo de todo el mundo. En SVQXDG la idea era reunir estudiantes de Xamarin University, repasar las últimas novedades presentadas en el Xamarin Evolve y compartir experiencias y networking entre todos como viene siendo habitual además de repartir algun que otro regalo gracias al equipo de Xamarin University.

El material

Hicimos un breve repaso de que es, funcionamiento y estado actual de Xamarin University. Para llegar al plato fuerte donde de la forma más práctica posible vimos las grandes novedades del Evolve:

  • Editor visual de Xamarin.Forms
  • Simulador remoto de iOS en Windows
  • Xamarin.Forms Themes y DataPages
  • Uso controles nativos en Xamarin.Forms
  • Etc

En cuanto a las demos técnicas realizadas, las tenéis disponible en GitHub:

Ver GitHub

Quisiera terminar añadiendo algunos agradecimientos a Josué Yeray y Marcos Cobeña por participar junto a un servidor en las sesiones y por supuesto, muchas gracias a todos los asistentes.

Más información