Estudo de caso de aplicativo de plataforma cruzada: Tasky

TaskyPortable é um aplicativo de lista de tarefas simples. Este documento discute como ele foi projetado e criado, seguindo as orientações do documento Building Cross-Platform Applications . A discussão abrange as seguintes áreas:

Processo de Projeto

É aconselhável criar um roteiro para o que você deseja alcançar antes de começar a codificar. Isso é especialmente verdadeiro para o desenvolvimento entre plataformas, onde você está criando funcionalidades que serão expostas de várias maneiras. Começar com uma ideia clara do que você está construindo economiza tempo e esforço mais tarde no ciclo de desenvolvimento.

Requisitos

O primeiro passo para projetar um aplicativo é identificar os recursos desejados. Podem ser metas de alto nível ou casos de uso detalhados. Tasky tem requisitos funcionais simples:

  • Exibir uma lista de tarefas
  • Adicionar, editar e excluir tarefas
  • Definir o status de uma tarefa como 'concluído'

Você deve considerar o uso de recursos específicos da plataforma. O Tasky pode tirar proveito da cerca geográfica do iOS ou do Windows Phone Live Tiles? Mesmo que você não use recursos específicos da plataforma na primeira versão, planeje com antecedência para garantir que suas camadas de dados do Business & possam acomodá-los.

Design da interface do usuário

Comece com um design de alto nível que pode ser implementado em todas as plataformas de destino. Tenha cuidado para observar as restrições de interface do usuário específicas da plataforma. Por exemplo, um TabBarController no iOS pode exibir mais de cinco botões, enquanto o equivalente do Windows Phone pode exibir até quatro. Desenhe o fluxo de tela usando a ferramenta de sua escolha (trabalhos em papel).

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

Modelo de dados

Saber quais dados precisam ser armazenados ajudará a determinar qual mecanismo de persistência usar. Consulte Acesso a dados entre plataformas para obter informações sobre os mecanismos de armazenamento disponíveis e ajudar a decidir entre eles. Para este projeto, usaremos SQLite.NET.

O Tasky precisa armazenar três propriedades para cada 'TaskItem':

  • Name – Cadeia de caracteres
  • Notas – String
  • Feito – Booleano

Funcionalidade principal

Considere a API que a interface do usuário precisará consumir para atender aos requisitos. Uma lista de tarefas requer as seguintes funções:

  • Listar todas as tarefas – para exibir a lista da tela principal de todas as tarefas disponíveis
  • Obter uma tarefa – quando uma linha de tarefa é tocada
  • Salvar uma tarefa – quando uma tarefa é editada
  • Excluir uma tarefa – quando uma tarefa é excluída
  • Criar tarefa vazia – quando uma nova tarefa é criada

Para obter a reutilização de código, essa API deve ser implementada uma vez na Biblioteca de Classes Portátil.

Implementação

Depois que o design do aplicativo tiver sido acordado, considere como ele pode ser implementado como um aplicativo multiplataforma. Isso se tornará a arquitetura do aplicativo. Seguindo as orientações do documento Building Cross-Platform Applications, o código do aplicativo deve ser dividido nas seguintes partes:

  • Common Code – um projeto comum que contém código reutilizável para armazenar os dados da tarefa, expor uma classe Model e uma API para gerenciar o salvamento e o carregamento de dados.
  • Código específico da plataforma – projetos específicos da plataforma que implementam uma interface do usuário nativa para cada sistema operacional, utilizando o código comum como o 'back-end'.

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

Essas duas partes são descritas nas seções a seguir.

Código comum (PCL)

O Tasky Portable usa a estratégia Portable Class Library para compartilhar código comum. Consulte o documento Opções de código de compartilhamento para obter uma descrição das opções de compartilhamento de código.

Todo o código comum, incluindo a camada de acesso a dados, o código do banco de dados e os contratos, é colocado no projeto de biblioteca.

O projeto completo da PCL é ilustrado abaixo. Todo o código na biblioteca portátil é compatível com cada plataforma de destino. Quando implantado, cada aplicativo nativo fará referência a essa biblioteca.

When deployed, each native app will reference that library

O diagrama de classes abaixo mostra as classes agrupadas por camada. A SQLiteConnection classe é o código clichê do pacote Sqlite-NET. O resto das classes são código personalizado para Tasky. As TaskItemManager classes e TaskItem representam a API exposta aos aplicativos específicos da plataforma.

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

Usar namespaces para separar as camadas ajuda a gerenciar referências entre cada camada. Os projetos específicos da plataforma só devem precisar incluir uma using instrução para a Camada de Negócios. A Camada de Acesso a Dados e a Camada de Dados devem ser encapsuladas pela API exposta na TaskItemManager Camada de Negócios.

Referências

As bibliotecas de classes portáteis precisam ser utilizáveis em várias plataformas, cada uma com níveis variados de suporte para recursos de plataforma e estrutura. Devido a isso, há limitações sobre quais pacotes e bibliotecas de estrutura podem ser usados. Por exemplo, o Xamarin.iOS não suporta a palavra-chave c# dynamic , portanto, uma biblioteca de classes portátil não pode usar nenhum pacote que dependa de código dinâmico, mesmo que esse código funcione no Android. O Visual Studio para Mac impedirá que você adicione pacotes e referências incompatíveis, mas convém manter as limitações em mente para evitar surpresas mais tarde.

Observação: você verá que seus projetos fazem referência a bibliotecas de estrutura que você não usou. Essas referências são incluídas como parte dos modelos de projeto Xamarin. Quando os aplicativos são compilados, o processo de vinculação removerá o código não referenciado, portanto, mesmo que System.Xml tenha sido referenciado, ele não será incluído no aplicativo final porque não estamos usando nenhuma função Xml.

Camada de dados (DL)

A Camada de Dados contém o código que faz o armazenamento físico de dados – seja para um banco de dados, arquivos simples ou outro mecanismo. A camada de dados Tasky consiste em duas partes: a biblioteca SQLite-NET e o código personalizado adicionado para conectá-la.

Tasky depende do pacote NuGet Sqlite-net (publicado por Frank Krueger) para incorporar código SQLite-NET que fornece uma interface de banco de dados ORM (Mapeamento Relacional de Objeto). A TaskItemDatabase classe herda e SQLiteConnection adiciona os métodos Create, Read, Update, Delete (CRUD) necessários para ler e gravar dados no SQLite. É uma simples implementação clichê de métodos CRUD genéricos que poderiam ser reutilizados em outros projetos.

O TaskItemDatabase é um singleton, garantindo que todo o acesso ocorra na mesma instância. Um bloqueio é usado para impedir o acesso simultâneo de vários threads.

SQLite no Windows Phone

Embora o iOS e o Android sejam fornecidos com o SQLite como parte do sistema operacional, o Windows Phone não inclui um mecanismo de banco de dados compatível. Para compartilhar código entre as três plataformas, é necessária uma versão nativa do Windows Phone do SQLite. Consulte Trabalhando com um banco de dados local para obter mais informações sobre como configurar seu projeto do Windows Phone para Sqlite.

Usando uma interface para generalizar o acesso a dados

A Camada de Dados usa uma dependência para BL.Contracts.IBusinessIdentity que possa implementar métodos abstratos de acesso a dados que exigem uma chave primária. Qualquer classe da Camada de Negócios que implementa a interface pode ser mantida na Camada de Dados.

A interface apenas especifica uma propriedade inteira para atuar como a chave primária:

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

A classe base implementa a interface e adiciona os atributos SQLite-NET para marcá-la como uma chave primária de incremento automático. Qualquer classe na Camada de Negócios que implementa essa classe base pode ser mantida na Camada de Dados:

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

Um exemplo dos métodos genéricos na camada de dados que usam a interface é este GetItem<T> método:

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

Bloqueio para impedir acesso simultâneo

Um bloqueio é implementado dentro da TaskItemDatabase classe para impedir o acesso simultâneo ao banco de dados. Isso é para garantir que o acesso simultâneo de threads diferentes seja serializado (caso contrário, um componente da interface do usuário pode tentar ler o banco de dados ao mesmo tempo em que um thread em segundo plano o atualiza). Um exemplo de como o bloqueio é implementado é mostrado aqui:

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);
    }
}

A maior parte do código da camada de dados pode ser reutilizada em outros projetos. O único código específico do aplicativo na camada é a CreateTable<TaskItem> chamada no TaskItemDatabase construtor.

Camada de acesso a dados (DAL)

A TaskItemRepository classe encapsula o mecanismo de armazenamento de dados com uma API fortemente tipada que permite TaskItem que os objetos sejam criados, excluídos, recuperados e atualizados.

Usando compilação condicional

A classe usa compilação condicional para definir o local do arquivo - este é um exemplo de implementação de divergência de plataforma. A propriedade que retorna o caminho é compilada para código diferente em cada plataforma. O código e as diretivas de compilador específicas da plataforma são mostrados aqui:

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;
    }
}

Dependendo da plataforma, a saída será "caminho> do aplicativo/Biblioteca/TaskDB.db3" para iOS, "caminho> do aplicativo/Documentos/TaskDB.db3" para Android ou apenas "TaskDB.db3"<< para Windows Phone.

Camada de negócios (BL)

A Camada de Negócios implementa as classes Model e uma Fachada para gerenciá-las. Em Tasky, o Model é a classe e implementa TaskItem o padrão Façade para fornecer uma API para gerenciar TaskItems.TaskItemManager

Fachada

TaskItemManager encapsula o DAL.TaskItemRepository para fornecer os métodos Get, Save e Delete que serão referenciados pelas camadas Application e UI.

As regras de negócios e a lógica seriam colocadas aqui, se necessário – por exemplo, quaisquer regras de validação que devem ser satisfeitas antes que um objeto seja salvo.

API para código específico da plataforma

Uma vez que o código comum tenha sido escrito, a interface do usuário deve ser criada para coletar e exibir os dados expostos por ele. A TaskItemManager classe implementa o padrão Façade para fornecer uma API simples para o código do aplicativo acessar.

O código escrito em cada projeto específico da plataforma geralmente será fortemente acoplado ao SDK nativo desse dispositivo e só acessará o código comum usando a API definida pelo TaskItemManager. Isso inclui os métodos e classes de negócios que ele expõe, como TaskItem.

As imagens não são compartilhadas entre plataformas, mas adicionadas independentemente a cada projeto. Isso é importante porque cada plataforma lida com imagens de forma diferente, usando diferentes nomes de arquivos, diretórios e resoluções.

As seções restantes discutem os detalhes de implementação específicos da plataforma da interface do usuário do Tasky.

iOS App

Há apenas um punhado de classes necessárias para implementar o aplicativo iOS Tasky usando o projeto PCL comum para armazenar e recuperar dados. O projeto iOS Xamarin.iOS completo é mostrado abaixo:

iOS project is shown here

As classes são mostradas neste diagrama, agrupadas em camadas.

The classes are shown in this diagram, grouped into layers

Referências

O aplicativo iOS faz referência às bibliotecas SDK específicas da plataforma – por exemplo. Xamarin.iOS e MonoTouch.Dialog-1.

Também deve fazer referência ao TaskyPortableLibrary projeto PCL. A lista de referências é mostrada aqui:

The references list is shown here

A Camada de Aplicação e a Camada de Interface do Usuário são implementadas neste projeto usando essas referências.

Camada de Aplicação (AL)

A camada de aplicativo contém classes específicas da plataforma necessárias para 'vincular' os objetos expostos pela PCL à interface do usuário. O aplicativo específico do iOS tem duas classes para ajudar a exibir tarefas:

  • EditingSource – Essa classe é usada para vincular listas de tarefas à interface do usuário. Como MonoTouch.Dialog foi usado para a lista de tarefas, precisamos implementar esse auxiliar para habilitar a funcionalidade de passar o dedo para excluir no UITableView . Passar o dedo para excluir é comum no iOS, mas não no Android ou no Windows Phone, então o projeto específico do iOS é o único que o implementa.
  • TaskDialog – Essa classe é usada para vincular uma única tarefa à interface do usuário. Ele usa a API Reflection para 'encapsular' o TaskItem objeto com uma classe que contém os atributos corretos para permitir que a MonoTouch.Dialog tela de entrada seja formatada corretamente.

A TaskDialog classe usa MonoTouch.Dialog atributos para criar uma tela com base nas propriedades de uma classe. A classe tem esta aparência:

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 os atributos exigem um nome de método – esses métodos devem existir na classe em que OnTap o MonoTouch.Dialog.BindingContext é criado (neste caso, a HomeScreen classe discutida na próxima seção).

Camada de interface do usuário (UI)

A camada de interface do usuário consiste nas seguintes classes:

  1. AppDelegate – Contém chamadas para a API de aparência para estilizar as fontes e cores usadas no aplicativo. Tasky é um aplicativo simples, portanto, não há outras tarefas de inicialização em execução no FinishedLaunching .
  2. Telas – subclasses que UIViewController definem cada tela e seu comportamento. As telas unem a interface do usuário com as classes da camada de aplicativo e a API comum ( TaskItemManager ). Neste exemplo, as telas são criadas em código, mas poderiam ter sido projetadas usando o Construtor de Interface do Xcode ou o designer de storyboard.
  3. Imagens – Os elementos visuais são uma parte importante de cada aplicação. Tasky tem tela inicial e imagens de ícones, que para iOS devem ser fornecidos em resolução regular e Retina.

Tela de Início

A tela inicial é uma tela que exibe uma MonoTouch.Dialog lista de tarefas do banco de dados SQLite. Ele herda DialogViewController e implementa código para definir o Root para conter uma coleção de TaskItem objetos para exibição.

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

Os dois principais métodos relacionados à exibição e interação com a lista de tarefas são:

  1. PopulateTable – Usa o método da TaskManager.GetTasks Camada de Negócios para recuperar uma coleção de TaskItem objetos a serem exibidos.
  2. Selecionado – Quando uma linha é tocada, exibe a tarefa em uma nova tela.

Tela de detalhes da tarefa

Detalhes da Tarefa é uma tela de entrada que permite que as tarefas sejam editadas ou excluídas.

Tasky usa MonoTouch.Dialoga API de reflexão do para exibir a tela, portanto, não UIViewController há implementação. Em vez disso, a classe instancia e exibe um DialogViewController usando a HomeScreenTaskDialog classe da camada de aplicativo.

Esta captura de tela mostra uma tela vazia que demonstra o atributo definindo o Entry texto da marca d'água nos campos Nome e Anotações :

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

A funcionalidade da tela Detalhes da Tarefa (como salvar ou excluir uma tarefa) deve ser implementada HomeScreen na classe, pois é aqui que a MonoTouch.Dialog.BindingContext é criada. Os seguintes HomeScreen métodos oferecem suporte à tela Detalhes da Tarefa:

  1. ShowTaskDetails – Cria um MonoTouch.Dialog.BindingContext para renderizar uma tela. Ele cria a tela de entrada usando reflexão para recuperar nomes de propriedade e tipos da TaskDialog classe. Informações adicionais, como o texto da marca d'água para as caixas de entrada, são implementadas com atributos nas propriedades.
  2. SaveTask – Esse método é referenciado TaskDialog na classe por meio de um OnTap atributo. Ele é chamado quando Salvar é pressionado e usa a MonoTouch.Dialog.BindingContext para recuperar os dados inseridos pelo usuário antes de salvar as alterações usando TaskItemManager o .
  3. DeleteTask – Esse método é referenciado TaskDialog na classe por meio de um OnTap atributo. Ele usa TaskItemManager para excluir os dados usando a chave primária (propriedade ID).

Aplicativo Android

O projeto Xamarin.Android completo está na imagem abaixo:

Android project is pictured here

O diagrama de classes, com classes agrupadas por camada:

The class diagram, with classes grouped by layer

Referências

O projeto de aplicativo Android deve fazer referência ao assembly Xamarin.Android específico da plataforma para acessar classes do SDK do Android.

Ele também deve fazer referência ao projeto PCL (por exemplo. TaskyPortableLibrary) para acessar os dados comuns e o código da camada de negócios.

TaskyPortableLibrary to access the common data and business layer code

Camada de Aplicação (AL)

Semelhante à versão do iOS que analisamos anteriormente, a Camada de Aplicativo na versão do Android contém classes específicas da plataforma necessárias para 'vincular' os objetos expostos pelo Core à interface do usuário.

TaskListAdapter – para exibir uma Lista<T> de objetos, precisamos implementar um adaptador para exibir objetos personalizados em um ListViewarquivo . O adaptador controla qual layout é usado para cada item na lista – neste caso, o código usa um layout SimpleListItemCheckedinterno do Android.

IU (Interface do Usuário)

A Camada de Interface do Usuário do aplicativo Android é uma combinação de código e marcação XML.

  • Recursos/Layout – layouts de tela e o design de célula de linha implementados como arquivos AXML. O AXML pode ser escrito à mão ou visualmente usando o Xamarin UI Designer para Android.
  • Recursos/Desenhável – imagens (ícones) e botão personalizado.
  • Telas – Subclasses de atividade que definem cada tela e seu comportamento. Vincula a interface do usuário com classes de camada de aplicativo e a API comum (TaskItemManager).

Tela de Início

A tela inicial consiste em uma subclasse HomeScreen Atividade e o arquivo que define o HomeScreen.axml layout (posição do botão e da lista de tarefas). A tela tem esta aparência:

The screen looks like this

O código da Tela Início define os manipuladores para clicar no botão e clicar nos itens da lista, bem como preencher a lista no método (para que reflita OnResume as alterações feitas na Tela de Detalhes da Tarefa). Os dados são carregados TaskItemManager usando a Camada de Negócios e a TaskListAdapter Camada de Aplicativo.

Tela de detalhes da tarefa

A tela de detalhes da tarefa também consiste em uma Activity subclasse e um arquivo de layout AXML. O layout determina o local dos controles de entrada e a classe C# define o comportamento para carregar e salvar TaskItem objetos.

The class defines the behavior to load and save TaskItem objects

Todas as referências à biblioteca PCL são através da TaskItemManager classe.

Aplicativo do Windows Phone

O projeto completo do Windows Phone:

Windows Phone App The complete Windows Phone project

O diagrama abaixo apresenta as classes agrupadas em camadas:

This diagram presents the classes grouped into layers

Referências

O projeto específico da plataforma deve fazer referência às bibliotecas específicas da plataforma necessárias (como Microsoft.Phone e System.Windows) para criar um aplicativo válido do Windows Phone.

Ele também deve fazer referência ao projeto PCL (por exemplo. ) para utilizar a classe e o TaskItem banco de dados. TaskyPortableLibrary

TaskyPortableLibrary to utilize the TaskItem class and database

Camada de Aplicação (AL)

Novamente, como nas versões iOS e Android, a camada de aplicativo consiste nos elementos não visuais que ajudam a vincular dados à interface do usuário.

ViewModels

ViewModels encapsula dados da PCL ( TaskItemManager) e os apresenta de forma que possa ser consumida pela associação de dados do Silverlight/XAML. Este é um exemplo de comportamento específico da plataforma (conforme discutido no documento Aplicativos entre plataformas).

IU (Interface do Usuário)

O XAML tem um recurso exclusivo de vinculação de dados que pode ser declarado na marcação e reduzir a quantidade de código necessária para exibir objetos:

  1. Pages – arquivos XAML e seu codebehind definem a interface do usuário e fazem referência a ViewModels e ao projeto PCL para exibir e coletar dados.
  2. Imagens – Tela inicial, plano de fundo e imagens de ícones são uma parte fundamental da interface do usuário.

MainPage

A classe MainPage usa o para exibir dados usando os TaskListViewModel recursos de vinculação de dados do XAML. A página é definida como o modelo de exibição, que é preenchido de DataContext forma assíncrona. A {Binding} sintaxe no XAML determina como os dados são exibidos.

Página TaskDetails

Cada tarefa é exibida vinculando o TaskViewModel ao XAML definido no TaskDetailsPage.xaml. Os dados da tarefa são recuperados por meio do TaskItemManager na Camada de Negócios.

Resultados

Os aplicativos resultantes têm esta aparência em cada plataforma:

iOS

O aplicativo usa design de interface de usuário padrão do iOS, como o botão 'adicionar' sendo posicionado na barra de navegação e usando o ícone de adição (+) integrado. Ele também usa o comportamento padrão UINavigationController do botão 'voltar' e suporta 'deslizar para excluir' na tabela.

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

O aplicativo Android usa controles internos, incluindo o layout interno para linhas que exigem um 'tick' exibido. O comportamento de retorno do hardware/sistema é suportado, além de um botão Voltar na tela.

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

O aplicativo do Windows Phone usa o layout padrão, preenchendo a barra de aplicativos na parte inferior da tela, em vez de uma barra de navegação na 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

Resumo

Este documento forneceu uma explicação detalhada de como os princípios do design de aplicativos em camadas foram aplicados a um aplicativo simples para facilitar a reutilização de código em três plataformas móveis: iOS, Android e Windows Phone.

Ele descreveu o processo usado para projetar as camadas de aplicativo e discutiu qual funcionalidade de código & foi implementada em cada camada.

O código pode ser baixado do github.