Compartir vía


Caso práctico de aplicaciones multiplataforma: Tasky

TaskyPortable es una aplicación de lista de tareas pendientes sencilla. En este documento se describe cómo se diseñó y compiló, siguiendo las instrucciones del documento Compilar aplicaciones multiplataforma. En esta discusión se tratan las siguientes áreas:

Proceso de diseño

Es aconsejable crear un mapa de ruta para lo que desea lograr antes de empezar a codificar. Esto es especialmente cierto para el desarrollo multiplataforma, donde se crea la funcionalidad que se expondrá de varias maneras. A partir de una idea clara de lo que está creando ahorra tiempo y esfuerzo más adelante en el ciclo de desarrollo.

Requisitos

El primer paso para diseñar una aplicación es identificar las características deseadas. Estos pueden ser objetivos de alto nivel o casos de uso detallados. Tasky tiene requisitos funcionales sencillos:

  • Visualizar una lista de tareas
  • Agregar, editar y eliminar tareas
  • Establecer el estado de una tarea en "listo"

Debe tener en cuenta el uso de características específicas de la plataforma. ¿Puede Tasky aprovechar las ventajas de la geovalla de iOS o Windows Phone Live Tiles? Incluso si no usa características específicas de la plataforma en la primera versión, debe planear con anticipación para asegurarse de que sus capas de negocio y datos pueden adaptarse a ellas.

Diseño de la interfaz de usuario

Comience con un diseño de alto nivel que se pueda implementar en las plataformas de destino. Tenga cuidado de tener en cuenta las restricciones de la interfaz de usuario de la especificación de la plataforma. Por ejemplo, un TabBarController en iOS puede mostrar más de cinco botones, mientras que el Windows Phone equivalente puede mostrar hasta cuatro. Dibuje el flujo de pantalla con la herramienta que prefiera (papeles).

Draw the screen-flow using the tool of your choice paper works

Modelo de datos

Saber qué datos deben almacenarse ayudarán a determinar qué mecanismo de persistencia se va a usar. Consulte Acceso a datos multiplataforma para obtener información sobre los mecanismos de almacenamiento disponibles y ayudar a decidir entre ellos. Para este proyecto, usaremos SQLite.NET.

Tasky debe almacenar tres propiedades para cada "TaskItem":

  • Name: cadena
  • Notas: Cadena
  • Listo: Booleano

Funcionalidad principal

Tenga en cuenta la API que la interfaz de usuario necesitará consumir para cumplir los requisitos. Una lista de tareas pendientes requiere las siguientes funciones:

  • Enumerar todas las tareas: para mostrar la lista de pantalla principal de todas las tareas disponibles
  • Obtener una tarea: cuando se toca una fila de tareas
  • Guardar una tarea: cuando se edita una tarea
  • Eliminar una tarea: cuando se elimina una tarea
  • Crear tarea vacía: cuando se crea una nueva tarea

Para lograr la reutilización del código, esta API se debe implementar una vez en la biblioteca de clases portable.

Implementación

Una vez que se haya acordado el diseño de la aplicación, considere cómo se puede implementar como una aplicación multiplataforma. Esto se convertirá en la arquitectura de la aplicación. Siguiendo las instrucciones del documento Compilar aplicaciones multiplataforma, el código de la aplicación debe dividirse en las siguientes partes:

  • Código común: un proyecto común que contiene código reutilizable para almacenar los datos de tareas; exponga una clase Model y una API para administrar el almacenamiento y la carga de datos.
  • Código específico de la plataforma: proyectos específicos de la plataforma que implementan una interfaz de usuario nativa para cada sistema operativo, utilizando el código común como "back-end".

Platform-specific projects implement a native UI for each operating system, utilizing the common code as the back end

Estas dos partes se describen en las siguientes secciones.

Código común (PCL)

Tasky Portable usa la estrategia de biblioteca de clases portable para compartir código común. Consulte el documento Opciones de código para compartir para obtener una descripción de las opciones de uso compartido de código.

Todo el código común, incluido el nivel de acceso a datos, el código de base de datos y los contratos, se coloca en el proyecto de biblioteca.

A continuación se muestra el proyecto PCL completo. Todo el código de la biblioteca portátil es compatible con cada plataforma de destino. Cuando se implementa, cada aplicación nativa hará referencia a esa biblioteca.

When deployed, each native app will reference that library

En el diagrama de clases siguiente se muestran las clases agrupadas por capa. La clase SQLiteConnection es código reutilizable del paquete Sqlite-NET. El resto de las clases son código personalizado para Tasky. Las clases TaskItemManager y TaskItem representan la API que se expone a las aplicaciones específicas de la plataforma.

The TaskItemManager and TaskItem classes represent the API that is exposed to the platform-specific applications

El uso de espacios de nombres para separar las capas ayuda a administrar las referencias entre cada capa. Los proyectos específicos de la plataforma solo deben tener que incluir una instrucción using para la capa empresarial. La API que expone TaskItemManager en la capa de negocio debe encapsular la capa de acceso a datos y la capa de datos.

Referencias

Las bibliotecas de clases portátiles deben ser utilizables en varias plataformas, cada una con distintos niveles de compatibilidad con las características de plataforma y marco. Debido a esto, existen limitaciones sobre qué paquetes y bibliotecas de marcos se pueden usar. Por ejemplo, Xamarin.iOS no admite la palabra clave c# dynamic, por lo que una biblioteca de clases portable no puede usar ningún paquete que dependa del código dinámico, aunque este código funcionaría en Android. Visual Studio para Mac impedirá agregar paquetes y referencias incompatibles, pero querrá tener en cuenta las limitaciones para evitar sorpresas más adelante.

Nota: Verá que los proyectos hacen referencia a las bibliotecas del marco de trabajo que no ha usado. Estas referencias se incluyen como parte de las plantillas de proyecto de Xamarin. Cuando se compilan las aplicaciones, el proceso de vinculación quitará el código sin referencia, por lo que, aunque System.Xml se haya hecho referencia a ella, no se incluirá en la aplicación final porque no se usan funciones Xml.

Capa de datos (DL)

La capa de datos contiene el código que realiza el almacenamiento físico de datos, ya sea en una base de datos, archivos planos u otro mecanismo. La capa de datos Tasky consta de dos partes: la biblioteca SQLite-NET y el código personalizado agregado para conectarlo.

Tasky se basa en el paquete NuGet Sqlite-net (publicado por Frank Krueger) para insertar código SQLite-NET que proporciona una interfaz de base de datos de asignación relacional de objetos (ORM). La clase TaskItemDatabase se hereda de SQLiteConnection y agrega los métodos necesarios Create, Read, Update, Delete (CRUD) para leer y escribir datos en SQLite. Se trata de una implementación reutilizable simple de métodos CRUD genéricos que podrían volver a usarse en otros proyectos.

TaskItemDatabase es un singleton, lo que garantiza que todo el acceso se produce en la misma instancia. Se usa un bloqueo para evitar el acceso simultáneo desde varios subprocesos.

SQLite en Windows Phone

Aunque iOS y Android se envían con SQLite como parte del sistema operativo, Windows Phone no incluye un motor de base de datos compatible. Para compartir código en las tres plataformas, se requiere una versión nativa de Windows Phone de SQLite. Consulte Trabajar con una base de datos local para obtener más información sobre cómo configurar el proyecto de Windows Phone para Sqlite.

Usar una interfaz para generalizar el acceso a datos

La capa de datos toma una dependencia de BL.Contracts.IBusinessIdentity para que pueda implementar métodos de acceso a datos abstractos que requieren una clave principal. Cualquier clase de capa empresarial que implemente la interfaz se puede conservar en la capa de datos.

La interfaz solo especifica una propiedad entera para que actúe como clave principal:

public interface IBusinessEntity {
    int ID { get; set; }
}

La clase base implementa la interfaz y agrega los atributos SQLite-NET para marcarlos como una clave principal de incremento automático. Cualquier clase de la capa de negocio que implemente esta clase base se puede conservar en la capa de datos:

public abstract class BusinessEntityBase : IBusinessEntity {
    public BusinessEntityBase () {}
    [PrimaryKey, AutoIncrement]
    public int ID { get; set; }
}

Un ejemplo de los métodos genéricos de la capa de datos que usan la interfaz es este método de GetItem<T>:

public T GetItem<T> (int id) where T : BL.Contracts.IBusinessEntity, new ()
{
    lock (locker) {
        return Table<T>().FirstOrDefault(x => x.ID == id);
    }
}

Bloqueo para evitar el acceso simultáneo

Un bloqueo se implementa dentro de la clase TaskItemDatabase para evitar el acceso simultáneo a la base de datos. Esto es para garantizar que el acceso simultáneo desde diferentes subprocesos se serialice (de lo contrario, un componente de interfaz de usuario podría intentar leer la base de datos al mismo tiempo que un subproceso en segundo plano lo está actualizando). Aquí se muestra un ejemplo de cómo se implementa el bloqueo:

static object locker = new object ();
public IEnumerable<T> GetItems<T> () where T : BL.Contracts.IBusinessEntity, new ()
{
    lock (locker) {
        return (from i in Table<T> () select i).ToList ();
    }
}
public T GetItem<T> (int id) where T : BL.Contracts.IBusinessEntity, new ()
{
    lock (locker) {
        return Table<T>().FirstOrDefault(x => x.ID == id);
    }
}

La mayoría del código de capa de datos podría volver a usarse en otros proyectos. El único código específico de la aplicación en la capa es la llamada de CreateTable<TaskItem> en el constructor TaskItemDatabase.

Capa de acceso a datos (DAL)

La clase TaskItemRepository encapsula el mecanismo de almacenamiento de datos con una API fuertemente tipada que permite que TaskItem cree, elimine, recupere y actualice objetos.

Usar la Compilación condicional

La clase usa la compilación condicional para establecer la ubicación del archivo; este es un ejemplo de implementación de la divergencia de la plataforma. La propiedad que devuelve la ruta de acceso se compila en código diferente en cada plataforma. Aquí se muestran el código y las directivas del compilador específicas de la plataforma:

public static string DatabaseFilePath {
    get {
        var sqliteFilename = "TaskDB.db3";
#if SILVERLIGHT
        // Windows Phone expects a local path, not absolute
        var path = sqliteFilename;
#else
#if __ANDROID__
        // Just use whatever directory SpecialFolder.Personal returns
        string libraryPath = Environment.GetFolderPath(Environment.SpecialFolder.Personal); ;
#else
        // we need to put in /Library/ on iOS5.1+ to meet Apple's iCloud terms
        // (they don't want non-user-generated data in Documents)
        string documentsPath = Environment.GetFolderPath (Environment.SpecialFolder.Personal); // Documents folder
        string libraryPath = Path.Combine (documentsPath, "..", "Library"); // Library folder
#endif
        var path = Path.Combine (libraryPath, sqliteFilename);
                #endif
                return path;
    }
}

En función de la plataforma, la salida será "<app path>/Library/TaskDB.db3" para iOS, "<app path>/Documents/TaskDB.db3" para Android o simplemente "TaskDB.db3" para Windows Phone.

Capa de negocio (BL)

La capa de negocio implementa las clases Model y una fachada para administrarlas. En Tasky, el modelo es la clase TaskItem y TaskItemManager implementa el patrón Façade para proporcionar una API para administrar TaskItems.

Façade

TaskItemManager encapsula DAL.TaskItemRepository para proporcionar los métodos Get, Save y Delete a los que hará referencia las capas de la aplicación y de la interfaz de usuario.

Las reglas de negocios y la lógica se colocarían aquí si fuera necesario; por ejemplo, las reglas de validación que se deben cumplir antes de guardar un objeto.

Código específico para la API de la plataforma

Una vez escrito el código común, la interfaz de usuario debe compilarse para recopilar y mostrar los datos expuestos por él. La clase TaskItemManager implementa el patrón Façade para proporcionar una API sencilla para que el código de la aplicación acceda.

Por lo general, el código escrito en cada proyecto específico de la plataforma se acoplará estrechamente al SDK nativo de ese dispositivo y solo tendrá acceso al código común mediante la API definida por TaskItemManager. Esto incluye los métodos y las clases empresariales que expone, como TaskItem.

Las imágenes no se comparten entre plataformas, sino que se agregan de forma independiente a cada proyecto. Esto es importante porque cada plataforma controla las imágenes de forma diferente, usando nombres de archivo, directorios y resoluciones diferentes.

En las secciones restantes se describen los detalles de implementación específicos de la plataforma de la interfaz de usuario de Tasky.

iOS App

Solo hay una serie de clases necesarias para implementar la aplicación Tasky de iOS mediante el proyecto PCL común para almacenar y recuperar datos. A continuación se muestra el proyecto completo de iOS Xamarin.iOS:

iOS project is shown here

Las clases se muestran en este diagrama, agrupadas en capas.

The classes are shown in this diagram, grouped into layers

Referencias

La aplicación iOS hace referencia a las bibliotecas del SDK específicas de la plataforma, por ejemplo, Xamarin.iOS y MonoTouch.Dialog-1.

También debe hacer referencia al proyecto PCL TaskyPortableLibrary. La lista de referencias se muestra aquí:

The references list is shown here

La capa de aplicación y la capa de interfaz de usuario se implementan en este proyecto mediante estas referencias.

Capa de aplicación (AL)

La capa de aplicación contiene clases específicas de la plataforma necesarias para "enlazar" los objetos expuestos por el PCL a la interfaz de usuario. La aplicación específica de iOS tiene dos clases para ayudar a mostrar las tareas:

  • EditingSource: esta clase se usa para enlazar listas de tareas a la interfaz de usuario. Dado que MonoTouch.Dialog se usó para la lista de tareas, es necesario implementar este asistente para habilitar la funcionalidad de deslizar el dedo a eliminar en UITableView. Deslizar el dedo a eliminar es común en iOS, pero no en Android o Windows Phone, por lo que el proyecto específico de iOS es el único que lo implementa.
  • TaskDialog: esta clase se usa para enlazar una sola tarea a la interfaz de usuario. Usa la API de Reflexiones MonoTouch.Dialog para "encapsular" el objeto TaskItem con una clase que contiene los atributos correctos para permitir que la pantalla de entrada tenga el formato correcto.

La clase TaskDialog usa atributos MonoTouch.Dialog para crear una pantalla basada en las propiedades de una clase. La clase tiene el aspecto siguiente:

public class TaskDialog {
    public TaskDialog (TaskItem task)
    {
        Name = task.Name;
        Notes = task.Notes;
        Done = task.Done;
    }
    [Entry("task name")]
    public string Name { get; set; }
    [Entry("other task info")]
    public string Notes { get; set; }
    [Entry("Done")]
    public bool Done { get; set; }
    [Section ("")]
    [OnTap ("SaveTask")]    // method in HomeScreen
    [Alignment (UITextAlignment.Center)]
    public string Save;
    [Section ("")]
    [OnTap ("DeleteTask")]  // method in HomeScreen
    [Alignment (UITextAlignment.Center)]
    public string Delete;
}

Observe que los atributos OnTap requieren un nombre de método: estos métodos deben existir en la clase donde MonoTouch.Dialog.BindingContext se crea (en este caso, la clase HomeScreen que se describe en la sección siguiente).

Capa de interfaz de usuario (UI)

La capa de interfaz de usuario consta de las siguientes clases:

  1. AppDelegate: contiene llamadas a la API de apariencia para aplicar estilo a las fuentes y colores usados en la aplicación. Tasky es una aplicación sencilla, por lo que no hay otras tareas de inicialización que se ejecuten en FinishedLaunching.
  2. Pantallas: subclases de UIViewController que definen cada pantalla y su comportamiento. Las pantallas unen la interfaz de usuario con las clases de capa de aplicación y la API común (TaskItemManager). En este ejemplo, las pantallas se crean en código, pero podrían haberse diseñado con el Generador de interfaces de Xcode o el diseñador de guiones gráficos.
  3. Imágenes: los elementos visuales son una parte importante de cada aplicación. Tasky tiene imágenes de icono y pantalla de presentación, que para iOS se deben proporcionar en resolución regular y Retina.

Pantalla principal

La pantalla principal es una pantalla MonoTouch.Dialog que muestra una lista de tareas de la base de datos de SQLite. Hereda de DialogViewController e implementa código para establecer que Root contiene una colección de objetos TaskItem para mostrar.

It inherits from DialogViewController and implements code to set the Root to contain a collection of TaskItem objects for display

Los dos métodos principales relacionados con la visualización e interacción con la lista de tareas son:

  1. PopulateTable: usa el método TaskManager.GetTasks la capa de negocio para recuperar una colección de objetos TaskItem que se van a mostrar.
  2. Seleccionado: cuando se toca una fila, muestra la tarea en una nueva pantalla.

Pantalla de detalles de la tarea

Detalles de la tarea es una pantalla de entrada que permite editar o eliminar tareas.

Tasky usa la API Reflexiones de MonoTouch.Dialog para mostrar la pantalla, por lo que no hay ninguna implementación UIViewController. En su lugar, la clase HomeScreen crea una instancia y muestra un DialogViewController mediante la clase TaskDialog de la capa de aplicación.

En esta captura de pantalla se muestra una pantalla vacía que muestra el atributo Entry que establece el texto de la marca de agua en los campos Nombre y Notas:

This screenshot shows an empty screen that demonstrates the Entry attribute setting the watermark text in the Name and Notes fields

La funcionalidad de la pantalla Detalles de la tarea (como guardar o eliminar una tarea) debe implementarse en la clase HomeScreen, ya que es donde se crea el MonoTouch.Dialog.BindingContext. Los métodos HomeScreen siguientes admiten la pantalla Detalles de la tarea:

  1. ShowTaskDetails: crea un objeto MonoTouch.Dialog.BindingContext para representar una pantalla. Crea la pantalla de entrada mediante la reflexión para recuperar los nombres y tipos de propiedad de la clase TaskDialog. La información adicional, como el texto de la marca de agua de los cuadros de entrada, se implementa con atributos en las propiedades.
  2. SaveTask: se hace referencia a este método en la clase TaskDialog a través de un atributo OnTap. Se llama cuando se presiona Guardar y usa MonoTouch.Dialog.BindingContext para recuperar los datos especificados por el usuario antes de guardar los cambios mediante TaskItemManager.
  3. DeleteTask: se hace referencia a este método en la clase TaskDialog a través de un atributo OnTap. Usa TaskItemManager para eliminar los datos mediante la clave principal (propiedad ID).

Aplicación para Android

A continuación se muestra el proyecto completo de Xamarin.Android:

Android project is pictured here

Diagrama de clases, con clases agrupadas por capa:

The class diagram, with classes grouped by layer

Referencias

El proyecto de aplicación Android debe hacer referencia al ensamblado de Xamarin.Android específico de la plataforma para acceder a las clases desde Android SDK.

También debe hacer referencia al proyecto PCL (por ejemplo, TaskyPortableLibrary) para acceder al código de capa empresarial y datos comunes.

TaskyPortableLibrary to access the common data and business layer code

Capa de aplicación (AL)

De forma similar a la versión de iOS que hemos visto anteriormente, la capa de aplicación de la versión de Android contiene clases específicas de la plataforma necesarias para "enlazar" los objetos expuestos por Core a la interfaz de usuario.

TaskListAdapter: para mostrar una lista<T> de objetos que necesitamos implementar un adaptador para mostrar objetos personalizados en ListView. El adaptador controla qué diseño se usa para cada elemento de la lista; en este caso, el código usa un diseño integrado de Android SimpleListItemChecked.

Interfaz de usuario (UI)

La capa de interfaz de usuario de la aplicación Android es una combinación de código y marcado XML.

  • Resources/Layout: diseños de pantalla y el diseño de celdas de fila implementados como archivos AXML. AXML se puede escribir a mano o diseñar visualmente mediante el Diseñador de interfaz de usuario de Xamarin para Android.
  • Recursos/Dibujables: imágenes (iconos) y botón personalizado.
  • Pantallas: subclases de actividad que definen cada pantalla y su comportamiento. Vincula la interfaz de usuario con las clases de Capa de aplicación y la API común (TaskItemManager).

Pantalla principal

La pantalla principal consta de una subclase Activity HomeScreen y el archivo HomeScreen.axml que define el diseño (posición del botón y la lista de tareas). La pantalla tiene el siguiente aspecto:

The screen looks like this

El código de pantalla principal define los controladores para hacer clic en el botón y hacer clic en elementos de la lista, así como rellenar la lista en el método OnResume (de modo que refleje los cambios realizados en la pantalla Detalles de la tarea). Los datos se cargan mediante las capas de negocio TaskItemManager y TaskListAdapter desde la capa de aplicación.

Pantalla de detalles de la tarea

La pantalla Detalles de la tarea también consta de una subclase Activity y un archivo de diseño AXML. El diseño determina la ubicación de los controles de entrada y la clase de C# define el comportamiento para cargar y guardar objetos TaskItem.

The class defines the behavior to load and save TaskItem objects

Todas las referencias a la biblioteca PCL se encuentran a través de la clase TaskItemManager.

Aplicación de Windows Phone

El proyecto completo de Windows Phone:

Windows Phone App The complete Windows Phone project

En el diagrama siguiente se presentan las clases agrupadas en capas:

This diagram presents the classes grouped into layers

Referencias

El proyecto específico de la plataforma debe hacer referencia a las bibliotecas específicas de la plataforma necesarias (como Microsoft.Phone y System.Windows) para crear una aplicación válida de Windows Phone.

También debe hacer referencia al proyecto PCL (por ejemplo, TaskyPortableLibrary) para utilizar la clase TaskItem y la base de datos.

TaskyPortableLibrary to utilize the TaskItem class and database

Capa de aplicación (AL)

De nuevo, al igual que con las versiones de iOS y Android, la capa de aplicación consta de los elementos no visuales que ayudan a enlazar datos a la interfaz de usuario.

ViewModels

ViewModels encapsula los datos de PCL (TaskItemManager) y lo presenta de forma que el enlace de datos Silverlight/XAML pueda consumirlo. Este es un ejemplo de comportamiento específico de la plataforma (como se describe en el documento Aplicaciones multiplataforma).

Interfaz de usuario (UI)

XAML tiene una funcionalidad de enlace de datos única que se puede declarar en marcado y reducir la cantidad de código necesario para mostrar objetos:

  1. Páginas: los archivos XAML y su código se definen la interfaz de usuario y hacen referencia a ViewModels y el proyecto PCL para mostrar y recopilar datos.
  2. Imágenes: la pantalla de presentación, el fondo y las imágenes de icono son una parte clave de la interfaz de usuario.

MainPage

La clase MainPage usa TaskListViewModel para mostrar datos mediante las características de enlace de datos de XAML. DataContext de la página se establece en el modelo de vista, que se rellena de forma asincrónica. La sintaxis de {Binding} del XAML determina cómo se muestran los datos.

TaskDetailsPage

Cada tarea se muestra enlazando TaskViewModel al XAML definido en TaskDetailsPage.xaml. Los datos de la tarea se recuperan a través de TaskItemManager en la capa de negocio.

Results

Las aplicaciones resultantes tienen este aspecto en cada plataforma:

iOS

La aplicación usa el diseño de la interfaz de usuario estándar de iOS, como el botón "agregar" que se coloca en la barra de navegación y usa el icono integrado más (+). También usa el comportamiento predeterminado UINavigationController del botón "Atrás" y admite "deslizar el dedo a eliminar" en la tabla.

It also uses the default UINavigationController back button behavior and supports swipe-to-delete in the tableIt also uses the default UINavigationController back button behavior and supports swipe-to-delete in the table

Android

La aplicación Android usa controles integrados, incluido el diseño integrado para las filas que requieren una "graduación" mostrada. El comportamiento de retroceso del hardware o sistema se admite además de un botón atrás en pantalla.

The hardware/system back behavior is supported in addition to an on-screen back buttonThe hardware/system back behavior is supported in addition to an on-screen back button

Windows Phone

La aplicación de Windows Phone usa el diseño estándar, rellenando la barra de aplicaciones en la parte inferior de la pantalla en lugar de una barra de navegación en la parte superior.

The Windows Phone app uses the standard layout, populating the app bar at the bottom of the screen instead of a nav bar at the topThe Windows Phone app uses the standard layout, populating the app bar at the bottom of the screen instead of a nav bar at the top

Resumen

Este documento ha proporcionado una explicación detallada de cómo se han aplicado los principios del diseño de aplicaciones en capas a una aplicación sencilla para facilitar el uso de código en tres plataformas móviles: iOS, Android y Windows Phone.

Ha descrito el proceso usado para diseñar las capas de aplicación y ha analizado qué código y funcionalidad se ha implementado en cada capa.

El código se puede descargar desde github.