Criando aplicativos multiplataforma com Xamarin: perspectiva de um desenvolvedor Android

Publicados: 2022-03-11

Escrever código uma vez e usá-lo em várias plataformas tem sido o sonho de muitos desenvolvedores de software. Embora isso já seja possível há algum tempo, sempre teve o custo de manutenção, facilidade de teste ou, pior ainda, experiência ruim do usuário.

O desenvolvimento de aplicativos móveis usando o SDK nativo é provavelmente o ponto de partida para todos os desenvolvedores que têm suas raízes no domínio do desenvolvimento de aplicativos de desktop. As linguagens de programação se tornariam uma barreira para alguns: se alguém tivesse experiência no desenvolvimento de aplicativos Java desktop ou back-end, mudar para uma empresa de desenvolvimento de aplicativos móveis e trabalhar com Android seria muito mais fácil do que começar com Objective-C do zero para iOS.

Sempre fui cético em relação ao desenvolvimento de aplicativos multiplataforma. Estruturas baseadas em JavaScript como Sencha, Cordova, Titanium, etc. nunca provam ser uma escolha sábia quando o desempenho é importante. A falta de APIs e a experiência do usuário peculiar foram um dado adquirido com essas estruturas.

Mas então, me deparei com o Xamarin.

Desenvolvimento multiplataforma com Xamarin

Neste artigo, você aprenderá como usar o Xamarin para compartilhar código em várias plataformas sem comprometer nenhum dos outros aspectos do desenvolvimento de aplicativos móveis. O artigo se concentrará no Android e no iOS em particular, mas você pode usar uma abordagem semelhante para adicionar suporte a qualquer outra plataforma compatível com o Xamarin.

O que é Xamarin?

Xamarin é uma plataforma de desenvolvimento que permite escrever aplicativos multiplataforma—ainda que nativos—para iOS, Android e Windows Phone em C# e .NET.

O Xamarin fornece associações C# para APIs nativas do Android e iOS. Isso lhe dá o poder de usar toda a interface de usuário nativa do Android e iOS, notificações, gráficos, animação e outros recursos do telefone, todos usando C#.

Cada nova versão do Android e iOS é correspondida pelo Xamarin, com uma nova versão que inclui associações para suas novas APIs.

A porta do .NET do Xamarin inclui recursos como tipos de dados, genéricos, coleta de lixo, consulta integrada à linguagem (LINQ), padrões de programação assíncrona, delegados e um subconjunto do Windows Communication Foundation (WCF). As bibliotecas são gerenciadas com um linger para incluir apenas os componentes referenciados.

Xamarin.Forms é uma camada sobre as outras ligações de interface do usuário e a API do Windows Phone, que fornece uma biblioteca de interface de usuário totalmente multiplataforma.

O escopo do Xamarin

Escrevendo aplicativos multiplataforma

Para escrever aplicativos multiplataforma com o Xamarin, os desenvolvedores precisam escolher um dos dois tipos de projetos disponíveis:

  • Biblioteca de classes portátil (PCL)
  • Projeto Compartilhado

O PCL permite escrever código que pode ser compartilhado entre várias plataformas, mas com uma limitação. Como nem todas as APIs .NET estão disponíveis em todas as plataformas, com um projeto PCL, você o limitará a ser executado nas plataformas para as quais é direcionado.

Conexões e limitações do Xamarin

A tabela abaixo mostra quais APIs estão disponíveis em quais plataformas:

Funcionalidade .NET Framework Aplicativos da Windows Store luz cinza telefone do Windows Xamarin
Testemunho S S S S S
LINQ S S S S S
IQueryable S S S 7,5+ S
Serialização S S S S S
Anotações de dados 4.0.3+ S S S S

Durante o processo de compilação, um PCL é compilado em DLLs separadas e carregado pelo Mono durante o tempo de execução. Uma implementação diferente da mesma interface pode ser fornecida durante o tempo de execução.

Por outro lado, os projetos compartilhados oferecem mais controle, permitindo que você escreva código específico da plataforma para cada plataforma que você deseja oferecer suporte. O código em um projeto compartilhado pode conter diretivas de compilador que habilitarão ou desabilitarão seções de código dependendo de qual projeto de aplicativo está usando o código.

Ao contrário de um PCL, um projeto compartilhado não produz nenhuma DLL. O código é incluído diretamente no projeto final.

Dando estrutura ao seu código multiplataforma com o MvvmCross

O código reutilizável pode economizar dinheiro e tempo para as equipes de desenvolvimento. No entanto, um código bem estruturado facilita muito a vida dos desenvolvedores. Ninguém aprecia um código livre de bugs bem escrito mais do que os desenvolvedores.

O Xamarin por si só fornece um mecanismo que torna muito mais fácil escrever código multiplataforma reutilizável.

Os desenvolvedores móveis estão familiarizados com cenários em que precisam escrever a mesma lógica duas vezes ou mais para oferecer suporte a iOS, Android e outras plataformas. Mas com o Xamarin, conforme explicado no capítulo anterior, é fácil reutilizar o código que foi escrito para uma plataforma para outras plataformas também.

Onde o MvvmCross se encaixa, então?

MvvmCross, como o nome pode ter sugerido, possibilita o uso do padrão MVVM em aplicativos Xamarin. Ele vem com várias bibliotecas, APIs e utilitários que são realmente úteis no desenvolvimento de aplicativos multiplataforma.

O MvvmCross pode reduzir significativamente a quantidade de código clichê que você escreveria (às vezes várias vezes em diferentes idiomas) em qualquer outra abordagem de desenvolvimento de aplicativos.

Estrutura de uma Solução MvvmCross

A comunidade MvvmCross recomenda uma forma bastante simples e eficiente de estruturar uma solução MvvmCross:

 <ProjectName>.Core <ProjectName>.UI.Droid <ProjectName>.UI.iOS

O projeto Core em uma solução MvvmCross está relacionado ao código reutilizável. O projeto Core é um projeto Xamarin PCL onde o foco principal é a reutilização.

Qualquer código escrito no Core deve ser agnóstico de plataforma da maneira máxima possível. Deve conter apenas lógica que possa ser reutilizada em todas as plataformas. O projeto Core não deve usar nenhuma API Android ou iOS nem acessar nada específico de nenhuma plataforma.

A camada de lógica de negócios, a camada de dados e a comunicação de back-end são candidatas perfeitas para serem incluídas no projeto Core. A navegação pela hierarquia de visualizações (atividades, fragmentos, etc.) será realizada no Core.

Antes de continuar, é necessário entender um padrão de projeto de arquitetura que é crucial para entender o MvvmCross e como ele funciona. Como pode ser visto pelo nome, MvvmCross depende muito do padrão MVVM.

MVVM é um padrão de projeto de arquitetura que facilita a separação da interface gráfica do usuário da lógica de negócios e dos dados de back-end.

Como esse padrão é usado no MvvmCross?

Bem, como queremos alcançar uma alta reutilização do nosso código, queremos ter o máximo que pudermos em nosso Core, que é um projeto PCL. Como as visualizações são a única parte do código que difere de uma plataforma para outra, não podemos reutilizá-las entre plataformas. Essa parte é implementada nos projetos relacionados à plataforma.

Estrutura MvvmCross

O MvvmCross nos dá a capacidade de orquestrar a navegação do aplicativo a partir do Core usando ViewModels.

Com o básico e os detalhes técnicos fora do caminho, vamos começar com o Xamarin criando nosso próprio projeto MvvmCross Core:

Criando um projeto principal MvvmCross

Abra o Xamarin Studio e crie uma solução chamada ToptalExampleSolution :

Criando a solução

Como estamos criando um projeto Core, é uma boa ideia manter a convenção de nomenclatura. Certifique-se de que o sufixo Core seja adicionado ao nome do projeto.

Para obter suporte ao MvvmCross, é necessário adicionar bibliotecas MvvmCross ao nosso projeto. Para adicionar isso, podemos usar o suporte interno para NuGet no Xamarin Studio.

Para adicionar uma biblioteca clique com o botão direito na pasta Packages e selecione a opção Add Packages… .

No campo de busca, podemos procurar por MvvmCross, que vai filtrar os resultados relacionados ao MvvmCross conforme mostrado abaixo:

Filtrando resultados

Clicar no botão Add Package irá adicioná-lo ao projeto.

Com o MvvmCross adicionado ao nosso projeto, estamos prontos para escrever nosso código Core.

Vamos definir nosso primeiro ViewModel. Para criar um, crie uma hierarquia de pastas da seguinte forma:

Hierarquia recomendada de pastas

Aqui está o que cada uma das pastas é sobre:

  • Modelos: modelos de domínio que representam o conteúdo do estado real
  • Serviços: Uma pasta que contém nosso serviço (lógica de negócios, banco de dados, etc.)
  • ViewModel: A maneira como nos comunicamos com nossos modelos

Nosso primeiro ViewModel é chamado FirstViewModel.cs

 public class FirstViewModel : MvxViewModel { private string _firstName; private string _lastName; private string _fullName; public string FirstName { get { return _firstName; } set { _lastName = value; RaisePropertyChanged(); } } public string LastName { get { return _lastName; } set { _lastName = value; RaisePropertyChanged(); } } public string FullName { get { return _fullName; } set { _fullName = value; RaisePropertyChanged(); } } public IMvxCommand ConcatNameCommand { get { return new MvxCommand(() => { FullName = $"{FirstName} {LastName}"; }); } public IMvxCommand NavigateToSecondViewModelCommand { get { return new MvxCommand(() => { ShowViewModel<SecondViewModel>(); }); } } }

Agora que temos nosso primeiro ViewModel, podemos criar nossa primeira visualização e unir as coisas.

IU do Android

Para mostrar o conteúdo do ViewModel, precisamos criar uma UI.

A primeira etapa para criar uma interface do usuário do Android é criar um projeto Android na solução atual. Para fazer isso, clique com o botão direito do mouse no nome da solução e selecione Adicionar -> Adicionar novo projeto… . No assistente, selecione o aplicativo Android e certifique-se de nomear seu projeto ToptalExample.UI.Droid .

Conforme descrito anteriormente, agora precisamos adicionar dependências MvvmCross para Android. Para fazer isso, siga as mesmas etapas do projeto Core para adicionar dependências do NuGet.

Após adicionar as dependências do MvvmCross, é necessário adicionar uma referência ao nosso projeto Core para que possamos usar nosso código escrito lá. Para adicionar uma referência ao projeto PCL, clique com o botão direito do mouse na pasta References e selecione a opção Edit References… . Na guia Projetos, selecione o projeto Core criado anteriormente e clique em OK.

Adicionando uma referência ao projeto PCL

A próxima parte pode ser um pouco difícil de entender.

Agora temos que dizer ao MvvmCross como ele deve configurar nossa aplicação. Para fazer isso, temos que criar uma classe Setup :

 namespace ToptalExample.UI.Droid { public class Setup : MvxAndroidSetup { public Setup(Context context) : base(context) { } protected override IMvxApplication CreateApp() { return new Core.App(); } } }

Como pode ser visto na classe, estamos dizendo ao MvvmCross para CreateApp baseado na implementação do Core.App , que é uma classe definida no Core e mostrada abaixo:

 public class App : MvxApplication { public override void Initialize() { RegisterAppStart(new AppStart()); } } public class AppStart : MvxNavigatingObject, IMvxAppStart { public void Start(object hint = null) { ShowViewModel<FirstViewModel>(); } }

Na classe App , estamos criando uma instância de AppStart , que mostrará nosso primeiro ViewModel.

A única coisa que resta agora é criar um arquivo de layout do Android que será vinculado ao MvvmCross:

 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:andro xmlns:local="http://schemas.android.com/apk/res-auto" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <EditText android:layout_width="match_parent" android:layout_height="match_parent" local:MvxBind="Text FirstName" /> <EditText android:layout_width="match_parent" android:layout_height="match_parent" local:MvxBind="Text LastName" /> <TextView android:layout_width="match_parent" android:layout_height="match_parent" local:MvxBind="Text FullName" /> <Button android:layout_width="match_parent" android:layout_height="wrap_content" local:MvxBind="Click ConcatNameCommand" /> <Button android:layout_width="match_parent" android:layout_height="wrap_content" local:MvxBind="Click NavigateToSecondViewModelCommand" /> </LinearLayout>

No arquivo de layout, temos ligações que são resolvidas automaticamente pelo MvvmCross. Para EditText , estamos criando uma associação para a propriedade Text, que será uma associação bidirecional. Qualquer alteração invocada do lado do ViewModel será refletida automaticamente na visualização e vice-versa.

A classe View pode ser uma atividade ou um fragmento. Para simplificar, estamos usando uma atividade que carrega o layout fornecido:

 [Activity(Label = "ToptalExample.UI.Droid", Theme = "@style/Theme.AppCompat", MainLauncher = true, Icon = "@mipmap/icon")] public class MainActivity : MvxAppCompatActivity<FirstViewModel> { protected override void OnCreate(Bundle bundle) { base.OnCreate(bundle); SetContentView(Resource.Layout.Main); } }

Para o primeiro botão, temos uma ligação de comando, o que significa que quando clicamos no botão, o MvvmCross invocará o ContactNameCommand do ViewModel.

Para o segundo botão, vamos mostrar outro ViewModel.

IU do iOS

Criar um projeto iOS não é muito diferente de criar um projeto Android. Você precisa seguir etapas semelhantes para adicionar um novo projeto, só que desta vez, em vez do Android, basta criar um projeto iOS. Apenas certifique-se de manter a convenção de nomenclatura consistente.

Depois de adicionar o projeto iOS, você precisa adicionar dependências para MvvmCross iOS. As etapas são absolutamente as mesmas do Core e do Android (clique com o botão direito do mouse em References no seu projeto iOS e clique em Add References… ).

Agora, como fizemos para o Android, é necessário criar uma classe Setup , que dirá ao MvvmCross como configurar nosso aplicativo.

 public class Setup : MvxIosSetup { public Setup(MvxApplicationDelegate appDelegate, IMvxIosViewPresenter presenter) : base(appDelegate, presenter) { } protected override MvvmCross.Core.ViewModels.IMvxApplication CreateApp() { return new App(); } }

Observe que a classe Setup agora estende MvxIosSetup e, para Android, estava estendendo MvxAndroidSetup .

Uma adição aqui é que temos que alterar nossa classe AppDelegate .

AppDelegate no iOS é responsável por iniciar a interface do usuário, então temos que dizer como as visualizações serão apresentadas no iOS. Você pode saber mais sobre os apresentadores aqui.

 [Register("AppDelegate")] public class AppDelegate : MvxApplicationDelegate { // class-level declarations public override UIWindow Window { get; set; } public override bool FinishedLaunching(UIApplication application, NSDictionary launchOptions) { Window = new UIWindow(UIScreen.MainScreen.Bounds); var presenter = new MvxIosViewPresenter(this, Window); var setup = new Setup(this, presenter); setup.Initialize(); var startup = Mvx.Resolve<IMvxAppStart>(); startup.Start(); Window.MakeKeyAndVisible(); return true; } }

Para apresentar nosso VIewModel, precisamos criar uma view. Para esse caso, vamos criar um ViewController clicando com o botão direito do mouse no projeto e selecionando Add -> New File e selecionando ViewController da seção iOS, que vamos nomear FirstViewController.

O Xamarin cria três arquivos nos quais vamos definir quais serão nossas ligações. Ao contrário do Android, para iOS, temos que definir nossos bindings de uma maneira diferente, por meio de código (embora possamos fazer isso no Android também e, em alguns casos, isso é obrigatório).

Quando é necessário navegar entre as visualizações, isso é feito por meio do ViewModel. No comando NavigateToSecondViewModelCommand , o método ShowViewModel<SecondViewModel>() encontrará a visualização apropriada e navegará até ela.

Mas, como o MVVMCross sabe qual visualização carregar?

Não há mágica nisso. Quando criamos uma view para Android (Activity ou Fragment) estamos estendendo uma das classes base com parâmetros de tipo ( MvxAppCompatActivity<VM> ). Quando chamamos ShowViewMolel<VM> , MvvmCross procura uma View que estende a classe Activity ou Fragment com parâmetros de tipo VM . É por isso que você não tem permissão para ter duas classes de visualização para o mesmo ViewModel.

Inversão de controle

Como o Xamarin está apenas fornecendo wrappers C# em torno de APIs nativas, ele não está fornecendo nenhuma forma de mecanismo de injeção de dependência (DI) ou inversão de controle (IoC).

Sem injeção de dependências em tempo de execução ou injeção de tempo de compilação, não é fácil criar componentes fracamente acoplados, reutilizáveis, testáveis ​​e de fácil manutenção. A ideia de IoC e DI é conhecida há muito tempo; detalhes sobre IoC podem ser encontrados em muitos artigos online. Você pode aprender mais sobre esses padrões no artigo introdutório de Martin Fowler.

O IoC está disponível desde as primeiras versões do MvvmCrosses e permite a injeção de dependências em tempo de execução quando o aplicativo é iniciado e sempre que necessário.

Para obter componentes fracamente acoplados, nunca devemos exigir implementações concretas de classes. A exigência de implementações concretas limita a capacidade de alterar o comportamento das implementações durante o tempo de execução (você não pode substituí-lo por outra implementação). Isso torna difícil testar esses componentes.

Por isso, vamos declarar uma interface para a qual teremos uma implementação concreta.

 public interface IPasswordGeneratorService { string Generate(int length); }

E implementação:

 public class PasswordGeneratorService : IPasswordGeneratorService { public string Generate(int length) { var val; var res = new StringBuilder(); var rnd = new Random(); while (0 < length--) { res.Append(valid[rnd.Next(valid.Length)]); } return res.ToString(); } }

Nosso ViewModel agora pode exigir uma instância da interface IPasswordGenerationService , que somos responsáveis ​​por fornecer.

Para que o MvvmCross injete uma implementação PasswordGeneratorService em tempo de execução, precisamos informar ao MvvmCross qual implementação usar. Se quisermos usar uma implementação para ambas as plataformas, podemos registrar as implementações em App.cs , após o registro do aplicativo:

 public override void Initialize() { RegisterAppStart(new AppStart()); Mvx.LazyConstructAndRegisterSingleton<IPasswordGeneratorService, PasswordGeneratorService>(); }

A chamada acima para o método estático LazyConstructAndRegisterSingleton<TInterface, TType> registra a implementação a ser injetada. Este método registra a implementação apropriada, mas não cria um objeto.

O objeto é criado apenas quando necessário e apenas uma vez, pois é registrado como singleton.

Se quisermos criar um objeto singleton imediatamente, isso pode ser feito chamando Mvx.RegisterSingleton<TInterface>() .

Existem casos em que não queremos ter apenas singletons em nossa aplicação. Nosso objeto pode não ser thread-safe ou pode haver algum outro motivo pelo qual queremos sempre ter uma nova instância. Se for esse o caso, MvvmCross fornece o método Mvx.RegisterType<TInterface,TType>() , que pode ser usado para registrar a implementação de forma que instancia uma nova instância sempre que necessário.

Caso você precise fornecer implementações concretas separadas para cada plataforma, você sempre pode fazê-lo nos projetos específicos da plataforma:

 public class DroidPasswodGeneratorService : IPasswordGeneratorService { public string Generate(int length) { return "DroidPasswordGenerator"; } }

E o registro de nossa implementação é feito na classe Setup.cs no projeto Droid:

 protected override void InitializePlatformServices() { base.InitializePlatformServices(); Mvx.LazyConstructAndRegisterSingleton<IPasswordGeneratorService, DroidPasswodGeneratorService>(); }

Após a inicialização do código PCL, o MvvmCross chamará InitializePlatformServices e registrará nossas implementações de serviços específicos da plataforma.

Quando registramos várias implementações singleton, o MvvmCross usará apenas a implementação que foi registrada por último. Todos os outros registros serão descartados.

Crie aplicativos multiplataforma com o Xamarin

Neste artigo, você viu como o Xamarin permite compartilhar código em diferentes plataformas e ainda manter a sensação nativa e o desempenho dos aplicativos.

O MvvmCross oferece outra camada de abstração, aprimorando ainda mais a experiência de criação de aplicativos multiplataforma com Xamarin. O padrão MVVM fornece uma maneira de criar fluxos de navegação e interação do usuário que são comuns a todas as plataformas, tornando a quantidade de código específico da plataforma que você precisa escrever limitada apenas às visualizações.

Espero que este artigo tenha lhe dado um motivo para dar uma olhada no Xamarin e motivado você a construir seu próximo aplicativo multiplataforma com ele.