Parte 5 – Estratégias práticas de compartilhamento de código

Esta seção fornece exemplos de como compartilhar código para cenários de aplicativos comuns.

Camada de dados

A camada de dados consiste em um mecanismo de armazenamento e métodos para ler e gravar informações. Para desempenho, flexibilidade e compatibilidade entre plataformas, o mecanismo de banco de dados SQLite é recomendado para aplicativos de plataforma cruzada Xamarin. Ele roda em uma grande variedade de plataformas, incluindo Windows, Android, iOS e Mac.

SQLite

SQLite é uma implementação de banco de dados de código aberto. A fonte e a documentação podem ser encontradas em SQLite.org. O suporte ao SQLite está disponível em cada plataforma móvel:

  • iOS – Integrado ao sistema operacional.
  • Android – Integrado ao sistema operacional desde o Android 2.2 (API Nível 10).
  • Windows – Consulte a extensão SQLite para Plataforma Universal do Windows.

Mesmo com o mecanismo de banco de dados disponível em todas as plataformas, os métodos nativos para acessar o banco de dados são diferentes. Tanto o iOS quanto o Android oferecem APIs internas para acessar o SQLite que podem ser usadas a partir do Xamarin.iOS ou Xamarin.Android, no entanto, o uso dos métodos nativos do SDK não oferece capacidade de compartilhar código (além talvez das próprias consultas SQL, supondo que sejam armazenadas como cadeias de caracteres). Para obter detalhes sobre a funcionalidade de banco de dados nativo, procure CoreData na classe do iOS ou Android, como essas opções não são multiplataforma, SQLiteOpenHelper elas estão além do escopo deste documento.

ADO.NET

Suporte ao Xamarin.iOS e Xamarin.Android System.Data e Mono.Data.Sqlite (consulte a documentação do Xamarin.iOS para obter mais informações). O uso desses namespaces permite que você escreva ADO.NET código que funcione em ambas as plataformas. Edite as referências do projeto para incluir System.Data.dll e Mono.Data.Sqlite.dll adicioná-las usando instruções ao seu código:

using System.Data;
using Mono.Data.Sqlite;

Em seguida, o seguinte código de exemplo funcionará:

string dbPath = Path.Combine (
        Environment.GetFolderPath (Environment.SpecialFolder.Personal),
        "items.db3");
bool exists = File.Exists (dbPath);
if (!exists)
    SqliteConnection.CreateFile (dbPath);
var connection = new SqliteConnection ("Data Source=" + dbPath);
connection.Open ();
if (!exists) {
    // This is the first time the app has run and/or that we need the DB.
    // Copy a "template" DB from your assets, or programmatically create one like this:
    var commands = new[]{
        "CREATE TABLE [Items] (Key ntext, Value ntext);",
        "INSERT INTO [Items] ([Key], [Value]) VALUES ('sample', 'text')"
    };
    foreach (var command in commands) {
        using (var c = connection.CreateCommand ()) {
            c.CommandText = command;
            c.ExecuteNonQuery ();
        }
    }
}
// use `connection`... here, we'll just append the contents to a TextView
using (var contents = connection.CreateCommand ()) {
    contents.CommandText = "SELECT [Key], [Value] from [Items]";
    var r = contents.ExecuteReader ();
    while (r.Read ())
        Console.Write("\n\tKey={0}; Value={1}",
                r ["Key"].ToString (),
                r ["Value"].ToString ());
}
connection.Close ();

As implementações do mundo real de ADO.NET obviamente seriam divididas em diferentes métodos e classes (este exemplo é apenas para fins de demonstração).

SQLite-NET – ORM de plataforma cruzada

Um ORM (ou Mapeador Objeto-Relacional) tenta simplificar o armazenamento de dados modelados em classes. Em vez de escrever manualmente consultas SQL que CRIAM TABELAS ou SELECT, INSERT e DELETE dados que são extraídos manualmente de campos de classe e propriedades, um ORM adiciona uma camada de código que faz isso para você. Usando a reflexão para examinar a estrutura de suas classes, um ORM pode criar automaticamente tabelas e colunas que correspondem a uma classe e gerar consultas para ler e gravar os dados. Isso permite que o código do aplicativo simplesmente envie e recupere instâncias de objeto para o ORM, que cuida de todas as operações SQL sob o capô.

SQLite-NET atua como um ORM simples que permitirá que você salve e recupere suas classes no SQLite. Ele esconde a complexidade do acesso SQLite de plataforma cruzada com uma combinação de diretivas de compilador e outros truques.

Recursos do SQLite-NET:

  • As tabelas são definidas adicionando atributos às classes Model.
  • Uma instância de banco de dados é representada por uma subclasse de SQLiteConnection , a classe principal na biblioteca SQLite-Net.
  • Os dados podem ser inseridos, consultados e excluídos usando objetos. Nenhuma instrução SQL é necessária (embora você possa escrever instruções SQL, se necessário).
  • Consultas Linq básicas podem ser executadas nas coleções retornadas pelo SQLite-NET.

O código-fonte e a documentação do SQLite-NET estão disponíveis no SQLite-Net no github e foram implementados em ambos os estudos de caso. Um exemplo simples de código SQLite-NET (do estudo de caso Tasky Pro ) é mostrado abaixo.

Primeiro, a TodoItem classe usa atributos para definir um campo para ser uma chave primária de banco de dados:

public class TodoItem : IBusinessEntity
{
    public TodoItem () {}
    [PrimaryKey, AutoIncrement]
    public int ID { get; set; }
    public string Name { get; set; }
    public string Notes { get; set; }
    public bool Done { get; set; }
}

Isso permite que uma tabela seja criada com a seguinte linha de código (e nenhuma instrução SQL) em uma TodoItemSQLiteConnection instância:

CreateTable<TodoItem> ();

Os dados na tabela também podem ser manipulados com outros métodos no SQLiteConnection (novamente, sem exigir instruções SQL):

Insert (TodoItem); // 'task' is an instance with data populated in its properties
Update (TodoItem); // Primary Key field must be populated for Update to work
Table<TodoItem>.ToList(); // returns all rows in a collection

Consulte o código-fonte do estudo de caso para obter exemplos completos.

Acesso a arquivos

O acesso a arquivos certamente será uma parte fundamental de qualquer aplicativo. Exemplos comuns de arquivos que podem fazer parte de um aplicativo incluem:

  • Arquivos de banco de dados SQLite.
  • Dados gerados pelo usuário (texto, imagens, som, vídeo).
  • Dados baixados para armazenamento em cache (imagens, arquivos html ou PDF).

System.IO Acesso Direto

Tanto o Xamarin.iOS quanto o Xamarin.Android permitem o acesso ao System.IO sistema de arquivos usando classes no namespace.

Cada plataforma tem diferentes restrições de acesso que devem ser levadas em consideração:

  • Os aplicativos iOS são executados em uma área restrita com acesso muito restrito ao sistema de arquivos. A Apple ainda dita como você deve usar o sistema de arquivos, especificando certos locais que são copiados (e outros que não são). Consulte o guia Trabalhando com o sistema de arquivos no Xamarin.iOS para obter mais detalhes.
  • O Android também restringe o acesso a certos diretórios relacionados ao aplicativo, mas também suporta mídia externa (por exemplo. cartões SD) e acesso a dados compartilhados.
  • O Windows Phone 8 (Silverlight) não permite acesso direto a arquivos – os arquivos só podem ser manipulados usando IsolatedStorageo .
  • Os projetos Windows 8.1 WinRT e Windows 10 UWP oferecem apenas operações de arquivo assíncronas por meio Windows.Storage de APIs, que são diferentes das outras plataformas.

Exemplo para iOS e Android

Um exemplo trivial que escreve e lê um arquivo de texto é mostrado abaixo. O uso Environment.GetFolderPath permite que o mesmo código seja executado no iOS e no Android, que retornam um diretório válido com base em suas convenções de sistema de arquivos.

string filePath = Path.Combine (
        Environment.GetFolderPath (Environment.SpecialFolder.Personal),
        "MyFile.txt");
System.IO.File.WriteAllText (filePath, "Contents of text file");
Console.WriteLine (System.IO.File.ReadAllText (filePath));

Consulte o documento Xamarin.iOS Trabalhando com o sistema de arquivos para obter mais informações sobre a funcionalidade do sistema de arquivos específico do iOS. Ao escrever código de acesso a arquivos entre plataformas, lembre-se de que alguns sistemas de arquivos diferenciam maiúsculas de minúsculas e têm separadores de diretório diferentes. É uma boa prática usar sempre o mesmo invólucro para nomes de arquivos e o Path.Combine() método ao construir caminhos de arquivo ou diretório.

Windows.Storage para Windows 8 e Windows 10

O livroCriando aplicativos móveis com o Xamarin.FormsCapítulo 20. Async e File I/O inclui exemplos para Windows 8.1 e Windows 10.

Usando um DependencyService é possível ler e arquivar arquivos nessas plataformas usando as APIs suportadas:

StorageFolder localFolder = ApplicationData.Current.LocalFolder;
IStorageFile storageFile = await localFolder.CreateFileAsync("MyFile.txt",
                                        CreationCollisionOption.ReplaceExisting);
await FileIO.WriteTextAsync(storageFile, "Contents of text file");

Consulte o capítulo 20 do livro para obter mais detalhes.

Armazenamento isolado no Windows Phone 7 & 8 (Silverlight)

O Armazenamento Isolado é uma API comum para salvar e carregar arquivos em todas as plataformas iOS, Android e Windows Phone mais antigas.

É o mecanismo padrão para acesso a arquivos no Windows Phone (Silverlight) que foi implementado no Xamarin.iOS e no Xamarin.Android para permitir que códigos comuns de acesso a arquivos sejam gravados. A System.IO.IsolatedStorage classe pode ser referenciada em todas as três plataformas em um projeto compartilhado.

Consulte a Visão geral do armazenamento isolado do Windows Phone para obter mais informações.

As APIs de armazenamento isolado não estão disponíveis em bibliotecas de classes portáteis. Uma alternativa para PCL é o PCLStorage NuGet

Acesso a arquivos entre plataformas em PCLs

Há também um NuGet compatível com PCL – PCLStorageque facilita o acesso a arquivos entre plataformas para plataformas compatíveis com Xamarin e as APIs mais recentes do Windows.

Operações de rede

A maioria dos aplicativos móveis terá componente de rede, por exemplo:

  • Download de imagens, vídeo e áudio (por exemplo, miniaturas, fotos, música).
  • Download de documentos (ex. HTML, PDF).
  • Carregar dados do usuário (como fotos ou texto).
  • Acesso a serviços Web ou APIs de terceiros (incluindo SOAP, XML ou JSON).

O .NET Framework fornece algumas classes diferentes para acessar recursos de rede: HttpClient, WebCliente HttpWebRequest.

HttpClient

A HttpClient classe no System.Net.Http namespace está disponível em Xamarin.iOS, Xamarin.Android e na maioria das plataformas Windows. Há um Microsoft HTTP Client Library NuGet que pode ser usado para trazer essa API para bibliotecas de classes portáteis (e Windows Phone 8 Silverlight).

var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://xamarin.com");
var response = await myClient.SendAsync(request);

WebClient

A WebClient classe fornece uma API simples para recuperar dados remotos de servidores remotos.

As operações da Plataforma Universal do Windows devem ser assíncronas, mesmo que o Xamarin.iOS e o Xamarin.Android ofereçam suporte a operações síncronas (que podem ser feitas em threads em segundo plano).

O código para uma operação assicrona WebClient simples é:

var webClient = new WebClient ();
webClient.DownloadStringCompleted += (sender, e) =>
{
    var resultString = e.Result;
    // do something with downloaded string, do UI interaction on main thread
};
webClient.Encoding = System.Text.Encoding.UTF8;
webClient.DownloadStringAsync (new Uri ("http://some-server.com/file.xml"));

WebClient também tem DownloadFileCompleted e DownloadFileAsync para recuperar dados binários.

HttpWebRequest

HttpWebRequest oferece mais personalização do que WebClient e, como resultado, requer mais código para usar.

O código para uma operação síncrona HttpWebRequest simples é:

var request = HttpWebRequest.Create(@"http://some-server.com/file.xml ");
request.ContentType = "text/xml";
request.Method = "GET";
using (HttpWebResponse response = request.GetResponse() as HttpWebResponse)
{
    if (response.StatusCode != HttpStatusCode.OK)
        Console.WriteLine("Error fetching data. Server returned status code: {0}", response.StatusCode);
    using (StreamReader reader = new StreamReader(response.GetResponseStream()))
    {
        var content = reader.ReadToEnd();
        // do something with downloaded string, do UI interaction on main thread
    }
}

Há um exemplo em nossa documentação de Serviços da Web.

Acessibilidade

Os dispositivos móveis operam sob uma variedade de condições de rede, desde conexões Wi-Fi ou 4G rápidas até áreas de recepção precárias e links de dados EDGE lentos. Por isso, é uma boa prática detectar se a rede está disponível e, em caso afirmativo, que tipo de rede está disponível, antes de tentar se conectar a servidores remotos.

As ações que um aplicativo móvel pode executar nessas situações incluem:

  • Se a rede não estiver disponível, avise o usuário. Se eles o desativaram manualmente (por exemplo. Modo avião ou desligar o Wi-Fi), então eles podem resolver o problema.
  • Se a conexão for 3G, os aplicativos podem se comportar de forma diferente (por exemplo, a Apple não permite que aplicativos maiores que 20Mb sejam baixados em 3G). Os aplicativos podem usar essas informações para avisar o usuário sobre tempos de download excessivos ao recuperar arquivos grandes.
  • Mesmo que a rede esteja disponível, é recomendável verificar a conectividade com o servidor de destino antes de iniciar outras solicitações. Isso evitará que as operações de rede do aplicativo atinjam o tempo limite repetidamente e também permitirá que uma mensagem de erro mais informativa seja exibida para o usuário.

Serviços Web

Consulte nossa documentação sobre Trabalhando com serviços Web, que aborda o acesso a pontos de extremidade REST, SOAP e WCF usando o Xamarin.iOS. É possível criar manualmente solicitações de serviço Web e analisar as respostas, no entanto, há bibliotecas disponíveis para tornar isso muito mais simples, incluindo Azure, RestSharp e ServiceStack. Até mesmo as operações básicas do WCF podem ser acessadas em aplicativos Xamarin.

Azure

O Microsoft Azure é uma plataforma de nuvem que fornece uma ampla variedade de serviços para aplicativos móveis, incluindo armazenamento e sincronização de dados e notificações por push.

Visite azure.microsoft.com para experimentá-lo gratuitamente.

RestSharp

RestSharp é uma biblioteca .NET que pode ser incluída em aplicativos móveis para fornecer um cliente REST que simplifica o acesso a serviços Web. Ele ajuda fornecendo uma API simples para solicitar dados e analisar a resposta REST. RestSharp pode ser útil

O site RestSharp contém documentação sobre como implementar um cliente REST usando RestSharp. O RestSharp fornece exemplos de Xamarin.iOS e Xamarin.Android no github.

Há também um trecho de código Xamarin.iOS em nossa documentação de Serviços da Web.

ServiceStack

Ao contrário do RestSharp, o ServiceStack é uma solução do lado do servidor para hospedar um serviço Web, bem como uma biblioteca de cliente que pode ser implementada em aplicativos móveis para acessar esses serviços.

O site ServiceStack explica a finalidade do projeto e links para exemplos de documento e código. Os exemplos incluem uma implementação completa do lado do servidor de um serviço Web, bem como vários aplicativos do lado do cliente que podem acessá-lo.

WCF

As ferramentas Xamarin podem ajudá-lo a consumir alguns serviços do Windows Communication Foundation (WCF). Em geral, o Xamarin oferece suporte ao mesmo subconjunto do lado do cliente do WCF fornecido com o tempo de execução do Silverlight. Isso inclui as implementações de protocolo e codificação mais comuns do WCF: mensagens SOAP codificadas em texto sobre o protocolo de transporte HTTP usando o BasicHttpBinding.

Devido ao tamanho e à complexidade da estrutura do WCF, pode haver implementações de serviço atuais e futuras que ficarão fora do escopo suportado pelo domínio de subconjunto de cliente do Xamarin. Além disso, o suporte ao WCF requer o uso de ferramentas disponíveis apenas em um ambiente Windows para gerar o proxy.

Threading

A capacidade de resposta dos aplicativos é importante para aplicativos móveis – os usuários esperam que os aplicativos carreguem e tenham um desempenho rápido. Uma tela 'congelada' que para de aceitar a entrada do usuário aparecerá para indicar que o aplicativo falhou, por isso é importante não vincular o thread da interface do usuário com chamadas de bloqueio de longa execução, como solicitações de rede ou operações locais lentas (como descompactar um arquivo). Em particular, o processo de inicialização não deve conter tarefas de longa execução – todas as plataformas móveis matarão um aplicativo que leva muito tempo para carregar.

Isso significa que sua interface do usuário deve implementar um 'indicador de progresso' ou uma interface do usuário 'utilizável' que seja rápida de exibir e tarefas assíncronas para executar operações em segundo plano. A execução de tarefas em segundo plano requer o uso de threads, o que significa que as tarefas em segundo plano precisam de uma maneira de se comunicar com o thread principal para indicar o progresso ou quando tiverem sido concluídas.

Biblioteca de tarefas paralelas

As tarefas criadas com a Biblioteca de Tarefas Paralelas podem ser executadas de forma assíncrona e retornar em seu thread de chamada, tornando-as muito úteis para disparar operações de longa execução sem bloquear a interface do usuário.

Uma operação de tarefa paralela simples pode ter esta aparência:

using System.Threading.Tasks;
void MainThreadMethod ()
{
    Task.Factory.StartNew (() => wc.DownloadString ("http://...")).ContinueWith (
        t => label.Text = t.Result, TaskScheduler.FromCurrentSynchronizationContext()
    );
}

A chave é TaskScheduler.FromCurrentSynchronizationContext() que reutilizará o SynchronizationContext.Current do thread chamando o método (aqui o thread principal que está sendo executado MainThreadMethod) como uma maneira de marshal chamadas de volta para esse thread. Isso significa que se o método for chamado no thread da interface do usuário, ele executará a ContinueWith operação de volta no thread da interface do usuário.

Se o código estiver iniciando tarefas de outros threads, use o seguinte padrão para criar uma referência ao thread da interface do usuário e a tarefa ainda poderá chamá-lo de volta para ele:

static Context uiContext = TaskScheduler.FromCurrentSynchronizationContext();

Invocando no thread da interface do usuário

Para código que não utiliza a Biblioteca de Tarefas Paralelas, cada plataforma tem sua própria sintaxe para empacotar operações de volta ao thread da interface do usuário:

  • iOSowner.BeginInvokeOnMainThread(new NSAction(action))
  • Androidowner.RunOnUiThread(action)
  • Xamarin.FormsDevice.BeginInvokeOnMainThread(action)
  • JanelasDeployment.Current.Dispatcher.BeginInvoke(action)

A sintaxe do iOS e do Android requer que uma classe 'context' esteja disponível, o que significa que o código precisa passar esse objeto para quaisquer métodos que exijam um retorno de chamada no thread da interface do usuário.

Para fazer chamadas de thread de interface do usuário em código compartilhado, siga o exemplo IDispatchOnUIThread (cortesia de @follesoe). Declare e programe para uma IDispatchOnUIThread interface no código compartilhado e, em seguida, implemente as classes específicas da plataforma, conforme mostrado aqui:

// program to the interface in shared code
public interface IDispatchOnUIThread {
    void Invoke (Action action);
}
// iOS
public class DispatchAdapter : IDispatchOnUIThread {
    public readonly NSObject owner;
    public DispatchAdapter (NSObject owner) {
        this.owner = owner;
    }
    public void Invoke (Action action) {
        owner.BeginInvokeOnMainThread(new NSAction(action));
    }
}
// Android
public class DispatchAdapter : IDispatchOnUIThread {
    public readonly Activity owner;
    public DispatchAdapter (Activity owner) {
        this.owner = owner;
    }
    public void Invoke (Action action) {
        owner.RunOnUiThread(action);
    }
}
// WP7
public class DispatchAdapter : IDispatchOnUIThread {
    public void Invoke (Action action) {
        Deployment.Current.Dispatcher.BeginInvoke(action);
    }
}

Os desenvolvedores do Xamarin.Forms devem usar Device.BeginInvokeOnMainThread código comum (projetos compartilhados ou PCL).

Recursos e degradação da plataforma e do dispositivo

Outros exemplos específicos de como lidar com diferentes recursos são fornecidos na documentação de Recursos da plataforma. Ele lida com a detecção de diferentes recursos e como degradar graciosamente um aplicativo para fornecer uma boa experiência ao usuário, mesmo quando o aplicativo não pode operar em todo o seu potencial.