[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:

[sourcecode language=»vb»]
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";
}
[/sourcecode]

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:

[sourcecode language=»vb»]
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";
}
[/sourcecode]

Los tiempos de media son:

  • SQLite: 50 milisegundos
  • Realm: 2 milisegundos

Por último, llegamos al momenos de eliminar registros:

[sourcecode language=»vb»]
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";
}
[/sourcecode]

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

[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

[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

[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

[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