Caso práctico de aplicaciones multiplataforma: Tasky
Tasky Portable 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).
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".
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.
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.
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:
Las clases se muestran en este diagrama, agrupadas en capas.
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í:
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 enUITableView
. 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 objetoTaskItem
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:
- 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
. - 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. - 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.
Los dos métodos principales relacionados con la visualización e interacción con la lista de tareas son:
- PopulateTable: usa el método
TaskManager.GetTasks
la capa de negocio para recuperar una colección de objetosTaskItem
que se van a mostrar. - 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:
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:
- 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 claseTaskDialog
. La información adicional, como el texto de la marca de agua de los cuadros de entrada, se implementa con atributos en las propiedades. - SaveTask: se hace referencia a este método en la clase
TaskDialog
a través de un atributoOnTap
. Se llama cuando se presiona Guardar y usaMonoTouch.Dialog.BindingContext
para recuperar los datos especificados por el usuario antes de guardar los cambios medianteTaskItemManager
. - DeleteTask: se hace referencia a este método en la clase
TaskDialog
a través de un atributoOnTap
. UsaTaskItemManager
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:
Diagrama de clases, con clases agrupadas por capa:
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.
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:
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
.
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:
En el diagrama siguiente se presentan las clases agrupadas en capas:
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.
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:
- 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.
- 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.
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.
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.
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.