jueves, 15 de octubre de 2015

Usando SQLite .NET multiplataforma en Xamarin Android, Xamarin IOS, Windows Phone y Windows Store (Parte 1 WP)

En esta serie de publicaciones voy a mostrar cómo crear una aplicación multiplataforma que permita crear bases de datos locales SQLite usando SQLite.Net para las plataformas:
  • Windows Phone 8.1 (Parte 1)
  • Windows Store (Parte 2)
  • IOS y Android (Parte 3)
Para empezar Vamos a generar una solución en Visual Studio la cual contenga los 4 proyectos que se van a usar a lo largo de este tutorial. Para este ejemplo se va a compartir el código mediante un proyecto Core que va a ser enlazado a los diferentes proyectos de la aplicación, en un futuro escribiré una publicación para mostrar el uso de SQLite usando una Librería de clases portable.
  • Librería de clases de C# (SQL.Core)
  • Proyecto de Windows Phone (SQL.WP)
  • Proyecto de Windows Store (SQL.WS)
  • Proyecto de Xamarin Android (SQL.Droid)
  • Proyecto de Xamarin IOS (SQL.IOS)

Creando el proyecto Core

1CreandoCore

Creando el Proyecto WP

2CreandoWP

Adicionalmente debemos instalar una extensión que nos permita usar de manera correcta SQLite en WP y con esto evitar un error “File not Found” durante la ejecución de nuestro programa. Esto lo hacemos desde la opción “Extensiones y actualizaciones” del menú herramientas de Visual Studio.
2.0.1Extensiones
En la ventana que aparece en la sección de extensiones buscamos “SQLite for Windows Phone” y lo instalamos
image
En nuestro proyecto agregamos la referencia a la extensión recién instalada, la cual se encuentra en la sección “Windows Phone 8.1/Extensiones” del menú lateral de la ventana del administrador de referencias
image
Como paso adicional agregaremos el paquete Nuget con las dependencias de SQLite .Net para WP8, el cual nos va a ser de utilidad en el futuro además de que es importante hacerlo en este momento antes de agregar SQLite Net a nuestro proyecto Core.
Abrimos el administrador de paquetes Nuget, con clic derecho en el proyecto que acabamos de crear seleccionando la opción “Manage Nuget Packages…”
Nueva imagen
En la ventana del administrador de paquetes, buscamos el paquete “sql-net-wp8” y lo instalamos
image
Al terminar la instalación tendremos una referencia “sqlite” y una referencia a “SQLite for Windows Phone” en el proyecto
image
Finalmente debemos agregar al proyecto una constante de compilación condicional llamado “USE_WP8_NATIVE_SQLITE”, esto se hace desde las propiedades del proyecto en la opción “Compliar” del menú lateral 
image
 

Enlazando el proyecto Core con los proyectos de cada plataforma

Para lograr esto usaremos la funcionalidad de Project Linker el cual nos permite que todos los cambios que se realicen a alguna clase del proyecto Core se vean reflejados en todas las plataformas, en el caso de un proyecto enlazado a la hora de compilar, el código se toma como si fuera parte de la misma aplicación y no como una librería externa por lo cual hay que tener en cuenta algunas cosas:
  • Las clases siempre hay que crearlas desde el proyecto Core para que se propague a todos los proyectos enlazados.
  • Las clases se pueden editar desde cualquier proyecto sin embargo es importante cuidar no agregar referencias a clases propias de la plataforma, esto haría que la clase no compilara en las demás plataformas. El proyecto Core solo debe llevar código que se va a compartir.
  • Si se quieren usar cosas específicas de la plataforma en alguna clase del proyecto Core deben usarse condiciones de compilación para que esta funcione de manera correcta en todas las plataformas.
Para enlazar un proyecto hacemos clic derecho en cada uno de los proyectos que van a consumir Core y seleccionamos “Add project link”
clip_image012
En la pantalla que aparece para seleccionar el proyecto origen seleccionamos Core y pulsamos Ok
clip_image014
Con esto ya podemos ver que todos los proyectos van a tener el contenido de Core enlazado con un pequeño indicador antes del nombre de la clase (En el ejemplo CRUDManager.cs)
clip_image016 

 

Generando la clase CRUD para nuestra aplicación

EL primer paso va a ser generar una interfaz donde indiquemos que la clase que la implemente debe contener una propiedad que sea la llave de cada registro.
En este caso le llamare IKeyObject a la interfaz y lo único que va a contener es una propiedad Key
public interface IKeyObject
    {
        string Key { get; set; }
    }




























Después de generar la interfaz vamos a generar una clase que sea un ejemplo de un objeto que deseemos guardar en una base de datos local, en este caso creare una clase que sirva para guardar un registro de contactos y sus datos.
La clase contacto (Contact) tendrá las siguientes propiedades y va a implementar la interfaz que generamos anteriormente

  • La propiedad llave que debe llevar al implementar a la interfaz IKeyObject (Key) en este ejemplo usaremos el mismo valor de la propiedad nombre
  • El nombre del contacto (Name)
  • El correo electrónico (Mail)
  • El teléfono (PhoneNumber)
  • La edad (Age)
public class Contact:IKeyObject
    {

        public string Key { get; set; }

        private string name;
        public string Name
        {
            get { return name; }
            set { name = value; }
        }

        private int age;
        public int Age
        {
            get { return age; }
            set { age = value; }
        }

        private string mail;
        public string Mail
        {
            get { return mail; }
            set { mail = value; }
        }

        private string phoneNumber;

        public string PhoneNumber
        {
            get { return phoneNumber; }
            set { phoneNumber = value; }
        }

        public Contact(string name, int age, string phoneNumber, string mail)
        {
            this.name = name;
            this.age = age;
            this.phoneNumber = phoneNumber;
            this.mail = mail;
            this.Key = name;

        }


        
    }



Lo primero que vamos a realizar es agregar SQLite .Net a nuestro proyecto Core para poder acceder a los atributos que nos permitan decorar nuestra clase Contact y posteriormente usaremos para generar la lógica de la base de datos.
Para esto vamos a agregar al proyecto Core, de nueva cuenta usando el administrador de paquetes Nuget, el paquete “sqlite-net”.
image
Al finalizar la instalación nuestro proyecto Core va a tener dos archivos nuevos “SQLite.cs” y “SQLiteAsync.cs” los cuales están enlazados en todos los demás proyectos
image
como siguiente paso agregamos la referencia a SQLite a nuestra clase Contact agregando “using SQLite;” en la parte superior de nuestra clase junto a los demás using y finalmente decoramos la propiedad Key con el atributo PrimaryKey de SQLite para indicar que es la llave primaria de la tabla que se creara.
public class Contact:IKeyObject
    {
        [PrimaryKey]
        public string Key { get; set; }

        private string name;
        public string Name
        {
            get { return name; }
            set { name = value; }
        }

        private int age;
        public int Age
        {
            get { return age; }
            set { age = value; }
        }

        private string mail;
        public string Mail
        {
            get { return mail; }
            set { mail = value; }
        }

        private string phoneNumber;

        public string PhoneNumber
        {
            get { return phoneNumber; }
            set { phoneNumber = value; }
        }

        public Contact(string name, int age, string phoneNumber, string mail)
        {
            this.name = name;
            this.age = age;
            this.phoneNumber = phoneNumber;
            this.mail = mail;
            this.Key = name;

        }


        
    }

Para generar la clase encargada de las altas, consultas, bajas y actualizaciones (Create, Read, Update y Delete) de nuestra base de datos SQLite podemos utilizar el código creado por Xamarin para su aplicación de ejemplo Tasky Pro (publicado en GitHub https://github.com/xamarin/mobile-samples/tree/master/TaskyPro) y adaptarlo a nuestras necesidades.
En este caso la clase contara con lo métodos mas básicos para el manejo de la tabla, los métodos son:

  • SaveItem con el cual se puede guardar un objeto nuevo o actualizar uno existente
  • DeleteItem el cual servirá para eliminar elementos de la base de datos.
  • GetAllItems que devuelve todos los elementos almacenados.
  • GetItem que devuelve el elemento con la llave introducida.
using SQLite;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace SQL.Core.Core
{
    public class CRUDManager : SQLiteConnection
    {
        public CRUDManager(string path)
            : base(path)
        {
            // create the tables
            CreateTable<Contact>();
        }

        public void SaveValue<T>(T value) where T : IKeyObject, new()
        {


            var all = (from entry in Table<T>().AsEnumerable<T>()
                       where entry.Key == value.Key
                       select entry).ToList();
            if (all.Count == 0)
                Insert(value);
            else
                Update(value);

        }


        public void DeleteValue<T>(T value) where T : IKeyObject, new()
        {

            var all = (from entry in Table<T>().AsEnumerable<T>()
                       where entry.Key == value.Key
                       select entry).ToList();
            if (all.Count == 1)
                Delete(value);
            else
                throw new Exception("The db doesn't contain a entry with the specified key");


        }


        public List<TSource> GetAllItems<TSource>() where TSource : IKeyObject, new()
        {

            return Table<TSource>().AsEnumerable<TSource>().ToList();

        }


        public TSource GetItem<TSource>(string key) where TSource : IKeyObject, new()
        {

            var result = (from entry in Table<TSource>().AsEnumerable<TSource>()
                          where entry.Key == key
                          select entry).FirstOrDefault();
            return result;

        }



    }
}

Con esto finalizamos la lógica del proyecto Core la cual como mencione anteriormente es la lógica que será compartida entre todas las aplicaciones.


Utilizando SQLite en el proyecto de Windows Phone

Realizado todo lo anterior y gracias a que anteriormente ya habiamos agregado las dependencias de SQlite .Net a este proyecto, solo nos hace falta hacer uso de nuestras clases generadas en el proyecto Core.Primero generamos una interfaz de usuario muy simple, solo con los campos de entrada de datos y un par de botones.
image
<phone:PhoneApplicationPage
    x:Class="SQL.WP.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
    xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    FontFamily="{StaticResource PhoneFontFamilyNormal}"
    FontSize="{StaticResource PhoneFontSizeNormal}"
    Foreground="{StaticResource PhoneForegroundBrush}"
    SupportedOrientations="Portrait" Orientation="Portrait"
    shell:SystemTray.IsVisible="True">

    <!--LayoutRoot is the root grid where all page content is placed-->
    <Grid x:Name="LayoutRoot" Background="Transparent">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>



        <!--TitlePanel contains the name of the application and page title-->
        <StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
            <TextBlock Text="SQLITE" Style="{StaticResource PhoneTextNormalStyle}" Margin="12,0"/>
            <TextBlock Text="Contactos" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/>
        </StackPanel>

        <!--ContentPanel - place additional content here-->
        <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
            <StackPanel>
                <TextBlock >Nombre</TextBlock>
                <TextBox x:Name="txtName"></TextBox>
                <TextBlock>Edad</TextBlock>
                <TextBox x:Name="txtAge"></TextBox>
                <TextBlock>Correo Electrónico</TextBlock>
                <TextBox x:Name="txtMail"></TextBox>
                <TextBlock>Teléfono</TextBlock>
                <TextBox x:Name="txtPhoneNumber"></TextBox>
                <Button x:Name="btnSave" Click="btnSave_Click">Guardar</Button>
                <Button x:Name="btnSearch">Búsqueda</Button>
            </StackPanel>
        </Grid>

  </Grid>

</phone:PhoneApplicationPage>









En el código fuente generamos los siguientes fragmentos de código:

Un método para inicializar la base de datos SQLite, instanciando nuestra clase CRUDManager e indicándole la ruta de la base de datos.
CRUDManager crud;
void InitializateDB() 
{
    var sqliteFilename = "ContactsDb";
    var path = Path.Combine(Windows.Storage.ApplicationData.Current.LocalFolder.Path, sqliteFilename); ;

     crud = new CRUDManager(path);
}

Ejecutamos este método cuando en la inicialización de la página
public MainPage()
{
    InitializeComponent();

    // Sample code to localize the ApplicationBar
    //BuildLocalizedApplicationBar();
    InitializateDB();
}
Finalmente generamos la funcionalidad del botón “Guardar” que tenemos en nuestra aplicación. Para este ejemplo no se realiza ningún tipo de validación a los datos que se introduzcan.
private void btnSave_Click(object sender, RoutedEventArgs e)
{
    Contact contact = 
        new Contact(txtName.Text, int.Parse(txtAge.Text), txtPhoneNumber.Text, txtMail.Text);

    crud.SaveValue<Contact>(contact);
    
}
Con esto tenemos lo necesario para almacenar registros en nuestra base de datos SQLite. Ahora crearemos otra página con la cual podamos consultar y eliminar registros.

Esta pantalla lo único que va a contener va a ser: 

  • Un cuadro donde recibir el nombre a buscar.
  • Una lista donde se despliegue el resultado.
  • Un botón para realizar la búsqueda y otro para mostrar todo los registros guardados.
image

<phone:PhoneApplicationPage
    x:Class="SQL.WP.Contacts"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
    xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    FontFamily="{StaticResource PhoneFontFamilyNormal}"
    FontSize="{StaticResource PhoneFontSizeNormal}"
    Foreground="{StaticResource PhoneForegroundBrush}"
    SupportedOrientations="Portrait" Orientation="Portrait"
    mc:Ignorable="d"
    shell:SystemTray.IsVisible="True">

    <!--LayoutRoot is the root grid where all page content is placed-->
    <Grid x:Name="LayoutRoot" Background="Transparent">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>

        <!--TitlePanel contains the name of the application and page title-->
        <StackPanel Grid.Row="0" Margin="12,17,0,28">
            <TextBlock Text="SQLite" Style="{StaticResource PhoneTextNormalStyle}"/>
            <TextBlock Text="Contactos" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/>
        </StackPanel>

        <!--ContentPanel - place additional content here-->
        <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
            <StackPanel>
                <TextBlock>Nombre</TextBlock>
                <TextBox x:Name="txtName"></TextBox>
                <Button x:Name="btnSearch" Click="btnSearch_Click">Buscar</Button>
                <Button x:Name="btnAllItems" Click="btnAllItems_Click">Mostrar Todos</Button>
                <ListBox x:Name="lbResults" Height="250">
                    <ListBox.ItemTemplate>
                        <DataTemplate>
                            <StackPanel>
                                <TextBlock FontWeight="Bold">Nombre</TextBlock>
                                <TextBlock Text="{Binding Name}"></TextBlock>
                                <TextBlock FontWeight="Bold">Edad</TextBlock>
                                <TextBlock Text="{Binding Age}"></TextBlock>
                                <TextBlock FontWeight="Bold">Correo Electrónico</TextBlock>
                                <TextBlock Text="{Binding Mail}"></TextBlock>
                                <TextBlock FontWeight="Bold">Teléfono</TextBlock>
                                <TextBlock Text="{Binding PhoneNumber}"></TextBlock>
                            </StackPanel>
                        </DataTemplate>
                    </ListBox.ItemTemplate>
                </ListBox>
                <Button x:Name="btnDelete" Click="btnDelete_Click">Eliminar seleccionado</Button>
            </StackPanel>
        </Grid>
    </Grid>

</phone:PhoneApplicationPage>

Finalmente el código para la funcionalidad.
public Contacts()
{
    InitializeComponent();
   
    InitializateDB();
}


 //Mismo código para inicializar
CRUDManager crud;
void InitializateDB()
{
    var sqliteFilename = "ContactsDb";
    var path = System.IO.Path.Combine(Windows.Storage.ApplicationData.Current.LocalFolder.Path, sqliteFilename); ;

    crud = new CRUDManager(path);
}

private void btnSearch_Click(object sender, RoutedEventArgs e)
{
    //Buscamos el contacto que coincida con lo introducido por el usuario
    lbResults.ItemsSource = new List<Contact> { crud.GetItem<Contact>(txtName.Text)} ;
}

private void btnAllItems_Click(object sender, RoutedEventArgs e)
{
    //Mostramos todos los registros
    lbResults.ItemsSource = crud.GetAllItems<Contact>();
}


private void btnDelete_Click(object sender, RoutedEventArgs e)
{
    //Eliminamos el contacto seleccionado
    crud.Delete<Contact>(((Contact)lbResults.SelectedItem).Key);
    lbResults.ItemsSource = null;
}
Con esto terminamos la aplicación de Windows Phone, a continuación dejo el proyecto de Visual Studio http://1drv.ms/1Gj9vyi