Compartir vía


Parte 5: Estrategias prácticas de uso compartido de código

En esta sección se proporcionan ejemplos de cómo compartir código para escenarios comunes de aplicación.

Capa de datos

La capa de datos consta de un motor de almacenamiento y métodos para leer y escribir información. Para obtener rendimiento, flexibilidad y compatibilidad multiplataforma, se recomienda el motor de base de datos de SQLite para aplicaciones multiplataforma de Xamarin. Se ejecuta en una amplia variedad de plataformas, como Windows, Android, iOS y Mac.

SQLite

SQLite es una implementación de base de datos de código abierto. El código fuente y la documentación se pueden encontrar en SQLite.org. La compatibilidad con SQLite está disponible en cada plataforma móvil:

Incluso con el motor de base de datos disponible en todas las plataformas, los métodos nativos para acceder a la base de datos son diferentes. Tanto iOS como Android ofrecen API integradas para acceder a SQLite que se pueden usar desde Xamarin.iOS o Xamarin.Android, pero el uso de los métodos de SDK nativos no ofrece ninguna capacidad para compartir código (excepto quizás las propias consultas SQL, suponiendo que se almacenen como cadenas). Para obtener más información sobre la funcionalidad de base de datos nativa, busque CoreData en la clase SQLiteOpenHelper de Android o iOS; dado que estas opciones no son multiplataforma, están fuera del ámbito de este documento.

ADO.NET

Tanto Xamarin.iOS como Xamarin.Android admiten System.Data y Mono.Data.Sqlite (consulte la documentación de Xamarin.iOS para obtener más información). El uso de estos espacios de nombres permite escribir código de ADO.NET que funciona en ambas plataformas. Edite las referencias del proyecto para incluir System.Data.dll y Mono.Data.Sqlite.dll agréguelo mediante instrucciones al código:

using System.Data;
using Mono.Data.Sqlite;

A continuación, el código de ejemplo siguiente funcionará:

string dbPath = Path.Combine (
        Environment.GetFolderPath (Environment.SpecialFolder.Personal),
        "items.db3");
bool exists = File.Exists (dbPath);
if (!exists)
    SqliteConnection.CreateFile (dbPath);
var connection = new SqliteConnection ("Data Source=" + dbPath);
connection.Open ();
if (!exists) {
    // This is the first time the app has run and/or that we need the DB.
    // Copy a "template" DB from your assets, or programmatically create one like this:
    var commands = new[]{
        "CREATE TABLE [Items] (Key ntext, Value ntext);",
        "INSERT INTO [Items] ([Key], [Value]) VALUES ('sample', 'text')"
    };
    foreach (var command in commands) {
        using (var c = connection.CreateCommand ()) {
            c.CommandText = command;
            c.ExecuteNonQuery ();
        }
    }
}
// use `connection`... here, we'll just append the contents to a TextView
using (var contents = connection.CreateCommand ()) {
    contents.CommandText = "SELECT [Key], [Value] from [Items]";
    var r = contents.ExecuteReader ();
    while (r.Read ())
        Console.Write("\n\tKey={0}; Value={1}",
                r ["Key"].ToString (),
                r ["Value"].ToString ());
}
connection.Close ();

Las implementaciones reales de ADO.NET obviamente se dividirán entre diferentes métodos y clases (este ejemplo es solo con fines de demostración).

SQLite-NET: ORM multiplataforma

Un ORM (o asignador relacional de objetos) intenta simplificar el almacenamiento de datos modelados en clases. En lugar de escribir manualmente consultas SQL con instrucciones CREATE TABLE o SELECT, INSERT y DELETE aplicadas a los datos que se extraen manualmente de propiedades y campos de clase, un ORM agrega una capa de código que lo hace automáticamente. Mediante la reflexión para examinar la estructura de las clases, un ORM puede crear automáticamente tablas y columnas que coincidan con una clase y generar consultas para leer y escribir los datos. Esto permite que el código de aplicación envíe y recupere instancias de objeto al ORM, que lleva a cabo todas las operaciones SQL en segundo plano.

SQLite-NET actúa como un ORM simple que permitirá guardar y recuperar sus clases en SQLite. Oculta la complejidad de acceso de SQLite multiplataforma con una combinación de directivas del compilador y otros trucos.

Características de SQLite-NET:

  • Las tablas se definen mediante la adición de atributos a las clases de modelo.
  • Una instancia de base de datos se representa mediante una subclase de SQLiteConnection, la clase principal de la biblioteca SQLite-Net.
  • Los datos se pueden insertar, consultar y eliminar mediante objetos. No se requiere ninguna instrucción SQL (aunque se pueden escribir instrucciones SQL si es necesario).
  • Se pueden realizar consultas básicas de Linq en las colecciones devueltas por SQLite-NET.

El código fuente y la documentación de SQLite-NET están disponibles en SQLite-Net en GitHub y se han implementado en los dos casos prácticos. A continuación se muestra un ejemplo sencillo de código SQLite-NET (del caso práctico de Tasky Pro).

En primer lugar, la clase TodoItem usa atributos para definir un campo para que sea una clave principal de base de datos:

public class TodoItem : IBusinessEntity
{
    public TodoItem () {}
    [PrimaryKey, AutoIncrement]
    public int ID { get; set; }
    public string Name { get; set; }
    public string Notes { get; set; }
    public bool Done { get; set; }
}

Esto permite crear una tabla TodoItem con la siguiente línea de código (y ninguna instrucción SQL) en una instancia de SQLiteConnection:

CreateTable<TodoItem> ();

Los datos de la tabla también se pueden manipular con otros métodos en SQLiteConnection (de nuevo, sin necesidad de instrucciones SQL):

Insert (TodoItem); // 'task' is an instance with data populated in its properties
Update (TodoItem); // Primary Key field must be populated for Update to work
Table<TodoItem>.ToList(); // returns all rows in a collection

Consulte el código fuente del caso práctico para obtener ejemplos completos.

Acceso a archivos

El acceso a archivos es con toda seguridad una parte clave de cualquier aplicación. Entre los ejemplos comunes de archivos que pueden formar parte de una aplicación se incluyen:

  • Archivos de base de datos de SQLite.
  • Datos generados por el usuario (texto, imágenes, sonido, vídeo).
  • Datos descargados para el almacenamiento en caché (imágenes, archivos HTML o PDF).

Acceso directo a System.IO

Tanto Xamarin.iOS como Xamarin.Android permiten el acceso del sistema de archivos mediante clases en el espacio de nombres System.IO.

Cada plataforma tiene restricciones de acceso diferentes que se deben tener en cuenta:

  • Las aplicaciones de iOS se ejecutan en un espacio aislado con acceso muy restringido al sistema de archivos. Apple determina aún más cómo debe usarse el sistema de archivos especificando determinadas ubicaciones de las que se realiza una copia de seguridad (y otras de las que no). Consulte la guía sobre cómo Trabajar con el sistema de archivos en Xamarin.iOS para obtener más detalles.
  • Android también restringe el acceso a determinados directorios relacionados con la aplicación, pero también admite medios externos (por ejemplo, tarjetas SD) y el acceso a datos compartidos.
  • Windows Phone 8 (Silverlight) no permite el acceso directo a archivos: los archivos solo se pueden manipular mediante IsolatedStorage.
  • Los proyectos de UWP de Windows 8.1 WinRT y Windows 10 solo ofrecen operaciones de archivos asincrónicas a través de API Windows.Storage, que son diferentes de las otras plataformas.

Ejemplo de iOS y Android

A continuación se muestra un ejemplo sencillo que escribe y lee un archivo de texto. El uso de Environment.GetFolderPath permite que el mismo código se ejecute en iOS y Android, y que cada uno devuelva un directorio válido en función de sus convenciones del sistema de archivos.

string filePath = Path.Combine (
        Environment.GetFolderPath (Environment.SpecialFolder.Personal),
        "MyFile.txt");
System.IO.File.WriteAllText (filePath, "Contents of text file");
Console.WriteLine (System.IO.File.ReadAllText (filePath));

Consulte el documento sobre cómo Trabajar con el sistema de archivos en Xamarin.iOS para obtener más información sobre la funcionalidad del sistema de archivos específico de iOS. Al escribir código de acceso a archivos multiplataforma, recuerde que algunos sistemas de archivos distinguen entre mayúsculas y minúsculas y tienen separadores de directorio diferentes. Se recomienda usar siempre las mismas mayúsculas y minúsculas para los nombres de archivo y el método Path.Combine() al construir rutas de acceso de archivo o directorio.

Espacios de almacenamiento de Windows en Windows 8 y Windows 10

El libroCreación de aplicaciones móviles con Xamarin.Forms capítulo 20. Async y E/S de archivos incluye ejemplos de Windows 8.1 y Windows 10.

Con DependencyService es posible leer y guardar archivos en estas plataformas mediante las API admitidas:

StorageFolder localFolder = ApplicationData.Current.LocalFolder;
IStorageFile storageFile = await localFolder.CreateFileAsync("MyFile.txt",
                                        CreationCollisionOption.ReplaceExisting);
await FileIO.WriteTextAsync(storageFile, "Contents of text file");

Consulte el capítulo 20 del libro para obtener más información.

Almacenamiento aislado en Windows Phone 7 y 8 (Silverlight)

El almacenamiento aislado es una API común para guardar y cargar archivos en todas las plataformas iOS, Android y versiones anteriores de Windows Phone.

Es el mecanismo predeterminado para el acceso a archivos en Windows Phone (Silverlight) que se ha implementado en Xamarin.iOS y Xamarin.Android para permitir que se escriba código de acceso a archivos común. Se puede hacer referencia a la clase System.IO.IsolatedStorage en las tres plataformas de un proyecto compartido.

Consulte la Información general sobre el almacenamiento aislado para Windows Phone para obtener más información.

Las API de almacenamiento aislado no están disponibles en las bibliotecas de clases portables. Una alternativa para las PCL son los NuGet de PCLStorage

Acceso a archivos multiplataforma en PCL

También hay un NuGet compatible con PCL, PCLStorage, que proporciona acceso a archivos multiplataforma para plataformas compatibles con Xamarin y las API de Windows más recientes.

Operaciones de red

La mayoría de las aplicaciones móviles tendrán un componente de red, por ejemplo:

  • Descargar imágenes, vídeo y audio (por ejemplo, miniaturas, fotos, música).
  • Descargar documentos (por ejemplo, HTML, PDF).
  • Cargar datos de usuario (como fotos o texto).
  • Acceso a servicios web o API de terceros (incluidos SOAP, XML o JSON).

.NET Framework proporciona algunas clases diferentes para acceder a los recursos de red: HttpClient, WebClient y HttpWebRequest.

HttpClient

La clase HttpClient del espacio de nombres System.Net.Http está disponible en Xamarin.iOS, Xamarin.Android y la mayoría de las plataformas Windows. Hay un NuGet de biblioteca de cliente HTTP de Microsoft que se puede usar para incorporar esta API a bibliotecas de clases portables (y Windows Phone 8 Silverlight).

var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://xamarin.com");
var response = await myClient.SendAsync(request);

WebClient

La clase WebClient proporciona una API sencilla para recuperar datos remotos de servidores remotos.

Las operaciones de Plataforma universal de Windows deben ser asincrónicas, aunque Xamarin.iOS y Xamarin.Android admitan operaciones sincrónicas (que se pueden realizar en subprocesos en segundo plano).

El código de una operación WebClient asincrónica simple es:

var webClient = new WebClient ();
webClient.DownloadStringCompleted += (sender, e) =>
{
    var resultString = e.Result;
    // do something with downloaded string, do UI interaction on main thread
};
webClient.Encoding = System.Text.Encoding.UTF8;
webClient.DownloadStringAsync (new Uri ("http://some-server.com/file.xml"));

WebClient también tiene DownloadFileCompleted y DownloadFileAsync para recuperar datos binarios.

HttpWebRequest

HttpWebRequest ofrece más personalización que WebClient y, como resultado, requiere el uso de más código.

El código de una operación HttpWebRequest asincrónica simple es:

var request = HttpWebRequest.Create(@"http://some-server.com/file.xml ");
request.ContentType = "text/xml";
request.Method = "GET";
using (HttpWebResponse response = request.GetResponse() as HttpWebResponse)
{
    if (response.StatusCode != HttpStatusCode.OK)
        Console.WriteLine("Error fetching data. Server returned status code: {0}", response.StatusCode);
    using (StreamReader reader = new StreamReader(response.GetResponseStream()))
    {
        var content = reader.ReadToEnd();
        // do something with downloaded string, do UI interaction on main thread
    }
}

Hay un ejemplo en nuestra documentación de servicios web.

Reachability

Los dispositivos móviles funcionan bajo una variedad de condiciones de red, desde conexiones 4G o Wi-Fi rápidas hasta áreas de recepción deficientes y vínculos de datos EDGE lentos. Por este motivo, se recomienda detectar si la red está disponible y, si es así, qué tipo de red está disponible, antes de intentar conectarse a servidores remotos.

Entre las acciones que puede realizar una aplicación móvil en estas situaciones se incluyen las siguientes:

  • Si la red no está disponible, informar al usuario. Si se ha deshabilitado manualmente (por ejemplo, poner en Modo avión o desactivar la conexión Wi-Fi) puede resolver el problema.
  • Si la conexión es 3G, las aplicaciones pueden comportarse de forma diferente (por ejemplo, Apple no permite que las aplicaciones de más de 20 Mb se descarguen por 3G). Las aplicaciones pueden usar esta información para advertir al usuario sobre tiempos de descarga excesivos al recuperar archivos grandes.
  • Incluso si la red está disponible, se recomienda comprobar la conectividad con el servidor de destino antes de iniciar otras solicitudes. Esto impedirá que las operaciones de red de la aplicación agoten el tiempo de espera repetidamente y también permitirá mostrar un mensaje de error más informativo al usuario.

Servicios web

Consulte nuestra documentación sobre cómo Trabajar con servicios web, que abarca el acceso a los puntos de conexión REST, SOAP y WCF mediante Xamarin.iOS. Es posible crear manualmente solicitudes de servicio web y analizar las respuestas; sin embargo, hay bibliotecas disponibles para que esto sea mucho más sencillo, incluido Azure, RestSharp y ServiceStack. Se puede acceder incluso a las operaciones básicas de WCF en aplicaciones de Xamarin.

Azure

Microsoft Azure es una plataforma en la nube que proporciona una amplia variedad de servicios para aplicaciones móviles, como almacenamiento y sincronización de datos y notificaciones push.

Visite azure.microsoft.com para probarlo gratis.

RestSharp

RestSharp es una biblioteca de .NET que se puede incluir en aplicaciones móviles para proporcionar un cliente REST que simplifica el acceso a los servicios web. Ayuda a proporcionar una API sencilla para solicitar datos y analizar la respuesta de REST. RestSharp puede ser útil

El sitio web de RestSharp contiene documentación sobre cómo implementar un cliente REST mediante RestSharp. RestSharp proporciona ejemplos de Xamarin.iOS y Xamarin.Android en GitHub.

También hay un fragmento de código de Xamarin.iOS en nuestra documentación de servicios web.

ServiceStack

A diferencia de RestSharp, ServiceStack es una solución del lado servidor para hospedar un servicio web, así como una biblioteca cliente que se puede implementar en aplicaciones móviles para acceder a esos servicios.

El sitio web de ServiceStack explica la finalidad del proyecto y contiene vínculos a ejemplos de documentos y código. Los ejemplos incluyen una implementación completa del lado servidor de un servicio web, así como varias aplicaciones del lado cliente que pueden acceder a él.

WCF

Las herramientas de Xamarin pueden ayudarle a consumir algunos servicios de Windows Communication Foundation (WCF). En general, Xamarin admite el mismo subconjunto del lado cliente de WCF que se incluye con el entorno de ejecución de Silverlight. Esto incluye las implementaciones de protocolo y codificación más comunes de WCF: mensajes SOAP codificados con texto a través del protocolo de transporte HTTP mediante BasicHttpBinding.

Dado el tamaño y la complejidad del marco de WCF, puede haber implementaciones de servicio actuales y futuras que quedarán fuera del ámbito admitido por el dominio de subconjunto de cliente de Xamarin. Además, la compatibilidad con WCF requiere el uso de herramientas solo disponibles en un entorno Windows para generar el proxy.

Subprocesos

La capacidad de respuesta de las aplicaciones es importante para las aplicaciones móviles: los usuarios esperan que las aplicaciones se carguen y ejecuten rápidamente. Una "pantalla congelada" que deja de interactuar con el usuario aparecerá para indicar que la aplicación se ha bloqueado, por lo que es importante no vincular el subproceso de interfaz de usuario con llamadas de bloqueo de larga duración, como solicitudes de red o operaciones locales lentas (como descomprimir un archivo). En concreto, el proceso de inicio no debe contener tareas de larga duración: todas las plataformas móviles eliminarán una aplicación que tarde demasiado tiempo en cargarse.

Esto significa que la interfaz de usuario debe implementar un "indicador de progreso" o una interfaz de usuario "utilizable" que sea rápida de mostrar, así como tareas asincrónicas para realizar operaciones en segundo plano. La ejecución de tareas en segundo plano requiere el uso de subprocesos, lo cual significa que las tareas en segundo plano necesitan una manera de comunicarse con el subproceso principal para indicar el progreso o el momento en que se hayan completado.

Biblioteca de tareas paralelas

Las tareas creadas con la biblioteca de tareas paralelas se pueden ejecutar de forma asincrónica y devolverse en su subproceso de llamada, y esto hace que sean muy útiles para desencadenar operaciones de larga duración sin bloquear la interfaz de usuario.

Una operación de tarea paralela simple podría tener este aspecto:

using System.Threading.Tasks;
void MainThreadMethod ()
{
    Task.Factory.StartNew (() => wc.DownloadString ("http://...")).ContinueWith (
        t => label.Text = t.Result, TaskScheduler.FromCurrentSynchronizationContext()
    );
}

La clave es TaskScheduler.FromCurrentSynchronizationContext(), que reutilizará SynchronizationContext.Current del subproceso que llama al método (aquí el subproceso principal que se está ejecutando MainThreadMethod) como una manera de serializar las llamadas inversas a ese subproceso. Esto significa que si se llama al método en el subproceso de la interfaz de usuario, se ejecutará la operación ContinueWith de nuevo en el subproceso de la interfaz de usuario.

Si el código inicia tareas desde otros subprocesos, use el siguiente patrón para crear una referencia al subproceso de la interfaz de usuario y que la tarea pueda devolverle a llamarla:

static Context uiContext = TaskScheduler.FromCurrentSynchronizationContext();

Invocación en el subproceso de la interfaz de usuario

Para el código que no utiliza la biblioteca de tareas paralelas, cada plataforma tiene su propia sintaxis para serializar las operaciones de nuevo en el subproceso de la interfaz de usuario:

  • iOS: owner.BeginInvokeOnMainThread(new NSAction(action))
  • Android: owner.RunOnUiThread(action)
  • Xamarin.Forms: Device.BeginInvokeOnMainThread(action)
  • Windows:Deployment.Current.Dispatcher.BeginInvoke(action)

La sintaxis de iOS y Android requiere que esté disponible una clase "context", esto significa que el código debe pasar este objeto a cualquier método que requiera una devolución de llamada en el subproceso de la interfaz de usuario.

Para realizar llamadas a subprocesos de interfaz de usuario en código compartido, siga el ejemplo IDispatchOnUIThread (cortesía de @follesoe). Declare y programe en una interfaz IDispatchOnUIThread en el código compartido y, a continuación, implemente las clases específicas de la plataforma, como se muestra aquí:

// program to the interface in shared code
public interface IDispatchOnUIThread {
    void Invoke (Action action);
}
// iOS
public class DispatchAdapter : IDispatchOnUIThread {
    public readonly NSObject owner;
    public DispatchAdapter (NSObject owner) {
        this.owner = owner;
    }
    public void Invoke (Action action) {
        owner.BeginInvokeOnMainThread(new NSAction(action));
    }
}
// Android
public class DispatchAdapter : IDispatchOnUIThread {
    public readonly Activity owner;
    public DispatchAdapter (Activity owner) {
        this.owner = owner;
    }
    public void Invoke (Action action) {
        owner.RunOnUiThread(action);
    }
}
// WP7
public class DispatchAdapter : IDispatchOnUIThread {
    public void Invoke (Action action) {
        Deployment.Current.Dispatcher.BeginInvoke(action);
    }
}

Los desarrolladores de Xamarin.Forms deben usar Device.BeginInvokeOnMainThread en el código común (proyectos compartidos o PCL).

Funcionalidades y degradación de la plataforma y del dispositivo

En la documentación sobre funcionalidades de la plataforma se proporcionan ejemplos específicos adicionales de cómo gestionar distintas funcionalidades. Se trata sobre cómo detectar diferentes funcionalidades y cómo degradar correctamente una aplicación para proporcionar una buena experiencia de usuario, incluso cuando la aplicación no puede funcionar con todo su potencial.