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).
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'.
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.
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.
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 TaskItems
o .
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:
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 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 noUITableView
. 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' oTaskItem
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:
- 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
. - 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. - 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.
Os dois principais métodos relacionados à exibição e interação com a lista de tarefas são:
- PopulateTable – Usa o método da
TaskManager.GetTasks
Camada de Negócios para recuperar uma coleção deTaskItem
objetos a serem exibidos. - 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.Dialog
a 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 :
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:
- 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 daTaskDialog
classe. Informações adicionais, como o texto da marca d'água para as caixas de entrada, são implementadas com atributos nas propriedades. - SaveTask – Esse método é referenciado na classe por meio de
TaskDialog
umOnTap
atributo. Ele é chamado quando Salvar é pressionado e usa aMonoTouch.Dialog.BindingContext
para recuperar os dados inseridos pelo usuário antes de salvar as alterações usandoTaskItemManager
. - DeleteTask – esse método é referenciado na classe por meio de
TaskDialog
umOnTap
atributo. Ele usaTaskItemManager
para excluir os dados usando a chave primária (propriedade ID).
Aplicativo Android
O projeto completo do Xamarin.Android é ilustrado abaixo:
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.
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 ListView
arquivo . O adaptador controla qual layout é usado para cada item da lista – nesse caso, o código usa um layout SimpleListItemChecked
integrado 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:
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.
Todas as referências à biblioteca PCL são feitas por meio da TaskItemManager
classe.
Aplicativo para Windows Phone
O projeto completo do Windows Phone:
O diagrama abaixo 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) TaskyPortableLibrary
para utilizar a classe e o TaskItem
banco de dados.
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:
- 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.
- 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.
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.
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.
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.