Автор: Константин Марс
Когда мы пришли на Medtech Hackathon, нашей целью было создать простой и легковесный инструмент для пользователей, которые хотят вести учет аптечки и вовремя получать оповещения об истечении срока годности препаратов, чтобы приобрести новые.
Мы долго колебались с выбором платформы между популярной и престижной iOS и модным, современным и приятным в разработке Android. Поэтому я предложил использовать Xamarin, который, между прочим, использует C# как основной язык разработки (и это главный язык, на котором в повседневной жизни пишет организатор нашей команды Арсений). Таким образом мы подошли к началу путешествия в мир кроссплатформенной разработки с Xamarin.
Отмечу, что Xamarin изначально базировался на фреймворке Mono, и поэтому несколько отличается от оригинального .NET-фреймворка Microsoft. Но эти отличия обсудим немного позднее.
В статье не станем обсуждать подробности хакатона и не будем углубляться в профессиональные секреты разработки с помощью Xamarin. Эта статья — базовая вводная в мир кроссплатформенной разработки с помощью Xamarin Forms. Цель материала — дать общее представление, как быстро разрабатывать с помощью Xamarin, подсказать, где в будущем искать ответы на более конкретные вопросы.
Архитектура проекта. PCL
Xamarin Forms строится вокруг общего кросс-платформенного кода, и может быть построен по одному из архитектурных подходов — PCL (Portable Class Library) или SAP (Shared Assets Project). Платформо-зависимые проекты — неотъемлемая часть солюшн Xamarin, называются соответственно и размещены в солюшне наравне с проектом общего кода.
Например, в нашем приложении мы имеем такой набор проектов:
Как видите, присутствуют проекты для Android (Droid) и iOS, отдельный проект для AndroidWear (он корректно инсталируется вместе с Android-приложением на смарт-часы, но пока не выполняет заметную полезную работу, кроме демонстрации возможностей фреймворка Xamarin для создания AndroidWear проектов). Еще есть проект библиотеки VuforiaBindings library (Java wrapper, который был задуман как интегратор Java-библиотеки Vuforia для распознавания образов и текста). Это примеры ключевых видов проектов, наиболее часто встречающихся при разработке c помощью Xamarin.
Xamarin Forms. XAML
Главное преимущество разработки приложений с помощью Xamarin для меня — возможность создавать UI для нескольких платформ одновременно. Xamarin позволяет создавать отдельные XML-формы для платформо-зависимых проектов и делать UI разным для разных платформ, но все же основной подход — стартовать с Xamarin Forms, создавать XML пользовательского интерфейса, который будет одинаково работать на всех поддерживаемых платформах.
Это стало возможным благодаря тому, что каждый платформо-зависимый проект стартует код из общего проекта, осуществляя иньекцию небольшого участка кода, специфичного для Xamarin Forms.
Например, в Android-проекте это происходит в MainActivity так:
global::Xamarin.Forms.Forms.Init (this, bundle);
В iOS проекте:
global::Xamarin.Forms.Forms.Init ();
В то же время, если вы заглянете в MainActivity проекта для AndroidWear – вы не найдете там ничего связанного с Xamarin Forms. Это потому что проект для этой платформы пока не поддерживается Xamarin Forms и является “чистым” платформо-зависимым проектом, написанным на C#.
Разметка UI в Xamarin Forms наывается XAML. Здесь все очень похоже на “классический” .NET (WPF):
Внутри XAML выглядит тоже вполне привычно для тех, кто имеет опыт работы с .NET:
<StackLayout Orientation="Vertical" VerticalOptions="FillAndExpand">
<Label x:Name="label" Text="List of medicines" HorizontalOptions="Center" />
<StackLayout Orientation="Horizontal">
<Entry x:Name="nameEntry" HorizontalOptions="FillAndExpand" />
<DatePicker x:Name="datePicker" HorizontalOptions="End"/>
<Button x:Name="addButton" Text="Add" Clicked="add" HorizontalOptions="End" />
<Button x:Name="scanButton" Text="Scan" Clicked="scan" HorizontalOptions="End" />
</StackLayout>
Ключевой функционал Bindings работает по тем же принципам, что и биндинги в WPF:
<ListView x:Name="list" VerticalOptions="FillAndExpand">
<ListView.ItemTemplate>
<DataTemplate>
<TextCell Text="{Binding Name}" Detail="{Binding ExpireDate, StringFormat='Expires: {0:MM-dd-yy}'}">
<TextCell.ContextActions>
<MenuItem Clicked="onDelete" CommandParameter="{Binding .}" Text="Delete" IsDestructive="True" />
</TextCell.ContextActions>
</TextCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Мы просто размещаем “{Binding <VARIABLE NAME>}” в XAML вместо хардкода значений.
Основной проблемой для разработчиков Xamarin, которые работают в Xamarin Studio (например на Mac OS) является отсутсвие адекватного визуального редактора UI. Странно, что при этом студия обеспечивает разработчикам визуальные редакторы для конкретных платформ, таких как Android и iOS, но, увы, не для Xamarin Forms.
DependecyService. Платформо-специфичные возможности и Xamarin Forms
Когда приходит время реализовать что-то специфичное для платформы (например, нотификации), нам уже не обойтись без платформо-зависимого кода.
Это неизбежно — ведь те же нотификации реализуются совсем по-разному для Android и iOS, и эта разница выражается во многих нюансах поведения функционала. Но как тогда вызывать подобный платформо-зависимый код из UI, общего для всех платформо-зависимых проектов?
Здесь нам на помощь приходит DependencyService.
Как обычно в кросс-платформенной разработке, нужно объявить интерфейс в общем проекте и реализовать его в платформо-зависимых проектах. Единственным вопросом будет «как определить, какую реализацию вызвать в каждом конкретном случае?». И тут за работу берется DependencyService, магический деятель фреймворка Xamarin. В зависимости от того, для какой платформы мы собираем проект, DependencyService подставляет необходимую реализацию вместо интерфейса.
Также стоит отметить, что для того чтобы эта магия заработала, нужно использовать аннотацию Xamarin.Forms.Dependency:
[assembly: Xamarin.Forms.Dependency (typeof (NotificationHelperImpl))]
Например, интерфейс нотификаций, объявленный в общем проекте Xamarin Forms выглядит так:
namespace MedChestAssistant
{
public interface INotificationHelper
{
void notify(String message);
}
}
А платформо-зависимая реализация в Android-проекте выглядит так:
[assembly: Xamarin.Forms.Dependency (typeof (NotificationHelperImpl))]
namespace MedChestAssistant.iOS
{
public class NotificationHelperImpl: INotificationHelper
{
#region INotificationHelper implementation
public void notify (string message)
{
var notification = new UILocalNotification();
// set the fire date (the date time in which it will fire)
notification.FireDate = NSDate.FromTimeIntervalSinceNow(5);
// configure the alert
notification.AlertAction = "Medical Chest Reminder";
notification.AlertBody = "Lyrica will expire in 2 days. Don't forget renew it";
// modify the badge
notification.ApplicationIconBadgeNumber = 1;
// set the sound to be the default sound
notification.SoundName = UILocalNotification.DefaultSoundName;
// schedule it
UIApplication.SharedApplication.ScheduleLocalNotification(notification);
}
#endregion
public NotificationHelperImpl ()
{
}
}
}
В целом все достаточно просто :).
Зависимости. Пакеты. Галерея NuGet
Xamarin имеет довольно богатую библиотеку пакетов, совместимых с поддерживаемыми платфорамами — NuGet. Например, тот, кому нужно распознавание и сканирование баркодов может воспользоваться пакетом Zxing, совместимым с Xamarin.
После добавления пакет появися в списке подключенных пакетов проекта.
Вот, например, кросс-платформенная часть пакета Zxing в нашем кросс-платформенном проекте Xamarin Forms:
Иногда (например для библиотеки Zxing library) нам нужно осуществить также некоторую специфичную для конкретных платформ инициализацию. Например, для Zxing нужно выполнить такие строки на старте платформ-специфичных приложений:
Android:
ZXing.Mobile.MobileBarcodeScanner.Initialize (Application);
iOS:
ZXing.Net.Mobile.Forms.iOS.Platform.Init();
Также платформ-специфичная инициализация бывает нужна и для стандартного функционала.
Например, нотификции в iOS требуют декларирования поддерживаемых форматов на старте приложения (это делается всегда в нативном iOS, и Xamarin просто покрывает уже существующую специфику):
if (UIDevice.CurrentDevice.CheckSystemVersion (8, 0)) {
var notificationSettings = UIUserNotificationSettings.GetSettingsForTypes (
UIUserNotificationType.Alert | UIUserNotificationType.Badge | UIUserNotificationType.Sound, null
);
app.RegisterUserNotificationSettings (notificationSettings);
}
Вот и весь краткий обзор возможностей разработки с Xamarin.
Исходный код нашего проекта с хакатона вы можете посмотреть здесь: https://github.com/DataArt/MedChestAssistant.git
Заметьте, это всего лишь код, написанный за короткий отрезок времени на хакатоне. Поэтому здесь покрыты лишь некоторые основные возможности Xamarin. Вы не найдете тут сервисов, выполняющихся в бэкграунде, сложных архитектурных паттернов, юнит-тестов или dependency-injection, которые конечно же понадобятся в коммерческих проектах. Наше приложение призвано показать, как быстро и легко решать конкретные задачи в сжатые сроки с помощью отличного кросс-платформенного инструмента, называемого Xamarin.
Если вы хотите узнать больше про Xamarin, обратитесь к официальной книге от Microsoft https://developer.xamarin.com/guides/xamarin-forms/creating-mobile-apps-xamarin-forms/
и посетите Xamarin Portal для изучения самых актуальных рецептов кросс-платформенной разработки https://developer.xamarin.com/guides/xamarin-forms/creating-mobile-apps-xamarin-forms/
Удачи вам в мире кросс-платформа! И новых свершений с Xamarin! :)
Автор: DataArt