Estudo de caso de aplicativo multiplataforma: Tasky

Tasky Portable é um aplicativo simples de lista de tarefas. Este documento discute como ele foi projetado e construído, seguindo as diretrizes do documento Criando aplicativos multiplataforma. A discussão abrange as seguintes áreas:

Processo de design

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

Requisitos

A primeira etapa no design de um aplicativo é identificar os recursos desejados. Podem ser metas de alto nível ou casos de uso detalhados. Tasky tem requisitos funcionais diretos:

  • 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 aproveitar as vantagens do geofencing do iOS ou do Windows Phone Live Tiles? Mesmo que você não use recursos específicos da plataforma na primeira versão, você deve planejar com antecedência para garantir que suas camadas de negócios e dados possam acomodá-los.

Design da interface do usuário

Comece com um design de alto nível que pode ser implementado nas plataformas de destino. Tome 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 da tela usando a ferramenta de sua escolha (trabalhos em papel).

Desenhe o fluxo da tela usando a ferramenta de sua escolha de trabalhos de papel

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.

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 for acordado, considere como ele pode ser implementado como um aplicativo multiplataforma. Isso se tornará a arquitetura do aplicativo. Seguindo as diretrizes no documento Criando aplicativos multiplataforma, o código do aplicativo deve ser dividido nas seguintes partes:

  • Código comum – 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'.

Projetos específicos da plataforma implementam uma interface do usuário nativa para cada sistema operacional, utilizando o código comum como 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, código de banco de dados e contratos, é colocado no projeto da biblioteca.

O projeto PCL completo é 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.

Quando implantado, cada aplicativo nativo fará referência a essa biblioteca

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

As classes TaskItemManager e TaskItem representam a API exposta aos aplicativos específicos da plataforma

O uso de namespaces para separar as camadas ajuda a gerenciar referências entre cada camada. Os projetos específicos da plataforma só precisam incluir uma using declaraçã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. Por causa disso, há limitações sobre quais pacotes e bibliotecas de estrutura podem ser usados. Por exemplo, o Xamarin.iOS não dá suporte à 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 você deve 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 do Xamarin. Quando os aplicativos são compilados, o processo de vinculação remove 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 dos dados – seja em 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 conta com o pacote NuGet Sqlite-net (publicado por Frank Krueger) para inserir o código SQLite-NET que fornece uma interface de banco de dados ORM (Mapeamento Relacional de Objeto). A TaskItemDatabase classe herda SQLiteConnection e adiciona os métodos CRUD (Criar, Ler, Atualizar, Excluir) necessários para ler e gravar dados no SQLite. É uma implementação simples de métodos CRUD genéricos que podem 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 em todas 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 o 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 de Camada de Negócios que implemente 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 implemente 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 o 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 de interface do usuário pode tentar ler o banco de dados ao mesmo tempo em que um thread em segundo plano o está atualizando). 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 objetos sejam criados, excluídos, recuperados e atualizados.

Usando a compilação condicional

A classe usa compilação condicional para definir o local do arquivo - este é um exemplo de implementação da Divergência de Plataforma. A propriedade que retorna o caminho é compilada para um código diferente em cada plataforma. O código e as diretivas do 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á "<app path>/Library/TaskDB.db3" para iOS, "<app path>/Documents/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. No Tasky, o Modelo é a TaskItem classe e TaskItemManager implementa o padrão Fachada para fornecer uma API para gerenciar TaskItemso .

Fachada

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

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 atendidas antes que um objeto seja salvo.

API para código específico da plataforma

Depois que o código comum for escrito, a interface do usuário deverá 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 maneira diferente, usando diferentes nomes de arquivo, 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 algumas classes necessárias para implementar o aplicativo iOS Tasky usando o projeto PCL comum para armazenar e recuperar dados. O projeto completo do iOS Xamarin.iOS é mostrado abaixo:

O projeto iOS é mostrado aqui

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

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

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:

A lista de referências é mostrada aqui

A camada de aplicativo 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 "associar" 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 associar 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 deslizar para excluir no UITableView . Deslizar para excluir é comum no iOS, mas não no Android ou no Windows Phone, portanto, o projeto específico do iOS é o único que o implementa.
  • TaskDialog – Essa classe é usada para associar uma única tarefa à interface do usuário. Ele usa a MonoTouch.Dialog API de reflexão para 'encapsular' o TaskItem objeto com uma classe que contém os atributos corretos para permitir que a 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 OnTap atributos exigem um nome de método – esses métodos devem existir na classe em que o MonoTouch.Dialog.BindingContext é criado (nesse caso, a classe discutida HomeScreen 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 podem ter sido projetadas usando o Construtor de Interfaces do Xcode ou o designer de storyboard.
  3. Imagens – Os elementos visuais são uma parte importante de todas as aplicações. Tasky tem imagens de tela inicial e ícones, que para iOS devem ser fornecidas em resolução regular e Retina.

Tela de Início

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

Ele herda de DialogViewController e implementa o código para definir a Raiz para conter uma coleção de objetos TaskItem para exibição

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 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 da para exibir a tela, portanto, não UIViewController há implementação. Em vez disso, a HomeScreen classe instancia e exibe um DialogViewController usando a TaskDialog classe da Camada de Aplicativo.

Esta captura de tela mostra uma tela vazia que demonstra o Entry atributo que define o texto da marca d'água nos campos Nome e Notas :

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

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 suportam a 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 e tipos de propriedade 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 na classe por meio de TaskDialog 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 .
  3. DeleteTask – esse método é referenciado na classe por meio de TaskDialog um OnTap atributo. Ele usa TaskItemManager para excluir os dados usando a chave primária (propriedade ID).

Aplicativo Android

O projeto completo do Xamarin.Android é ilustrado abaixo:

O projeto Android é retratado aqui

O diagrama de classes, com classes agrupadas por camada:

O diagrama de classes, com classes agrupadas por camada

Referências

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

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 para acessar os dados comuns e o código da camada de negócios

Camada de aplicação (AL)

Semelhante à versão do iOS que vimos 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 da lista – nesse caso, o código usa um layout SimpleListItemCheckedintegrado 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 da célula de linha implementados como arquivos AXML. O AXML pode ser escrito à mão ou disposto visualmente usando o Designer de Interface do Usuário do Xamarin para Android.
  • Recursos/Desenháveis – imagens (ícones) e botão personalizado.
  • Telas – Subclasses de atividade que definem cada tela e seu comportamento. Une a interface do usuário com as classes da camada de aplicativo e a API comum (TaskItemManager).

Tela de Início

A tela inicial consiste em uma subclasse HomeScreen de atividade e o HomeScreen.axml arquivo que define o layout (posição do botão e lista de tarefas). A tela fica assim:

A tela fica assim

O código da tela inicial define os manipuladores para clicar no botão e clicar em itens na 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 usando a camada TaskItemManager de negócios e a TaskListAdapter camada de aplicativos.

Tela Detalhes da Tarefa

A tela 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.

A classe define o comportamento para carregar e salvar objetos TaskItem

Todas as referências à biblioteca PCL são feitas por meio da TaskItemManager classe.

Aplicativo para Windows Phone

O projeto completo do Windows Phone:

Aplicativo para Windows Phone O projeto completo do Windows Phone

O diagrama abaixo apresenta as classes agrupadas em camadas:

Este diagrama apresenta as classes agrupadas em camadas

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) TaskyPortableLibrarypara utilizar a classe e o TaskItem banco de dados.

TaskyPortableLibrary para utilizar a classe e o banco de dados TaskItem

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 encapsulam dados do PCL ( TaskItemManager) e os apresentam de forma que possam ser consumidos pela associação de dados Silverlight/XAML. Este é um exemplo de comportamento específico da plataforma (conforme discutido no documento Aplicativos multiplataforma).

IU (Interface do Usuário)

O XAML tem uma funcionalidade exclusiva de associação de dados que pode ser declarada na marcação e reduzir a quantidade de código necessária para exibir objetos:

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

MainPage

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

Página de detalhes da tarefa

Cada tarefa é exibida associando 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 são semelhantes a este em cada plataforma:

iOS

O aplicativo usa o design de interface do usuário padrão do iOS, como o botão 'adicionar' 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 dá suporte a 'deslizar para excluir' na tabela.

Ele também usa o comportamento padrão do botão Voltar UINavigationController e dá suporte a deslizar para excluir na tabela Ele também usa o comportamento padrão do botão Voltar UINavigationController e dá suporte a deslizar para excluir na tabela

Android

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

O comportamento de retorno de hardware/sistema é suportado, além de um botão Voltar na telaO comportamento de retorno de hardware/sistema é suportado, além de um botão Voltar na tela

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.

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

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 aplicação e discutiu qual código e funcionalidade foram implementados em cada camada.

O código pode ser baixado do github.