Tworzenie aplikacji wieloplatformowych za pomocą platformy Xamarin: perspektywa programisty Androida

Opublikowany: 2022-03-11

Napisanie kodu raz i używanie go na wielu platformach było marzeniem wielu programistów. Chociaż było to możliwe już od jakiegoś czasu, zawsze odbywało się to kosztem łatwości konserwacji, łatwości testowania lub, co gorsza, kiepskiego doświadczenia użytkownika.

Tworzenie aplikacji mobilnych z wykorzystaniem natywnego SDK jest prawdopodobnie punktem wyjścia dla wszystkich programistów, którzy mają swoje korzenie w sferze tworzenia aplikacji desktopowych. Języki programowania stałyby się dla niektórych barierą: gdyby ktoś miał doświadczenie w tworzeniu aplikacji desktopowych lub back-endowych w języku Java, przejście do firmy zajmującej się tworzeniem aplikacji mobilnych i praca z Androidem byłaby znacznie łatwiejsza niż rozpoczęcie od zera Objective-C dla iOS.

Zawsze byłem sceptycznie nastawiony do tworzenia aplikacji wieloplatformowych. Struktury oparte na JavaScript, takie jak Sencha, Cordova, Titanium itp., nigdy nie okazują się mądrym wyborem, gdy ważna jest wydajność. W przypadku tych frameworków brak interfejsów API i dziwaczne wrażenia użytkownika.

Ale potem natknąłem się na Xamarin.

Rozwój międzyplatformowy z Xamarin

W tym artykule dowiesz się, jak używać platformy Xamarin do udostępniania kodu na wielu platformach bez narażania innych aspektów tworzenia aplikacji mobilnych. W artykule skupimy się w szczególności na systemach Android i iOS, ale możesz użyć podobnego podejścia, aby dodać obsługę dowolnej innej platformy obsługiwanej przez platformę Xamarin.

Co to jest Xamarin?

Xamarin to platforma programistyczna, która umożliwia pisanie wieloplatformowych — ale natywnych — aplikacji dla systemów iOS, Android i Windows Phone w językach C# i .NET.

Platforma Xamarin zapewnia powiązania języka C# z natywnymi interfejsami API systemu Android i iOS. Daje to możliwość korzystania ze wszystkich natywnych interfejsów użytkownika systemów Android i iOS, powiadomień, grafiki, animacji i innych funkcji telefonu — wszystko za pomocą C#.

Każda nowa wersja systemów Android i iOS jest dopasowywana przez platformę Xamarin z nową wersją, która zawiera powiązania dla ich nowych interfejsów API.

Port platformy .NET platformy Xamarin zawiera funkcje, takie jak typy danych, typy ogólne, wyrzucanie elementów bezużytecznych, zapytania zintegrowane z językiem (LINQ), wzorce programowania asynchronicznego, delegaci i podzbiór programu Windows Communication Foundation (WCF). Biblioteki są zarządzane z opóźnieniem, aby zawierały tylko komponenty, do których istnieją odniesienia.

Xamarin. Forms to warstwa nad innymi powiązaniami interfejsu użytkownika i interfejsem API systemu Windows Phone, który zapewnia całkowicie wieloplatformową bibliotekę interfejsu użytkownika.

Zakres Xamarin

Pisanie aplikacji wieloplatformowych

Aby pisać aplikacje wieloplatformowe za pomocą platformy Xamarin, programiści muszą wybrać jeden z dwóch dostępnych typów projektów:

  • Biblioteka klas przenośnych (PCL)
  • Wspólny projekt

PCL umożliwia pisanie kodu, który może być współużytkowany przez wiele platform, ale z jednym ograniczeniem. Ponieważ nie wszystkie interfejsy API platformy .NET są dostępne na wszystkich platformach, w przypadku projektu PCL ograniczysz go do uruchamiania na platformach, dla których jest przeznaczony.

Połączenia i ograniczenia Xamarin

Poniższa tabela pokazuje, które interfejsy API są dostępne na poszczególnych platformach:

Funkcja .NET Framework Aplikacje Sklepu Windows Srebrne światło telefon Windows Xamarin
Rdzeń Y Y Y Y Y
LINQ Y Y Y Y Y
Możliwość zapytania Y Y Y 7,5+ Y
Serializacja Y Y Y Y Y
Adnotacje do danych 4.0.3+ Y Y Y Y

Podczas procesu kompilacji PCL jest kompilowany do oddzielnych bibliotek DLL i ładowany przez Mono w czasie wykonywania. Inną implementację tego samego interfejsu można zapewnić w czasie wykonywania.

Z drugiej strony wspólne projekty zapewniają większą kontrolę, umożliwiając pisanie kodu specyficznego dla platformy dla każdej platformy, którą chcesz obsługiwać. Kod w projekcie udostępnionym może zawierać dyrektywy kompilatora, które włączają lub wyłączają sekcje kodu w zależności od tego, który projekt aplikacji używa kodu.

W przeciwieństwie do PCL, projekt współdzielony nie tworzy żadnej biblioteki DLL. Kod zawarty jest bezpośrednio w ostatecznym projekcie.

Nadawanie struktury Twojemu wieloplatformowemu kodowi za pomocą MvvmCross

Kod wielokrotnego użytku może zaoszczędzić pieniądze i czas zespołom programistów. Jednak dobrze skonstruowany kod znacznie ułatwia życie programistom. Nikt nie docenia ładnie napisanego, wolnego od błędów kodu bardziej niż programiści.

Xamarin sam w sobie zapewnia mechanizm, który znacznie ułatwia pisanie wieloplatformowego kodu wielokrotnego użytku.

Programiści mobilni znają scenariusze, w których muszą napisać tę samą logikę dwa razy lub więcej, aby obsługiwać systemy iOS, Android i inne. Ale z Xamarin, jak wyjaśniono w poprzednim rozdziale, łatwo jest ponownie wykorzystać kod, który jest napisany dla jednej platformy dla kilku innych platform.

Gdzie w takim razie pojawia się MvvmCross?

MvvmCross, jak nazwa może sugerować, umożliwia wykorzystanie wzorca MVVM w aplikacjach Xamarin. Zawiera wiele bibliotek, interfejsów API i narzędzi, które są naprawdę przydatne w tworzeniu aplikacji wieloplatformowych.

MvvmCross może znacznie zmniejszyć ilość kodu, który zostałbyś napisany (czasem wielokrotnie w różnych językach) w każdym innym podejściu do tworzenia aplikacji.

Struktura rozwiązania MvvmCross

Społeczność MvvmCross zaleca dość prosty i skuteczny sposób ustrukturyzowania rozwiązania MvvmCross:

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

Projekt Core w rozwiązaniu MvvmCross jest powiązany z kodem wielokrotnego użytku. Projekt Core to projekt Xamarin PCL, którego głównym celem jest ponowne wykorzystanie.

Każdy kod napisany w Core powinien być maksymalnie niezależny od platformy. Powinien zawierać tylko logikę, którą można ponownie wykorzystać na wszystkich platformach. Projekt Core nie może korzystać z żadnego interfejsu API systemu Android lub iOS ani uzyskiwać dostępu do niczego specyficznego dla żadnej platformy.

Warstwa logiki biznesowej, warstwa danych i komunikacja zaplecza są idealnymi kandydatami do włączenia do projektu Core. Nawigacja po hierarchii widoków (działania, fragmenty itp.) zostanie osiągnięta w rdzeniu.

Przed kontynuowaniem konieczne jest zrozumienie jednego wzorca architektonicznego, który jest kluczowy dla zrozumienia MvvmCross i sposobu jego działania. Jak widać z nazwy, MvvmCross w dużym stopniu zależy od wzorca MVVM.

MVVM to architektoniczny wzorzec projektowy, który ułatwia oddzielenie graficznego interfejsu użytkownika od logiki biznesowej i danych zaplecza.

Jak ten wzorzec jest używany w MvvmCross?

Cóż, ponieważ chcemy osiągnąć wysoką reużywalność naszego kodu, chcemy mieć jak najwięcej w naszym Core, który jest projektem PCL. Ponieważ widoki są jedyną częścią kodu, która różni się w zależności od platformy, nie możemy ich ponownie używać na różnych platformach. Ta część jest realizowana w projektach związanych z platformą.

Struktura MvvmCross

MvvmCross daje nam możliwość orkiestracji nawigacji aplikacji z rdzenia za pomocą ViewModels.

Po usunięciu podstaw i szczegółów technicznych zacznijmy od Xamarin, tworząc nasz własny projekt MvvmCross Core:

Tworzenie projektu MvvmCross Core

Otwórz Xamarin Studio i utwórz rozwiązanie o nazwie ToptalExampleSolution :

Tworzenie rozwiązania

Ponieważ tworzymy projekt Core, warto trzymać się konwencji nazewnictwa. Upewnij się, że sufiks Core został dodany do nazwy projektu.

Aby uzyskać wsparcie dla MvvmCross, wymagane jest dodanie bibliotek MvvmCross do naszego projektu. Aby dodać, że możemy skorzystać z wbudowanej obsługi NuGet w Xamarin Studio.

Aby dodać bibliotekę, kliknij prawym przyciskiem myszy folder Pakiety i wybierz opcję Dodaj pakiety… .

W polu wyszukiwania możemy wyszukać MvvmCross, który odfiltruje wyniki związane z MvvmCross, jak pokazano poniżej:

Filtrowanie wyników

Kliknięcie przycisku Dodaj pakiet doda go do projektu.

Po dodaniu MvvmCross do naszego projektu jesteśmy gotowi do napisania naszego kodu Core.

Zdefiniujmy nasz pierwszy ViewModel. Aby je utworzyć, utwórz hierarchię folderów w następujący sposób:

Zalecana hierarchia folderów

Oto, o czym jest każdy z folderów:

  • Modele: modele domen, które reprezentują zawartość stanu rzeczywistego
  • Usługi: folder, w którym znajduje się nasza usługa (logika biznesowa, baza danych itp.)
  • ViewModel: sposób, w jaki komunikujemy się z naszymi modelami

Nasz pierwszy ViewModel nazywa się 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>(); }); } } }

Teraz, gdy mamy nasz pierwszy ViewModel, możemy stworzyć nasz pierwszy widok i powiązać wszystko razem.

Interfejs Androida

Aby pokazać zawartość ViewModel, musimy stworzyć UI.

Pierwszym krokiem do utworzenia interfejsu użytkownika systemu Android jest utworzenie projektu systemu Android w bieżącym rozwiązaniu. Aby to zrobić, kliknij prawym przyciskiem myszy nazwę rozwiązania i wybierz Dodaj -> Dodaj nowy projekt… . W kreatorze wybierz aplikację Android i upewnij się, że nazwałeś swój projekt ToptalExample.UI.Droid .

Jak opisano wcześniej, musimy teraz dodać zależności MvvmCross dla Androida. Aby to zrobić, wykonaj te same kroki, co w przypadku projektu Core, aby dodać zależności NuGet.

Po dodaniu zależności MvvmCross wymagane jest dodanie referencji do naszego projektu Core, abyśmy mogli wykorzystać nasz kod tam napisany. Aby dodać odniesienie do projektu PCL, kliknij prawym przyciskiem myszy folder Odniesienia i wybierz opcję Edytuj odniesienia… . Na karcie Projekty wybierz wcześniej utworzony projekt podstawowy i kliknij OK.

Dodanie referencji do projektu PCL

Następna część może być trochę trudna do zrozumienia.

Teraz musimy powiedzieć MvvmCrossowi, jak ma skonfigurować naszą aplikację. W tym celu musimy stworzyć klasę Setup :

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

Jak widać z klasy, mówimy MvvmCross do CreateApp w oparciu o implementację Core.App , która jest klasą zdefiniowaną w Core i pokazaną poniżej:

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

W klasie App tworzymy instancję AppStart , która pokaże nasz pierwszy ViewModel.

Pozostaje tylko utworzyć plik Android Layout, który będzie powiązany przez 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>

W pliku układu mamy powiązania, które są automatycznie rozwiązywane przez MvvmCross. Dla EditText tworzymy wiązanie dla właściwości Text, które będzie wiązaniem dwukierunkowym. Każda zmiana wywołana ze strony ViewModel zostanie automatycznie odzwierciedlona w widoku i na odwrót.

Klasa View może być działaniem lub fragmentem. Dla uproszczenia używamy działania, które ładuje dany układ:

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

Dla pierwszego przycisku mamy powiązanie polecenia, co oznacza, że ​​po kliknięciu przycisku MvvmCross wywoła ContactNameCommand z ViewModel.

Dla drugiego przycisku pokażemy inny ViewModel.

Interfejs iOS

Tworzenie projektu na iOS nie różni się tak naprawdę od tworzenia projektu na Androida. Musisz wykonać podobne kroki, aby dodać nowy projekt, tylko tym razem, zamiast Androida, po prostu utwórz projekt iOS. Upewnij się tylko, że zachowałeś spójną konwencję nazewnictwa.

Po dodaniu projektu iOS musisz dodać zależności dla MvvmCross iOS. Kroki są absolutnie takie same jak w przypadku Core i Androida (kliknij prawym przyciskiem myszy Referencje w projekcie iOS i kliknij Dodaj referencje… ).

Teraz, tak jak w przypadku Androida, wymagane jest utworzenie klasy Setup , która powie MvvmCrossowi, jak skonfigurować naszą aplikację.

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

Zauważ, że klasa Setup rozszerza teraz MvxIosSetup , a dla systemu Android rozszerzała MvxAndroidSetup .

Jednym z dodatków jest to, że musimy zmienić naszą klasę AppDelegate .

AppDelegate na iOS odpowiada za uruchomienie interfejsu użytkownika, więc musimy powiedzieć, jak widoki będą prezentowane na iOS. Możesz dowiedzieć się więcej o prezenterach tutaj.

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

Aby zaprezentować nasz ViewModel, musimy stworzyć widok. W takim przypadku utwórzmy ViewController, klikając prawym przyciskiem myszy projekt i wybierając Add -> New File i wybierając ViewController z sekcji iOS, którą nazwiemy FirstViewController.

Xamarin tworzy trzy pliki, w których zdefiniujemy, jakie będą nasze powiązania. W przeciwieństwie do Androida, w przypadku iOS, musimy zdefiniować nasze powiązania w inny sposób, za pomocą kodu (chociaż możemy to zrobić również na Androidzie, a w niektórych przypadkach jest to wymagane).

Gdy wymagana jest nawigacja między widokami, odbywa się to za pomocą ViewModel. W poleceniu NavigateToSecondViewModelCommand metoda ShowViewModel<SecondViewModel>() znajdzie odpowiedni widok i przejdzie do niego.

Ale skąd MVVMCross wie, który widok ma załadować?

Nie ma w tym żadnej magii. Tworząc widok dla Androida (Activity lub Fragment) rozszerzamy jedną z klas bazowych o parametry typu ( MvxAppCompatActivity<VM> ). Kiedy wywołujemy ShowViewMolel<VM> , MvvmCross wyszukuje View , który rozszerza klasę Activity lub Fragment z parametrami typu VM . Dlatego nie możesz mieć dwóch klas widoków dla tego samego ViewModel.

Odwrócenie sterowania

Ponieważ platforma Xamarin zapewnia jedynie otoki języka C# wokół natywnych interfejsów API, nie zapewnia żadnej formy mechanizmu wstrzykiwania zależności (DI) ani odwrócenia kontroli (IoC).

Bez wstrzykiwania zależności w czasie wykonywania lub wstrzykiwania w czasie kompilacji nie jest łatwo tworzyć luźno powiązane, wielokrotnego użytku, testowalne i łatwe w utrzymaniu komponenty. Idea IoC i DI znana jest od bardzo dawna; szczegóły dotyczące IoC można znaleźć w wielu artykułach online. Możesz dowiedzieć się więcej o tych wzorcach z artykułu wprowadzającego Martina Fowlera.

IoC jest dostępny od wczesnych wersji MvvmCrosses i umożliwia wstrzykiwanie zależności w czasie wykonywania, kiedy aplikacja jest uruchamiana i kiedy są wymagane.

Aby uzyskać luźno powiązane komponenty, nigdy nie powinniśmy wymagać konkretnych implementacji klas. Wymaganie konkretnych implementacji ogranicza możliwość zmiany zachowania implementacji w czasie wykonywania (nie można zastąpić jej inną implementacją). Utrudnia to testowanie tych komponentów.

Z tego powodu zamierzamy zadeklarować interfejs, dla którego będziemy mieć jedną konkretną implementację.

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

Oraz realizacja:

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

Nasz ViewModel może teraz wymagać wystąpienia interfejsu IPasswordGenerationService , za który jesteśmy odpowiedzialni.

Aby MvvmCross mógł wstrzyknąć implementację PasswordGeneratorService w czasie wykonywania, musimy poinformować MvvmCross, której implementacji użyć. Jeśli chcemy korzystać z jednej implementacji dla obu platform, możemy zarejestrować implementacje w App.cs , po zarejestrowaniu aplikacji:

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

Powyższe wywołanie metody statycznej LazyConstructAndRegisterSingleton<TInterface, TType> rejestruje implementację, która ma zostać wstrzyknięta. Ta metoda rejestruje odpowiednią implementację, ale nie tworzy obiektu.

Obiekt jest tworzony tylko wtedy, gdy jest to wymagane i tylko raz, ponieważ jest zarejestrowany jako singleton.

Jeśli chcemy od razu stworzyć obiekt singleton, można to osiągnąć poprzez wywołanie Mvx.RegisterSingleton<TInterface>() .

Zdarzają się przypadki, w których nie chcemy mieć w naszej aplikacji samych singli. Nasz obiekt może nie być bezpieczny dla wątków lub może istnieć inny powód, dla którego zawsze chcemy mieć nową instancję. W takim przypadku MvvmCross udostępnia metodę Mvx.RegisterType<TInterface,TType>() , której można użyć do zarejestrowania implementacji w sposób, który tworzy wystąpienie nowego wystąpienia, gdy jest to wymagane.

Jeśli musisz zapewnić oddzielne konkretne implementacje dla każdej platformy, zawsze możesz to zrobić w projektach specyficznych dla platformy:

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

A rejestracja naszej implementacji odbywa się w klasie Setup.cs w ramach projektu Droid:

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

Po zainicjowaniu kodu PCL, MvvmCross wywoła InitializePlatformServices i zarejestruje implementacje usług dla naszej platformy.

Gdy zarejestrujemy wiele implementacji singleton, MvvmCross użyje tylko implementacji, która została zarejestrowana jako ostatnia. Wszystkie inne rejestracje zostaną odrzucone.

Twórz aplikacje wieloplatformowe za pomocą platformy Xamarin

W tym artykule zobaczyłeś, jak platforma Xamarin umożliwia udostępnianie kodu na różnych platformach i nadal zachowuje natywny styl i wydajność aplikacji.

MvvmCross zapewnia kolejną warstwę abstrakcji, która dodatkowo zwiększa możliwości tworzenia aplikacji wieloplatformowych za pomocą platformy Xamarin. Wzorzec MVVM zapewnia sposób tworzenia przepływów nawigacji i interakcji z użytkownikiem, które są wspólne dla wszystkich platform, dzięki czemu ilość kodu specyficznego dla platformy, który trzeba napisać, jest ograniczona do samych widoków.

Mam nadzieję, że ten artykuł dał Ci powód do rzucenia okiem na platformę Xamarin i zmotywował Cię do zbudowania z nim kolejnej aplikacji wieloplatformowej.