Работаем с состояниями экранов в Xamarin.Forms

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

Один экран, много состояний


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

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

62d123518d5440299c88017057660b27.png

Итак, давайте выделим основные состояния одного (!) экрана:

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

eb08c7dde2af46e98fbfc24ff4754e07.png

У программиста могут начать шевелиться волосы при мыслях о том, сколько кода надо будет написать, чтобы заменять содержимое одного экрана, при расчете, что таких экранов могут быть десятки, а каждое из состояний может быть достаточно сложным. Рано паниковать, простое и элегантное решение предложил Patrick McCurley. Мы возьмем это решение за основу и немного доработаем.

Знакомьтесь — это StateContainer


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

Вот так будет выглядеть XAML-описание одной страницы с поддержкой смены состояний:



    

        
            
        

        
            

        
            
                

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

Вот так будет описан враппер для одного состояния:

[ContentProperty("Content")]
    public class StateCondition : View
    {
        public object State { get; set; }
        public View Content { get; set; }
    }

А вот перечисление (enum) возможных состояний экрана:
 public enum States
    {
        Loading,
        Normal,
        Error,
        NoInternet,
        NoData
    }

Мы немного доработали State Container от Patrick McCurley, добавив простые анимации смены состояния, чтобы все работало плавно:
[ContentProperty("Conditions")]
    public class StateContainer : ContentView {
        public List Conditions { get; set; } = new List();

        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 и может вполне спокойно размещаться на экране со статическим или уже загруженным контентом. Это позволит реализовать механизмы частичной дозагрузки данных, например, когда у нас уже есть название и ссылка на фотографию, которые можно отображать пользователю без необходимости ожидания.

7727965d2e114042b7b77a1ca85d8f14.png

Заключение


Итак, сегодня мы рассмотрели работу с состояниями экранов с помощью простого и элегантного StateContainer, который позволяет улучшить пользовательский опыт. А простые анимации смены состояний добавляют плавности и придают приложению законченный вид.

В следующей статье мы рассмотрим вопросы интеграции с внешним REST API с помощью Refit, ModernHttpClient и Polly.

Об авторах


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

Полезные ссылки


  • Анонс бесплатных инструментов Xamarin для разработки кроссплатформенных приложений на С# по ссылке
  • Visual Studio 2015 Community, Visual Studio Team Services, Visual Studio Code: бесплатные предложения для разработчиков
  • Дополнительные бесплатные инструменты и службы в программе Visual Studio Dev Essentials

Комментарии (0)

© Habrahabr.ru