
Ещё в качестве идеи Xamarin.Forms понравился всем WPF разработчикам: популярность создания приложений под Android и iOS росла, WPF становился пережитком, а востребованность WPF разработчиков неуклонно стремилась к нулю — Forms звучал, как спасение. Появилась надежда, что мы со своим знанием XAML и паттерна MVVM будем кому-нибудь нужны. Конечно, изначально Xamarin.Forms оказался сырым, с большим количеством багов и отсутствием некоторых жизненно необходимых вещей (вспомнить хотя бы input control без возможности указания maxwith).
Прошло три года и Microsoft приобрел Xamarin. Теперь он поставляет его вместе с Visual Studio, и как следствие: багов стало меньше, а возможностей из коробки — больше. Но осталась одна проблема: приложения с единым интерфейсом не получаются нативными. То есть, если появляется различие в интерфейсах Android и iOS, разработчик сталкивается с болью в виде создания отдельных ViewModel под каждую платформу…и это только цветочки.
Но мы в Mobile Dimension специализируемся на корпоративных приложениях, и в этом случае это единство интерфейса является плюсом. Более того, когда в компании много решений для различных целей, даже целые функциональные контроллы (форма авторизации или каталог товаров) должны выглядеть одинаково.
В такой ситуации хочется иметь возможность переиспользовать код не только в разных платформах, но и на разных проектах. Соответственно, нужно предусмотреть самодостаточность контроллов, т.е. контролл, существующий со всеми его функциями бизнес-логики, взаимодействием с REST API, кэшированием и пр. Причем такие контроллы должны быть независимы от наличия других контроллов. Необходимо создавать ситуацию, при которой контролл живет своей жизнью и вызывает (invoke) какие-то ивенты, когда что-то произошло.
Забегая вперед, отмечу, что выглядит все просто и естесственно, однако такой подход пришел к нам через долгие недели рефакторинга кода, где изначально сильно отделялся слой данных от слоя интерфейса с бизнесслогикой. При таком классическом подходе все здорово: понятно какой модуль за что отвечает, порог входа нового разработчика низок. Однако, когда проект разрастается до нескольких десятков контроллов, каждый из которых взаимодействет с другими — появляются неожиданные ошибки, связанные с запутанностью вызовов методов разных контроллов. Это все, в конечном итоге привел нас к так называемому «аду вьюмоделей».
Распутывается он благодаря подходу через сервисы и подписки на изменения в этих сервисах. Т.е. мы делаем акцент не на том, чтоб слой данных был абсолютно независимым от логики или интерфейса, а на том, чтоб какой-то конкретный контролл был абсолютно незамисимым от остальных контроллов.
Сегодня я покажу самый простой пример – авторизацию. Представим, что у нас есть REST точка для авторизации. Мы создаем контролл, способный авторизовывать пользователей во всех системах в компании. Его интерфейс реализуется один раз. Все требования по стилям указываются в разметке в контролле авторизации:
Разметка контролла авторизации
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<ActivityIndicator Grid.RowSpan="2" VerticalOptions="FillAndExpand" HorizontalOptions="FillAndExpand" Color="Red" IsEnabled="True" IsVisible="{Binding IsLoading}" IsRunning="True"/>
<Label Text="Авторизация" FontSize="Large" VerticalOptions="CenterAndExpand" HorizontalOptions="CenterAndExpand"/>
<StackLayout Grid.Row="1" Orientation="Horizontal">
<Entry Text="{Binding Login, Mode=TwoWay}" VerticalOptions="CenterAndExpand" HorizontalOptions="FillAndExpand"/>
<Button Text="OK" VerticalOptions="CenterAndExpand" Command="{Binding RegisterCommand}" BackgroundColor="Red" TextColor="White"/>
</StackLayout>
</Grid>
Верстка такого контролла — самая сложная часть его создания: надо учитывать то, что он должен быть адаптивным к любому из контейнеров, где в последствии может оказаться. Буквально надо остерегаться любой конкретной величины, пытаясь растягивать контент. Тут нам поможет Grid с его автоматической растяжкой контента по ячейкам и Vertical/HorizontalOptions. К сожалению, ViewBox (контролл, который растягивает контент) из UWP в Forms еще не реализовали, однако такая инициатива была предложена и за нее можно проголосовать на странице разработчиков Xamarin, где пользователи буквально приоритезируют «фичи» которые войдут в следующие билды Xamarin.Forms.
Затем реализуем ViewModel, в которой вызывается метод сервиса авторизации из бизнес-логики.
ViewModel регистрации
IAuthorizationService _authorizationService;
public AuthorizationViewModel()
{
this.RegisterCommand = new Command(async () => { await Registration(); });
_authorizationService = Core.DI.Container.GetInstance<IAuthorizationService>();
}
private async Task Registration()
{
IsLoading = true;
await _authorizationService.Login(Login);
IsLoading = false;
}
private string _login;
public string Login
{ get { return _login; }
set { _login = value; RaizePropertyChanged(nameof(Login));}
}
//INotifyPropertyChanged realization
Использование контролла
<local:AuthorizationView WidthRequest="300" VerticalOptions="CenterAndExpand" HorizontalOptions="Center"/>
Подписываться на изменение этого сервиса можно из любого места в приложении и обработать это соответствующим образом. Сервис отвечает только за рейз.
Подписка на событие контролла
var authorizationService = Core.DI.Container.GetInstance<IAuthorizationService>();
authorizationService.AuthorizationChanged += AuthorizationService_ AuthorizationChanged;
Этот контролл можно добавить и в любое другое место в проекте, в любой контейнер любого размера — выглядеть он будет везде хорошо, ибо мы позаботились о верстке.
Читайте в части 2: более сложные контроллы и их взаимодействие друг с другом.
Ссылка на github

