Parte 4 – Lidar com várias plataformas

Manipulando recursos de divergência de plataforma &

A divergência não é apenas um problema de "plataforma cruzada"; dispositivos na "mesma" plataforma têm capacidades diferentes (especialmente a grande variedade de dispositivos Android que estão disponíveis). O mais óbvio e básico é o tamanho da tela, mas outros atributos do dispositivo podem variar e exigir que um aplicativo verifique determinados recursos e se comporte de forma diferente com base em sua presença (ou ausência).

Isso significa que todos os aplicativos precisam lidar com a degradação graciosa da funcionalidade, ou então apresentar um conjunto de recursos pouco atraente e de menor denominador comum. A integração profunda do Xamarin com os SDKs nativos de cada plataforma permite que os aplicativos aproveitem a funcionalidade específica da plataforma, portanto, faz sentido projetar aplicativos para usar esses recursos.

Consulte a documentação de Recursos da plataforma para obter uma visão geral de como as plataformas diferem em funcionalidade.

Exemplos de divergência de plataforma

Elementos fundamentais que existem entre plataformas

Existem algumas características dos aplicativos móveis que são universais. Estes são conceitos de nível superior que geralmente são verdadeiros para todos os dispositivos e, portanto, podem formar a base do design do seu aplicativo:

  • Seleção de recursos por meio de guias ou menus
  • Listas de dados e rolagem
  • Exibições únicas de dados
  • Editando exibições únicas de dados
  • Navegando de volta

Ao projetar seu fluxo de tela de alto nível, você pode basear uma experiência de usuário comum nesses conceitos.

Atributos específicos da plataforma

Além dos elementos básicos que existem em todas as plataformas, você precisará abordar as principais diferenças de plataforma em seu design. Talvez seja necessário considerar (e escrever código especificamente para lidar) essas diferenças:

  • Tamanhos de tela – Algumas plataformas (como iOS e versões anteriores do Windows Phone) têm tamanhos de tela padronizados que são relativamente simples de segmentar. Os dispositivos Android têm uma grande variedade de dimensões de tela, que exigem mais esforço para suportar em seu aplicativo.
  • Metáforas de navegação – Diferem entre plataformas (por exemplo, botão 'voltar' de hardware, controle de interface do usuário Panorama) e dentro de plataformas (Android 2 e 4, iPhone vs iPad).
  • Teclados – Alguns dispositivos Android têm teclados físicos , enquanto outros têm apenas um teclado de software. O código que detecta quando um teclado virtual está obscurecendo parte da tela precisa ser sensível a essas diferenças.
  • Toque e gestos – O suporte do sistema operacional para reconhecimento de gestos varia, especialmente em versões mais antigas de cada sistema operacional. As versões anteriores do Android têm suporte muito limitado para operações de toque, o que significa que o suporte a dispositivos mais antigos pode exigir código separado
  • Notificações push – Existem diferentes recursos/implementações em cada plataforma (por exemplo. Blocos dinâmicos no Windows).

Recursos específicos do dispositivo

Determinar quais devem ser os recursos mínimos exigidos para o aplicativo; ou quando decidir quais recursos adicionais aproveitar em cada plataforma. O código será necessário para detectar recursos e desativar a funcionalidade ou oferecer alternativas (por exemplo, uma alternativa à geolocalização pode ser permitir que o usuário digite um local ou escolha a partir de um mapa):

  • Câmera – A funcionalidade difere entre os dispositivos: alguns dispositivos não têm câmera, outros têm câmeras frontais e traseiras. Algumas câmeras são capazes de gravar vídeo.
  • Geo-localização & mapas – O suporte para localização GPS ou Wi-Fi não está presente em todos os dispositivos. Os aplicativos também precisam atender aos diferentes níveis de precisão suportados por cada método.
  • Acelerômetro, giroscópio e bússola – Esses recursos geralmente são encontrados em apenas uma seleção de dispositivos em cada plataforma, então os aplicativos quase sempre precisam fornecer um fallback quando o hardware não é suportado.
  • Twitter e Facebook – apenas "integrados" no iOS5 e iOS6, respectivamente. Em versões anteriores e outras plataformas, você precisará fornecer suas próprias funções de autenticação e interface diretamente com a API de cada serviço.
  • Near Field Communications (NFC) – Apenas em (alguns) telefones Android (no momento em que este artigo foi escrito).

Lidando com divergências de plataforma

Existem duas abordagens diferentes para oferecer suporte a várias plataformas a partir da mesma base de código, cada uma com seu próprio conjunto de benefícios e desvantagens.

  • Abstração de plataforma – padrão de fachada de negócios, fornece um acesso unificado entre plataformas e abstrai as implementações de plataforma específicas em uma única API unificada.
  • Implementação divergente – Invocação de recursos específicos da plataforma por meio de implementações divergentes por meio de ferramentas arquitetônicas, como interfaces e herança ou compilação condicional.

Abstração de plataforma

Abstração de classe

Usando interfaces ou classes base definidas no código compartilhado e implementadas ou estendidas em projetos específicos da plataforma. Escrever e estender código compartilhado com abstrações de classe é particularmente adequado para bibliotecas de classes portáteis porque elas têm um subconjunto limitado da estrutura disponível para elas e não podem conter diretivas de compilador para oferecer suporte a ramificações de código específicas da plataforma.

Interfaces

O uso de interfaces permite que você implemente classes específicas da plataforma que ainda podem ser passadas para suas bibliotecas compartilhadas para aproveitar o código comum.

A interface é definida no código compartilhado e passada para a biblioteca compartilhada como um parâmetro ou propriedade.

Os aplicativos específicos da plataforma podem então implementar a interface e ainda aproveitar o código compartilhado para "processá-lo".

Vantagens

A implementação pode conter código específico da plataforma e até mesmo bibliotecas externas específicas da plataforma de referência.

Desvantagens

Ter que criar e passar implementações para o código compartilhado. Se a interface for usada profundamente dentro do código compartilhado, ela acabará sendo passada através de vários parâmetros de método ou empurrada para baixo através da cadeia de chamadas. Se o código compartilhado usa muitas interfaces diferentes, todas elas devem ser criadas e definidas no código compartilhado em algum lugar.

Herança

O código compartilhado pode implementar classes abstratas ou virtuais que podem ser estendidas em um ou mais projetos específicos da plataforma. Isso é semelhante ao uso de interfaces, mas com algum comportamento já implementado. Há diferentes pontos de vista sobre se interfaces ou herança são uma melhor escolha de design: em particular, como o C# só permite herança única, ele pode ditar a maneira como suas APIs podem ser projetadas no futuro. Use a herança com cautela.

As vantagens e desvantagens das interfaces se aplicam igualmente à herança, com a vantagem adicional de que a classe base pode conter algum código de implementação (talvez uma implementação independente de plataforma inteira que pode ser opcionalmente estendida).

Xamarin.Forms

Consulte a documentação do Xamarin.Forms .

Outras bibliotecas multiplataforma

Essas bibliotecas também oferecem funcionalidade de plataforma cruzada para desenvolvedores de C#:

Compilação Condicional

Existem algumas situações em que seu código compartilhado ainda precisará funcionar de forma diferente em cada plataforma, possivelmente acessando classes ou recursos que se comportam de forma diferente. A compilação condicional funciona melhor com projetos de ativos compartilhados, onde o mesmo arquivo de origem está sendo referenciado em vários projetos que têm símbolos diferentes definidos.

Os projetos Xamarin sempre definem __MOBILE__ o que é verdadeiro para projetos de aplicativos iOS e Android (observe o sublinhado duplo pré e pós-correção nesses símbolos).

#if __MOBILE__
// Xamarin iOS or Android-specific code
#endif

iOS

Xamarin.iOS define __IOS__ o que você pode usar para detectar dispositivos iOS.

#if __IOS__
// iOS-specific code
#endif

Há também símbolos específicos do relógio e da TV:

#if __TVOS__
// tv-specific stuff
#endif

#if __WATCHOS__
// watch-specific stuff
#endif

Android

O código que só deve ser compilado em aplicativos Xamarin.Android pode usar o seguinte:

#if __ANDROID__
// Android-specific code
#endif

Cada versão da API também define uma nova diretiva de compilador, portanto, um código como esse permitirá que você adicione recursos se APIs mais recentes forem direcionadas. Cada nível de API inclui todos os símbolos de nível 'inferior'. Esse recurso não é realmente útil para suportar várias plataformas; Normalmente, o __ANDROID__ símbolo será suficiente.

#if __ANDROID_11__
// code that should only run on Android 3.0 Honeycomb or newer
#endif

Mac

O Xamarin.Mac define __MACOS__ o que você pode usar para compilar somente para macOS:

#if __MACOS__
// macOS-specific code
#endif

UWP (Plataforma Universal do Windows)

Use WINDOWS_UWP. Não há sublinhados em torno da corda como os símbolos da plataforma Xamarin.

#if WINDOWS_UWP
// UWP-specific code
#endif

Usando compilação condicional

Um exemplo simples de estudo de caso de compilação condicional é definir o local do arquivo para o arquivo de banco de dados SQLite. As três plataformas têm requisitos ligeiramente diferentes para especificar o local do arquivo:

  • iOS – A Apple prefere que os dados de não usuários sejam colocados em um local específico (o diretório Biblioteca), mas não há uma constante do sistema para esse diretório. O código específico da plataforma é necessário para criar o caminho correto.
  • Android – O caminho do sistema retornado por Environment.SpecialFolder.Personal é um local aceitável para armazenar o arquivo de banco de dados.
  • Windows Phone – O mecanismo de armazenamento isolado não permite que um caminho completo seja especificado, apenas um caminho relativo e um nome de arquivo.
  • Plataforma Universal do Windows – Usa Windows.Storage APIs.

O código a seguir usa compilação condicional para garantir que o DatabaseFilePath está correto para cada plataforma:

public static string DatabaseFilePath
{
    get
    {
        var filename = "TodoDatabase.db3";
#if SILVERLIGHT
        // Windows Phone 8
        var path = filename;
#else

#if __ANDROID__
        string libraryPath = Environment.GetFolderPath(Environment.SpecialFolder.Personal);
#else
#if __IOS__
        // 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");
#else
        // UWP
        string libraryPath = Windows.Storage.ApplicationData.Current.LocalFolder.Path;
#endif
#endif
        var path = Path.Combine(libraryPath, filename);
#endif
        return path;
    }
}

O resultado é uma classe que pode ser criada e usada em todas as plataformas, colocando o arquivo de banco de dados SQLite em um local diferente em cada plataforma.