Працюємо з станами екранів в Xamarin.Forms

Друзі! Ми раді представити новий матеріал на тему розробки мобільних додатків на Xamarin. У новій статті ми розглянемо, як у Xamarin.Forms реалізовувати управліннями станами вікон (триває завантаження даних, відсутній інтернет і інші) на XAML. Всі статті з колонки можна знайти і прочитати за посиланням #xamarincolumn

Один екран, багато станів

Переважна більшість сучасних бізнес-додатків активно взаємодіють із зовнішніми інтернет-сервісами з метою отримання даних для відображення. Крім цього, часто виникають ситуації, в яких один і той же екран може показувати різні набори даних, аж до зміни оформлення.

Мобільні додатки, на відміну від веб-сайтів мають набагато швидше взаємодіяти з користувачем, тому показувати тривалий час порожній екран під час завантаження даних, вважається не дуже правильним. Додатково, додаток повинен повідомляти про помилки завантаження даних або відсутності інтернет-з'єднання. Ледачі розробники можуть обійтися відображенням спливаючих повідомлень в дусі «Помилка завантаження даних», але ми підемо іншим шляхом.



Отже, давайте виділимо основні стану одного (!) екрану:

  • завантаження даних (індикатор завантаження по центру екрана)
  • відсутній інтернет-з'єднання (супровідний текст, можливо красива картинка і кнопка «Повторити»)
  • помилка завантаження даних (супровідний текст, можливо красива картинка і кнопка «Повторити»)
  • немає даних (наприклад, порожня кошик для покупок)
  • відображення даних (наприклад, завантажений список товарів)




У програміста можуть почати ворушитися волосся при думках про те, скільки коду треба буде написати, щоб заміняти вміст одного екрану, при розрахунку, що таких екранів можуть бути десятки, а кожне з станів може бути досить складним. Рано панікувати, просте і елегантне рішення запропонував Patrick McCurley. Ми візьмемо це рішення за основу і трохи доопрацюємо.

Знайомтеся — це StateContainer

В основі даного підходу лежить ідея описувати всі стани екрану в XAML і управляти їх зміною з допомогою ViewModel. Забігаючи вперед, відзначимо, що рішення досить просте і може бути використане не тільки для управліннями станами всього вікна, але й окремих його частин.

Ось так буде виглядати XAML-опис однієї сторінки з підтримкою зміни станів:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage x:Class="ApiDemo.DemoPage"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:stateContainerDemo="clr-namespace:StateContainerDemo;assembly=ApiDemo">
<stateContainerDemo:StateContainer State="{Binding State}">

<stateContainerDemo:StateCondition State="Loading">
<ActivityIndicator IsRunning="True" />
</stateContainerDemo:StateCondition>

<stateContainerDemo:StateCondition State="Normal">
<Label Text="Дані завантажені і можемо їх відобразити"/>
</stateContainerDemo:StateCondition>

<stateContainerDemo:StateCondition State="Error">
<StackLayout>
<Label Text="Помилка завантаження даних" />
<Command Button="{Binding LoadDataCommand}" Text="ПОВТОРИТИ" />
</StackLayout>
</stateContainerDemo:StateCondition>

<stateContainerDemo:StateCondition State="NoInternet">
<StackLayout>
<Label Text="Відсутній інтернет-з'єднання" />
<Command Button="{Binding LoadDataCommand}" Text="ПОВТОРИТИ" />
</StackLayout>
</stateContainerDemo:StateCondition>

<stateContainerDemo:StateCondition State="NoData">
<Label Text="Немає даних, показуємо користувачеві запрошення до дії" />
</stateContainerDemo:StateCondition>
</stateContainerDemo:StateContainer>
</ContentPage>

Просто і зрозуміло. При цьому великі блоки для станів можна винести у вигляді окремих View для повторного використання.

Ось так буде описаний врапперов для одного стану:
[ContentProperty("Content")]
public class StateCondition : View
{
public object State { get; set; }
public Content View { get; set; }
}

А ось перерахування (enum) можливих станів екрану:
public enum States
{
Loading,
Normal,
Error,
NoInternet,
NoData
}

Ми трохи допрацювали State Container від Patrick McCurley, додавши прості анімації зміни стану, щоб все працювало плавно:
[ContentProperty("Conditions")]
public class StateContainer : ContentView {
public List<StateCondition> Conditions { get; set; } = new List<StateCondition>();

public static readonly BindableProperty StateProperty = BindableProperty.Create(nameof(State), typeof(object), typeof(StateContainer), null, BindingMode.Default, null, StateChanged);

public static void Init()
{
//for linker
}

private static async void StateChanged(BindableObject bindable, object oldValue, object newValue)
{
var parent = bindable as StateContainer;
if (parent != null)
await parent.ChooseStateProperty(newValue);
}

public object State
{
get { return GetValue(StateProperty); }
set { SetValue(StateProperty, value); }
}

private async Task ChooseStateProperty(object newValue)
{
if (Conditions == null && Conditions?.Count == 0) return;

try
{
foreach (var stateCondition in Conditions.Where(stateCondition => stateCondition.State != null && stateCondition.State.ToString().Equals(newValue.ToString ()))) {
if (Content != null)
{
await Content.FadeTo(0, 100U); //швидка анімація приховування
Content.IsVisible = false; //Повністю приховуємо з екрану старе стан
await Task.Delay(30); //Дозволяємо UI-потоку відпрацювати свою чергу повідомлень і гарантовано приховати попередній стан
}

// Плавно показуємо новий стан 
stateCondition.Content.Opacity = 0;
Content = stateCondition.Content;
Content.IsVisible = true;
await Content.FadeTo(1);

break;
}
} catch (Exception e)
{
Debug.WriteLine($"StateContainer ChooseStateProperty {newValue} error: {e}");
}
}
}


Для отримання статусу інтернет-з'єднання ми підключили бібліотеку ConnectivityPlugin.
if (!CrossConnectivity.Current.IsConnected)
{
State = States.NoInternet; // Змінюємо властивість у ViewModel
return;
}

Як бачимо, StateContainer це не надбудова над Page, а звичайна ContentView і може цілком спокійно розміщуватися на екрані зі статичним або вже завантаженим контентом. Це дозволить реалізувати механізми часткової дозавантаження даних, наприклад, коли у нас вже є назва і посилання на фотографію, які можна показувати користувачеві без необхідності очікування.



Висновок

Отже, сьогодні ми розглянули роботу зі станами екранів з допомогою простого й елегантного StateContainer, який дозволяє поліпшити користувальницький досвід. А прості анімації зміни станів додають плавності і надають додатком закінчений вигляд.

У наступній статті ми розглянемо питання інтеграції із зовнішнім REST API за допомогою Refit, ModernHttpClient і Polly.

Про авторів


В'ячеслав Черніков — керівник відділу розробки компанії Binwell. В минулому — один з Nokia Champion і Qt Certified Specialist, в даний час — спеціаліст по платформах Xamarin і Azure. У сферу mobile прийшов в 2005 році, з 2008 року займається розробкою мобільних додатків: починав з Symbian, Maemo, Meego, Windows Mobile, потім перейшов на iOS, Android і Windows Phone.

Корисні посилання

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.