Creazione di app multipiattaforma con Xamarin: prospettiva di uno sviluppatore Android

Pubblicato: 2022-03-11

Scrivere il codice una volta e usarlo su più piattaforme è stato il sogno di molti sviluppatori di software. Sebbene ciò sia stato possibile per un po' di tempo, è sempre stato a scapito della manutenibilità, della facilità di test o, peggio ancora, della scarsa esperienza dell'utente.

Lo sviluppo di applicazioni mobili utilizzando l'SDK nativo è probabilmente il punto di partenza per tutti gli sviluppatori che hanno le proprie radici nel regno dello sviluppo di applicazioni desktop. I linguaggi di programmazione diventerebbero un ostacolo per alcuni: se qualcuno avesse esperienza nello sviluppo di applicazioni desktop o back-end Java, passare a un'azienda di sviluppo di app mobili e lavorare con Android sarebbe molto più semplice che iniziare con Objective-C da zero per iOS.

Sono sempre stato scettico sullo sviluppo di applicazioni multipiattaforma. Framework basati su JavaScript come Sencha, Cordova, Titanium, ecc. non si rivelano mai una scelta saggia quando le prestazioni sono importanti. La mancanza di API e l'esperienza utente bizzarra erano un dato di fatto con questi framework.

Ma poi, mi sono imbattuto in Xamarin.

Sviluppo multipiattaforma con Xamarin

In questo articolo imparerai come usare Xamarin per condividere codice su più piattaforme senza compromettere nessuno degli altri aspetti dello sviluppo di applicazioni mobili. L'articolo si concentrerà in particolare su Android e iOS, ma puoi usare un approccio simile per aggiungere il supporto per qualsiasi altra piattaforma supportata da Xamarin.

Cos'è Xamarin?

Xamarin è una piattaforma di sviluppo che consente di scrivere applicazioni multipiattaforma, ma native, per iOS, Android e Windows Phone in C# e .NET.

Xamarin fornisce collegamenti C# alle API Android e iOS native. Ciò ti dà la possibilità di utilizzare tutta l'interfaccia utente nativa di Android e iOS, le notifiche, la grafica, l'animazione e altre funzionalità del telefono, il tutto utilizzando C#.

Ogni nuova versione di Android e iOS è abbinata a Xamarin, con una nuova versione che include le associazioni per le nuove API.

Il port di .NET di Xamarin include funzionalità come tipi di dati, generici, Garbage Collection, query integrate nel linguaggio (LINQ), modelli di programmazione asincrona, delegati e un sottoinsieme di Windows Communication Foundation (WCF). Le librerie vengono gestite con una pausa per includere solo i componenti di riferimento.

Xamarin.Forms è un livello sopra le altre associazioni dell'interfaccia utente e l'API di Windows Phone, che fornisce una libreria di interfaccia utente completamente multipiattaforma.

L'ambito di Xamarin

Scrittura di applicazioni multipiattaforma

Per scrivere applicazioni multipiattaforma con Xamarin, gli sviluppatori devono scegliere uno dei due tipi di progetto disponibili:

  • Libreria di classi portatile (PCL)
  • Progetto condiviso

Il PCL consente di scrivere codice che può essere condiviso tra più piattaforme, ma con una limitazione. Poiché non tutte le API .NET sono disponibili su tutte le piattaforme, con un progetto PCL lo limiterai all'esecuzione su piattaforme a cui è destinato.

Connessioni e limitazioni di Xamarin

La tabella seguente mostra quali API sono disponibili su quali piattaforme:

Caratteristica .NET Framework App di Windows Store Silverlight Windows Phone Xamarin
Nucleo Y Y Y Y Y
LINQ Y Y Y Y Y
IQueryable Y Y Y 7,5+ Y
Serializzazione Y Y Y Y Y
Annotazioni sui dati 4.0.3+ Y Y Y Y

Durante il processo di compilazione, un PCL viene compilato in DLL separate e caricato da Mono durante il runtime. Durante il runtime può essere fornita un'implementazione diversa della stessa interfaccia.

D'altra parte, i progetti condivisi ti danno un maggiore controllo consentendoti di scrivere codice specifico della piattaforma per ogni piattaforma che desideri supportare. Il codice in un progetto condiviso può contenere direttive del compilatore che abiliteranno o disabiliteranno sezioni di codice a seconda del progetto applicativo che utilizza il codice.

A differenza di un PCL, un progetto condiviso non produce alcuna DLL. Il codice è incluso direttamente nel progetto finale.

Dare struttura al tuo codice multipiattaforma con MvvmCross

Il codice riutilizzabile può far risparmiare tempo e denaro ai team di sviluppo. Tuttavia, un codice ben strutturato rende la vita molto più semplice agli sviluppatori. Nessuno apprezza il codice privo di bug ben scritto più degli sviluppatori.

Xamarin di per sé fornisce un meccanismo che semplifica notevolmente la scrittura di codice multipiattaforma riutilizzabile.

Gli sviluppatori mobili hanno familiarità con scenari in cui devono scrivere la stessa logica due o più volte per supportare iOS, Android e altre piattaforme. Ma con Xamarin, come spiegato nel capitolo precedente, è facile riutilizzare il codice scritto per una piattaforma anche per altre piattaforme.

Dove entra in gioco MvvmCross, allora?

MvvmCross, come suggerisce il nome, consente di usare il modello MVVM nelle applicazioni Xamarin. Viene fornito con un sacco di librerie, API e utilità che sono davvero utili nello sviluppo di applicazioni multipiattaforma.

MvvmCross può ridurre significativamente la quantità di codice standard che avresti scritto (a volte più volte in linguaggi diversi) in qualsiasi altro approccio allo sviluppo di applicazioni.

Struttura di una soluzione MvvmCross

La community MvvmCross consiglia un modo abbastanza semplice ed efficiente per strutturare una soluzione MvvmCross:

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

Il progetto Core in una soluzione MvvmCross è relativo al codice riutilizzabile. Il progetto Core è un progetto Xamarin PCL in cui l'obiettivo principale è la riutilizzabilità.

Qualsiasi codice scritto in Core dovrebbe essere indipendente dalla piattaforma nel miglior modo possibile. Dovrebbe contenere solo una logica che può essere riutilizzata su tutte le piattaforme. Il progetto Core non deve utilizzare alcuna API Android o iOS né accedere a nulla di specifico per alcuna piattaforma.

Il livello della logica aziendale, il livello dei dati e la comunicazione back-end sono tutti candidati perfetti per l'inclusione nel progetto Core. La navigazione attraverso la gerarchia delle viste (attività, frammenti, ecc.) sarà realizzata nel Core.

Prima di continuare, è necessario comprendere un modello di progettazione architettonica che è fondamentale per comprendere MvvmCross e come funziona. Come si può vedere dal nome, MvvmCross dipende fortemente dal pattern MVVM.

MVVM è un modello di progettazione architettonica che facilita la separazione dell'interfaccia utente grafica dalla logica aziendale e dai dati di back-end.

Come viene utilizzato questo modello in MvvmCross?

Bene, dal momento che vogliamo ottenere un'elevata riutilizzabilità del nostro codice, vogliamo avere quanto più possibile nel nostro Core, che è un progetto PCL. Poiché le visualizzazioni sono l'unica parte del codice che differisce da una piattaforma all'altra, non possiamo riutilizzarle su più piattaforme. Quella parte è implementata nei progetti relativi alla piattaforma.

Struttura MvvmCross

MvvmCross ci offre la possibilità di orchestrare la navigazione dell'applicazione dal Core usando ViewModels.

Con le nozioni di base e i dettagli tecnici fuori mano, iniziamo con Xamarin creando il nostro progetto MvvmCross Core personale:

Creazione di un progetto MvvmCross Core

Apri Xamarin Studio e crea una soluzione denominata ToptalExampleSolution :

Creare la soluzione

Poiché stiamo creando un progetto Core, è una buona idea attenersi alla convenzione di denominazione. Assicurati che il suffisso Core sia aggiunto al nome del progetto.

Per ottenere il supporto di MvvmCross, è necessario aggiungere le librerie MvvmCross al nostro progetto. Per aggiungere che possiamo usare il supporto integrato per NuGet in Xamarin Studio.

Per aggiungere una libreria, fai clic con il pulsante destro del mouse sulla cartella Pacchetti e seleziona l'opzione Aggiungi pacchetti… .

Nel campo di ricerca, possiamo cercare MvvmCross, che filtrerà i risultati relativi a MvvmCross come mostrato di seguito:

Filtraggio dei risultati

Facendo clic sul pulsante Aggiungi pacchetto lo aggiungerai al progetto.

Con MvvmCross aggiunto al nostro progetto, siamo pronti per scrivere il nostro codice Core.

Definiamo il nostro primo ViewModel. Per crearne uno, creare una gerarchia di cartelle come segue:

Gerarchia di cartelle consigliata

Ecco di cosa tratta ciascuna cartella:

  • Modelli: modelli di dominio che rappresentano il contenuto dello stato reale
  • Servizi: una cartella che contiene il nostro servizio (logica aziendale, database, ecc.)
  • ViewModel: il modo in cui comunichiamo con i nostri modelli

Il nostro primo ViewModel si chiama 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>(); }); } } }

Ora che abbiamo il nostro primo ViewModel, possiamo creare la nostra prima vista e unire le cose insieme.

Interfaccia utente Android

Per mostrare il contenuto del ViewModel, dobbiamo creare un'interfaccia utente.

Il primo passaggio per creare un'interfaccia utente Android consiste nel creare un progetto Android nella soluzione corrente. Per fare ciò, fai clic con il pulsante destro del mouse sul nome della soluzione e seleziona Aggiungi -> Aggiungi nuovo progetto... . Nella procedura guidata, seleziona l'app Android e assicurati di nominare il tuo progetto ToptalExample.UI.Droid .

Come descritto in precedenza, ora è necessario aggiungere le dipendenze MvvmCross per Android. Per farlo, segui gli stessi passaggi del progetto Core per aggiungere dipendenze NuGet.

Dopo aver aggiunto le dipendenze MvvmCross, è necessario aggiungere un riferimento al nostro progetto Core in modo da poter utilizzare il nostro codice scritto lì. Per aggiungere un riferimento al progetto PCL, fare clic con il pulsante destro del mouse sulla cartella Riferimenti e selezionare l'opzione Modifica riferimenti.... Nella scheda Progetti, seleziona il progetto principale creato in precedenza e fai clic su OK.

Aggiunta di un riferimento al progetto PCL

La parte successiva può essere un po' difficile da capire.

Ora dobbiamo dire a MvvmCross come dovrebbe impostare la nostra applicazione. Per fare ciò, dobbiamo creare una classe Setup :

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

Come si può vedere dalla classe, stiamo dicendo a MvvmCross di CreateApp in base all'implementazione Core.App , che è una classe definita in Core e mostrata di seguito:

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

Nella classe App , stiamo creando un'istanza di AppStart , che mostrerà il nostro primo ViewModel.

L'unica cosa rimasta ora è creare un file di layout Android che sarà vincolato da 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>

Nel file di layout, abbiamo i binding che vengono risolti automaticamente da MvvmCross. Per EditText , stiamo creando un'associazione per la proprietà Text, che sarà un'associazione a due vie. Qualsiasi modifica richiamata dal lato ViewModel verrà automaticamente riflessa sulla vista e viceversa.

La classe View può essere un'attività o un frammento. Per semplicità, utilizziamo un'attività che carica il layout indicato:

 [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); } }

Per il primo pulsante, abbiamo un'associazione di comandi, il che significa che quando facciamo clic sul pulsante MvvmCross invocherà ContactNameCommand dal ViewModel.

Per il secondo pulsante, mostreremo un altro ViewModel.

interfaccia utente iOS

La creazione di un progetto iOS non è molto diversa dalla creazione di un progetto Android. Devi seguire passaggi simili per aggiungere un nuovo progetto, solo che questa volta, invece di Android, crea un progetto iOS. Assicurati solo di mantenere coerente la convenzione di denominazione.

Dopo aver aggiunto il progetto iOS, è necessario aggiungere dipendenze per MvvmCross iOS. I passaggi sono assolutamente gli stessi di Core e Android (fai clic con il pulsante destro del mouse su Riferimenti nel tuo progetto iOS e fai clic su Aggiungi riferimenti... ).

Ora, come abbiamo fatto per Android, è necessario creare una classe Setup , che dirà a MvvmCross come configurare la nostra applicazione.

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

Si noti che la classe Setup ora estende MvxIosSetup e, per Android, estendeva MvxAndroidSetup .

Un'aggiunta qui è che dobbiamo cambiare la nostra classe AppDelegate .

AppDelegate su iOS è responsabile dell'avvio dell'interfaccia utente, quindi dobbiamo dire come verranno presentate le visualizzazioni su iOS. Puoi saperne di più sui presentatori qui.

 [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; } }

Per presentare il nostro ViewModel, dobbiamo creare una vista. In tal caso, creiamo un ViewController facendo clic con il pulsante destro del mouse sul progetto e selezionando Aggiungi -> Nuovo file e selezionando ViewController dalla sezione iOS, che chiameremo FirstViewController.

Xamarin crea tre file in cui definiremo quali saranno le nostre associazioni. A differenza di Android, per iOS, dobbiamo definire i nostri binding in un modo diverso, tramite codice (sebbene possiamo farlo anche su Android e, in alcuni casi, è necessario farlo).

Quando è necessario navigare tra le viste, lo si fa tramite ViewModel. Nel comando NavigateToSecondViewModelCommand , il metodo ShowViewModel<SecondViewModel>() troverà la vista appropriata e vi sposterà.

Ma come fa MVVMCross a sapere quale vista caricare?

Non c'è nessuna magia in questo. Quando creiamo una vista per Android (Activity o Fragment) stiamo estendendo una delle classi base con parametri di tipo ( MvxAppCompatActivity<VM> ). Quando chiamiamo ShowViewMolel<VM> , MvvmCross cerca una View che estende la classe Activity o Fragment con parametri di tipo VM . Questo è il motivo per cui non è consentito avere due classi di visualizzazione per lo stesso ViewModel.

Inversione di controllo

Poiché Xamarin fornisce semplicemente wrapper C# attorno alle API native, non fornisce alcuna forma di inserimento delle dipendenze (DI) o meccanismo di inversione del controllo (IoC).

Senza l'iniezione di dipendenze in fase di esecuzione o l'iniezione di tempo di compilazione, non è facile creare componenti liberamente accoppiati, riutilizzabili, testabili e di facile manutenzione. L'idea di IoC e DI è nota da molto tempo; i dettagli su IoC possono essere trovati in molti articoli online. Puoi saperne di più su questi modelli dall'articolo introduttivo di Martin Fowler.

IoC è disponibile sin dalle prime versioni di MvvmCrosses e consente l'inserimento di dipendenze in fase di esecuzione all'avvio dell'applicazione e ogni volta che sono necessarie.

Per ottenere componenti liberamente accoppiati, non dovremmo mai richiedere implementazioni concrete di classi. La richiesta di implementazioni concrete limita la possibilità di modificare il comportamento delle implementazioni durante il runtime (non è possibile sostituirla con un'altra implementazione). Rende difficile testare questi componenti.

Per questo motivo, dichiareremo un'interfaccia per la quale avremo un'implementazione concreta.

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

E implementazione:

 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(); } }

Il nostro ViewModel ora può richiedere un'istanza dell'interfaccia IPasswordGenerationService , che siamo responsabili della fornitura.

Affinché MvvmCross inietti un'implementazione PasswordGeneratorService in fase di esecuzione, è necessario indicare a MvvmCross quale implementazione utilizzare. Se vogliamo utilizzare un'implementazione per entrambe le piattaforme, possiamo registrare le implementazioni in App.cs , dopo la registrazione dell'applicazione:

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

La chiamata precedente al metodo statico LazyConstructAndRegisterSingleton<TInterface, TType> registra l'implementazione da inserire. Questo metodo registra l'implementazione appropriata ma non crea un oggetto.

L'oggetto viene creato solo quando è richiesto e solo una volta poiché è registrato come singleton.

Se vogliamo creare subito un oggetto singleton, è possibile ottenerlo chiamando Mvx.RegisterSingleton<TInterface>() .

Ci sono casi in cui non vogliamo avere solo singleton nella nostra applicazione. Il nostro oggetto potrebbe non essere thread-safe o potrebbe esserci qualche altro motivo per cui vogliamo avere sempre una nuova istanza. In tal caso, MvvmCross fornisce il metodo Mvx.RegisterType<TInterface,TType>() , che può essere utilizzato per registrare l'implementazione in modo da creare un'istanza di una nuova istanza ogni volta che è richiesta.

Nel caso in cui sia necessario fornire implementazioni concrete separate per ciascuna piattaforma, è sempre possibile farlo nei progetti specifici della piattaforma:

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

E la registrazione della nostra implementazione viene eseguita nella classe Setup.cs nell'ambito del progetto Droid:

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

Dopo l'inizializzazione del codice PCL, MvvmCross chiamerà InitializePlatformServices e registrerà le implementazioni dei servizi specifici della nostra piattaforma.

Quando registriamo più implementazioni singleton, MvvmCross utilizzerà solo l'ultima implementazione registrata. Tutte le altre registrazioni verranno annullate.

Crea app multipiattaforma con Xamarin

In questo articolo, hai visto come Xamarin ti consente di condividere il codice su piattaforme diverse e di mantenere la sensazione e le prestazioni native delle applicazioni.

MvvmCross offre un altro livello di astrazione, migliorando ulteriormente l'esperienza di creazione di applicazioni multipiattaforma con Xamarin. Il modello MVVM fornisce un modo per creare flussi di navigazione e interazione con l'utente comuni a tutte le piattaforme, limitando la quantità di codice specifico della piattaforma da scrivere alle sole viste.

Spero che questo articolo ti abbia dato un motivo per dare un'occhiata a Xamarin e ti abbia motivato a creare la tua prossima applicazione multipiattaforma con esso.