Создание кроссплатформенных приложений с помощью Xamarin: взгляд разработчика Android
Опубликовано: 2022-03-11Написать код один раз и использовать его на нескольких платформах было мечтой многих разработчиков программного обеспечения. Хотя это было возможно в течение некоторого времени, это всегда происходило за счет удобства обслуживания, простоты тестирования или, что еще хуже, плохого пользовательского опыта.
Разработка мобильных приложений с использованием собственного SDK, вероятно, является отправной точкой для всех разработчиков, у которых есть опыт разработки настольных приложений. Для некоторых языки программирования станут барьером: если кто-то имеет опыт разработки Java-приложений для настольных компьютеров или серверных приложений, переход в фирму по разработке мобильных приложений и работа с Android будут казаться намного проще, чем начинать с Objective-C с нуля для iOS.
Я всегда скептически относился к разработке кроссплатформенных приложений. Фреймворки на основе JavaScript, такие как Sencha, Cordova, Titanium и т. д., никогда не оказываются мудрым выбором, когда важна производительность. Отсутствие API и необычный пользовательский интерфейс были данностью для этих фреймворков.
Но потом я наткнулся на Xamarin.
В этой статье вы узнаете, как использовать Xamarin для совместного использования кода на нескольких платформах без ущерба для других аспектов разработки мобильных приложений. В статье основное внимание будет уделено Android и iOS, но вы можете использовать аналогичный подход и добавить поддержку для любой другой платформы, которую поддерживает Xamarin.
Что такое Ксамарин?
Xamarin — это платформа разработки, позволяющая писать кроссплатформенные, но нативные приложения для iOS, Android и Windows Phone на C# и .NET.
Xamarin предоставляет привязки C# к собственным API-интерфейсам Android и iOS. Это дает вам возможность использовать весь родной пользовательский интерфейс Android и iOS, уведомления, графику, анимацию и другие функции телефона — и все это с помощью C#.
Каждый новый выпуск Android и iOS соответствует Xamarin с новым выпуском, который включает привязки для их новых API.
Порт Xamarin для .NET включает в себя такие функции, как типы данных, универсальные шаблоны, сборка мусора, запросы, интегрированные в язык (LINQ), шаблоны асинхронного программирования, делегаты и подмножество Windows Communication Foundation (WCF). Библиотеки управляются с помощью задержки, чтобы включать только компоненты, на которые есть ссылки.
Xamarin.Forms — это слой поверх других привязок пользовательского интерфейса и API Windows Phone, который предоставляет полностью кроссплатформенную библиотеку пользовательского интерфейса.
Написание кроссплатформенных приложений
Чтобы писать кроссплатформенные приложения с помощью Xamarin, разработчикам необходимо выбрать один из двух доступных типов проектов:
- Портативная библиотека классов (PCL)
- Общий проект
PCL позволяет вам писать код, который можно использовать на нескольких платформах, но с одним ограничением. Поскольку не все API-интерфейсы .NET доступны на всех платформах, с проектом PCL вы будете ограничивать его запуск на платформах, для которых он предназначен.
В таблице ниже показано, какие API доступны на каких платформах:
Характерная черта | .NET Framework | Приложения Магазина Windows | Сильверлайт | Windows Phone | Ксамарин |
---|---|---|---|---|---|
Основной | Д | Д | Д | Д | Д |
LINQ | Д | Д | Д | Д | Д |
IQueryable | Д | Д | Д | 7,5+ | Д |
Сериализация | Д | Д | Д | Д | Д |
Аннотации данных | 4.0.3+ | Д | Д | Д | Д |
В процессе сборки PCL компилируется в отдельные библиотеки DLL и загружается Mono во время выполнения. Во время выполнения может быть предоставлена другая реализация одного и того же интерфейса.
С другой стороны, общие проекты дают вам больше контроля, позволяя вам писать код для каждой платформы, которую вы хотите поддерживать. Код в общем проекте может содержать директивы компилятора, которые будут включать или отключать разделы кода в зависимости от того, какой проект приложения использует код.
В отличие от PCL, общий проект не создает DLL. Код включается непосредственно в окончательный проект.
Придание структуры вашему кроссплатформенному коду с помощью MvvmCross
Повторно используемый код может сэкономить деньги и время для команд разработчиков. Однако хорошо структурированный код значительно облегчает жизнь разработчикам. Никто не ценит красиво написанный код без ошибок больше, чем разработчики.
Xamarin сам по себе предоставляет механизм, который значительно упрощает написание повторно используемого кроссплатформенного кода.
Мобильные разработчики знакомы со сценариями, в которых им приходится писать одну и ту же логику дважды или более, чтобы обеспечить поддержку iOS, Android и других платформ. Но с Xamarin, как объяснялось в предыдущей главе, легко повторно использовать код, написанный для одной платформы, и для некоторых других платформ.
Где тогда MvvmCross встает на место?
MvvmCross, как следует из названия, позволяет использовать шаблон MVVM в приложениях Xamarin. Он поставляется с набором библиотек, API и утилит, которые действительно удобны при разработке кроссплатформенных приложений.
MvvmCross может значительно сократить объем шаблонного кода, который вы бы написали (иногда несколько раз на разных языках) при любом другом подходе к разработке приложений.
Структура решения MvvmCross
Сообщество MvvmCross рекомендует довольно простой и эффективный способ структурирования решения MvvmCross:
<ProjectName>.Core <ProjectName>.UI.Droid <ProjectName>.UI.iOS
Основной проект в решении MvvmCross связан с повторно используемым кодом. Основной проект — это проект Xamarin PCL, основное внимание в котором уделяется повторному использованию.
Любой код, написанный на Core, должен быть максимально независимым от платформы. Он должен содержать только логику, которую можно повторно использовать на всех платформах. Основной проект не должен использовать какой-либо Android или iOS API или иметь доступ к чему-либо специфичному для какой-либо платформы.
Уровень бизнес-логики, уровень данных и внутренняя связь — идеальные кандидаты для включения в проект Core. Навигация по иерархии представлений (активности, фрагменты и т. д.) будет осуществляться в Ядре.
Прежде чем продолжить, необходимо понять один шаблон архитектурного проектирования, который имеет решающее значение для понимания MvvmCross и того, как он работает. Как видно из названия, MvvmCross сильно зависит от шаблона MVVM.
MVVM — это шаблон архитектурного проектирования, который упрощает отделение графического пользовательского интерфейса от бизнес-логики и внутренних данных.
Как этот шаблон используется в MvvmCross?
Что ж, поскольку мы хотим добиться высокой степени повторного использования нашего кода, мы хотим иметь как можно больше в нашем ядре, которое является проектом PCL. Поскольку представления — это единственная часть кода, которая отличается от одной платформы к другой, мы не можем повторно использовать их на разных платформах. Эта часть реализована в проектах, связанных с платформой.
MvvmCross дает нам возможность организовать навигацию по приложениям из ядра с помощью ViewModels.
Избавившись от основ и технических деталей, давайте начнем с Xamarin, создав наш собственный проект MvvmCross Core:
Создание основного проекта MvvmCross
Откройте Xamarin Studio и создайте решение с именем ToptalExampleSolution
:
Поскольку мы создаем проект Core, рекомендуется придерживаться соглашения об именах. Убедитесь, что к имени проекта добавлен суффикс Core
.
Чтобы получить поддержку MvvmCross, необходимо добавить библиотеки MvvmCross в наш проект. Чтобы добавить, что мы можем использовать встроенную поддержку NuGet в Xamarin Studio.
Чтобы добавить библиотеку, щелкните правой кнопкой мыши папку «Пакеты» и выберите параметр « Добавить пакеты… ».
В поле поиска мы можем искать MvvmCross, который будет отфильтровывать результаты, связанные с MvvmCross, как показано ниже:
Нажатие на кнопку « Добавить пакет » добавит его в проект.
С добавлением MvvmCross в наш проект мы готовы написать наш основной код.
Давайте определим нашу первую ViewModel. Чтобы создать его, создайте иерархию папок следующим образом:
Вот о чем каждая из папок:
- Модели: модели предметной области, которые представляют содержимое реального состояния.
- Службы: папка, в которой находится наша служба (бизнес-логика, база данных и т. д.).
- ViewModel: то, как мы общаемся с нашими моделями
Наша первая ViewModel называется 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>(); }); } } }
Теперь, когда у нас есть наша первая ViewModel, мы можем создать наше первое представление и связать вещи вместе.
Пользовательский интерфейс Android
Чтобы показать содержимое ViewModel, нам нужно создать пользовательский интерфейс.
Первым шагом к созданию пользовательского интерфейса Android является создание проекта Android в текущем решении. Для этого щелкните правой кнопкой мыши имя решения и выберите « Добавить» -> «Добавить новый проект…» . В мастере выберите приложение Android и убедитесь, что вы назвали свой проект ToptalExample.UI.Droid
.
Как описано ранее, теперь нам нужно добавить зависимости MvvmCross для Android. Для этого выполните те же действия, что и для основного проекта, для добавления зависимостей NuGet.
После добавления зависимостей MvvmCross необходимо добавить ссылку на наш основной проект, чтобы мы могли использовать написанный там код. Чтобы добавить ссылку на проект PCL, щелкните правой кнопкой мыши папку «Ссылки» и выберите параметр « Редактировать ссылки… ». На вкладке «Проекты» выберите ранее созданный основной проект и нажмите «ОК».

Следующая часть может быть немного сложной для понимания.
Теперь мы должны сообщить MvvmCross, как он должен настроить наше приложение. Для этого нам нужно создать класс Setup
:
namespace ToptalExample.UI.Droid { public class Setup : MvxAndroidSetup { public Setup(Context context) : base(context) { } protected override IMvxApplication CreateApp() { return new Core.App(); } } }
Как видно из класса, мы сообщаем MvvmCross CreateApp
на основе реализации Core.App
, которая представляет собой класс, определенный в Core и показанный ниже:
public class App : MvxApplication { public override void Initialize() { RegisterAppStart(new AppStart()); } } public class AppStart : MvxNavigatingObject, IMvxAppStart { public void Start(object hint = null) { ShowViewModel<FirstViewModel>(); } }
В классе App
мы создаем экземпляр AppStart
, который будет отображать нашу первую ViewModel.
Осталось только создать файл макета Android, который будет связан 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>
В файле макета у нас есть привязки, которые автоматически разрешаются MvvmCross. Для EditText
мы создаем привязку для свойства Text, которая будет двусторонней привязкой. Любое изменение, вызванное со стороны ViewModel, будет автоматически отражено в представлении и наоборот.
Класс View
может быть действием или фрагментом. Для простоты мы используем активность, которая загружает данный макет:
[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); } }
Для первой кнопки у нас есть привязка команды, что означает, что когда мы нажимаем на кнопку, MvvmCross вызывает ContactNameCommand
из ViewModel.
Для второй кнопки мы собираемся показать другую ViewModel.
iOS интерфейс
Создание проекта для iOS на самом деле не отличается от создания проекта для Android. Вам нужно выполнить аналогичные шаги для добавления нового проекта, только на этот раз вместо Android просто создайте проект iOS. Просто убедитесь, что вы соблюдаете соглашение об именах.
После добавления проекта iOS необходимо добавить зависимости для MvvmCross iOS. Шаги абсолютно такие же, как для Core и Android (щелкните правой кнопкой мыши ссылку в вашем проекте iOS и нажмите « Добавить ссылку… »).
Теперь, как и для Android, необходимо создать класс Setup
, который будет сообщать MvvmCross, как настроить наше приложение.
public class Setup : MvxIosSetup { public Setup(MvxApplicationDelegate appDelegate, IMvxIosViewPresenter presenter) : base(appDelegate, presenter) { } protected override MvvmCross.Core.ViewModels.IMvxApplication CreateApp() { return new App(); } }
Обратите внимание, что класс Setup
теперь расширяет MvxIosSetup , а для Android — MvxAndroidSetup .
Одним из дополнений здесь является то, что мы должны изменить наш класс AppDelegate
.
AppDelegate
на iOS отвечает за запуск пользовательского интерфейса, поэтому мы должны сказать, как представления будут представлены на iOS. Подробнее о докладчиках можно узнать здесь.
[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; } }
Чтобы представить нашу модель VIewModel, нам нужно создать представление. В этом случае давайте создадим ViewController, щелкнув проект правой кнопкой мыши и выбрав « Добавить» -> «Новый файл» и выбрав ViewController из раздела iOS, который мы собираемся назвать FirstViewController.
Xamarin создает три файла, в которых мы собираемся определить, какими будут наши привязки. В отличие от Android, для iOS мы должны определять наши привязки по-другому, через код (хотя мы можем сделать это и на Android, а в некоторых случаях это необходимо).
Когда требуется перемещаться между представлениями, это делается через ViewModel. В команде NavigateToSecondViewModelCommand
метод ShowViewModel<SecondViewModel>()
найдет соответствующее представление и перейдет к нему.
Но как MVVMCross узнает, какое представление загружать?
В этом нет никакой магии. Когда мы создаем представление для Android (Activity или Fragment), мы расширяем один из базовых классов с параметрами типа ( MvxAppCompatActivity<VM>
). Когда мы вызываем ShowViewMolel<VM>
, MvvmCross ищет View
, который расширяет класс Activity
или Fragment
с параметрами типа VM
. Вот почему вам не разрешено иметь два класса представления для одной и той же ViewModel.
Инверсия контроля
Поскольку Xamarin просто предоставляет оболочки C# для собственных API-интерфейсов, он не предоставляет никаких форм механизма внедрения зависимостей (DI) или инверсии управления (IoC).
Без внедрения зависимостей во время выполнения или внедрения во время компиляции непросто создать слабосвязанные, повторно используемые, тестируемые и легко поддерживаемые компоненты. Идея IoC и DI известна очень давно; подробности о IoC можно найти во многих статьях в Интернете. Вы можете узнать больше об этих паттернах из вводной статьи Мартина Фаулера.
IoC был доступен с ранних версий MvvmCrosses и позволяет внедрять зависимости во время выполнения при запуске приложения и всякий раз, когда они требуются.
Чтобы получить слабосвязанные компоненты, мы никогда не должны требовать конкретных реализаций классов. Требование конкретных реализаций ограничивает возможность изменения поведения реализаций во время выполнения (вы не можете заменить его другой реализацией). Это затрудняет тестирование этих компонентов.
По этой причине мы собираемся объявить интерфейс, для которого у нас будет одна конкретная реализация.
public interface IPasswordGeneratorService { string Generate(int length); }
И реализация:
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(); } }
Наша ViewModel теперь может требовать экземпляр интерфейса IPasswordGenerationService
, за предоставление которого мы отвечаем.
Чтобы MvvmCross внедрил реализацию PasswordGeneratorService
во время выполнения, нам нужно сообщить MvvmCross, какую реализацию использовать. Если мы хотим использовать одну реализацию для обеих платформ, мы можем зарегистрировать реализации в App.cs
после регистрации приложения:
public override void Initialize() { RegisterAppStart(new AppStart()); Mvx.LazyConstructAndRegisterSingleton<IPasswordGeneratorService, PasswordGeneratorService>(); }
Приведенный выше вызов статического метода LazyConstructAndRegisterSingleton<TInterface, TType>
регистрирует внедряемую реализацию. Этот метод регистрирует соответствующую реализацию, но не создает объект.
Объект создается только тогда, когда это требуется, и только один раз, так как он зарегистрирован как синглтон.
Если мы хотим сразу создать одноэлементный объект, это можно сделать, вызвав Mvx.RegisterSingleton<TInterface>()
.
Бывают случаи, когда мы не хотим, чтобы в нашем приложении были только синглтоны. Наш объект может не быть потокобезопасным, или может быть какая-то другая причина, по которой мы хотим всегда иметь новый экземпляр. В этом случае MvvmCross предоставляет метод Mvx.RegisterType<TInterface,TType>()
, который можно использовать для регистрации реализации таким образом, чтобы при необходимости создавался новый экземпляр.
Если вам нужно предоставить отдельные конкретные реализации для каждой платформы, вы всегда можете сделать это в проектах для конкретных платформ:
public class DroidPasswodGeneratorService : IPasswordGeneratorService { public string Generate(int length) { return "DroidPasswordGenerator"; } }
А регистрация нашей реализации производится в классе Setup.cs
под проектом Droid:
protected override void InitializePlatformServices() { base.InitializePlatformServices(); Mvx.LazyConstructAndRegisterSingleton<IPasswordGeneratorService, DroidPasswodGeneratorService>(); }
После инициализации кода PCL MvvmCross вызовет InitializePlatformServices
и зарегистрирует реализацию службы для нашей платформы.
Когда мы регистрируем несколько одноэлементных реализаций, MvvmCross будет использовать только ту реализацию, которая была зарегистрирована последней. Все остальные регистрации будут аннулированы.
Создавайте кроссплатформенные приложения с помощью Xamarin
В этой статье вы увидели, как Xamarin позволяет вам обмениваться кодом на разных платформах, сохраняя при этом привычные ощущения и производительность приложений.
MvvmCross предоставляет еще один уровень абстракции, еще больше расширяющий возможности создания кроссплатформенных приложений с помощью Xamarin. Шаблон MVVM позволяет создавать потоки навигации и взаимодействия с пользователем, общие для всех платформ, что ограничивает объем кода для конкретной платформы, который необходимо написать, только представлениями.
Я надеюсь, что эта статья дала вам повод заглянуть в Xamarin и побудила вас создать с его помощью ваше следующее кроссплатформенное приложение.