Как реализовать навигацию Xamarin.Forms с помощью делегатов и координаторов

1656593655 kak realizovat navigacziyu xamarinforms s pomoshhyu delegatov i koordinatorov

автор Рафаэль Фиол

1*jQnSCD-sqgoSDH9ucrFRKA

В последнее время я много думал о том, как лучше реализовать навигацию страницей в мобильном приложении Xamarin.Forms. Мое глубокое погружение в эту тему началось, когда один коллега послал мне статью под названием «Координатор», написанную Сорушем Ханлоу. Сначала я был очень счастливый пассажир подножки Coordinator. По-видимому, я все еще есть. Это отличный способ подумать и реализовать разделение проблем. применительно к UI/UX и общей навигации.

Следующие примечания представляют мои мысли по этому поводу, знания по реализации в Xamarin.Forms и как делегатов на C# может помочь.

Почему координаторы?

Решение Сороша сосредотачивается в первую очередь на проблемах с переполненными контроллерами просмотра (в качестве основного примера цитируется iOS UIViewController) и предполагает, что основной обязанностью координатора должно быть взятие навигации и мутации модели. Он утверждает, что:

Когда мы изымаем эти задачи из контроллера представления, мы получаем контроллер представления инертный. Он может быть представлен, он может получать данные, преобразовывать их для презентации, отображать, но он не может изменить их.

Это хорошо. Это то, что мы хотим. Это означает, что мы можем повторно использовать наши контроллеры представления в качестве участников нескольких рабочих процессов со специально созданными координаторами для каждого рабочего процесса. Наши страницы и модели просмотра в наших программах MVVM больше не будут иметь проблем с беспорядочной навигацией.

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

Очевидное (и безобразное) решение – начать засорять свои страницы или модели просмотра условной логикой – обычно в форме операторов IF/THEN. Это никогда не масштабируется, и в больших приложениях вы получаете очень хрупкий код.

Координаторы для спасения.

Когда я начинал свои эксперименты по внедрению координаторов в Xamarin.Forms, я имел в виду несколько целей дизайна.

Во-первых, речь не идет о MVVM. У меня нет желания вводить еще один фреймворк MVVM для Xamarin. Кроме того, навигация не является проблемой MVVM. Я утверждаю, что View Models (и, в том случае, Pages) не должны знать о других моделях View (и страницах) в программе.

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

В-третьих, я хотел, чтобы реализация была простой, без большого количества принудительных объектов и без уменьшения никаких функций, которые делают RAD из Xamarin.Forms таким удивительным.

Делегаты на спасение.

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

Используя пример входа, который я цитировал раньше, как модель просмотра сигнализирует, что пользователь вошел успешно (или неудачно), и передает сгенерированный маркер доступа и информацию о пользователе рабочему процессу, который создал страницу? И, что еще более важно, как View Model публикует эту информацию? Существует ли контракт, который можно проверить без необходимости копаться в кучах кода?

Для меня решение заключалось в использовании делегатов. В C#, a делегат является переменной типа ссылки, содержащей ссылку на метод. Делегаты подобны указателям функций в C++. Однако делегаты безопасны и безопасны. Используя делегатов, я создаю крючкикакой абонент может использоваться для участия и влияния на рабочий процесс.

Например, в моей LoginViewModel я объявляю определение делегата как:

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

В LoginViewModel нет реализации этого делегата. Вместо этого вызов модели View реализует метод – обычно как анонимный метод или лямбда – создавая своего рода обратный вызов (или вебхук) узор.

Также у LoginViewModel есть реализация an ICommand вызываемый при нажатии кнопки входа. Именно с помощью этой команды после успешного входа в систему мы вызываем делегата. Ниже приведен фрагмент реализации команды. Я пропустил большую часть шаблонного кода, связанного с обработкой исключений и т.д.

Обратите внимание на последнюю строчку, которую вызывает делегат. Также обратите внимание на нулевое условие. Делегат вызывается только если он существует. Давайте посмотрим, как мы можем это использовать.

Заправка крючка.

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

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

Что касается страниц с кодом?

Раньше я говорил, что wМы определили стратегию для наших моделей просмотра, чтобы показать важные события жизненного цикла всем, кому это не безразлично.. Вопрос таков: кого точно волнует, когда завершится вход? Ну наш пока не определено Координаты будут заботиться. Но напомню, что моя вторая цель дизайна указывала:

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

Я принял решение, что это будет жесткое правило. Мои координаторы знают о страницах, составляющих рабочий процесс, но не знающих и не взаимодействующих с моделями просмотра этих страниц. Фактически мои страницы кода создают свои модели просмотра как защищенный свойства. Итак, чтобы сохранить контракты в чистоте мои страницы кода просто передают вызовы делегатов. К примеру, мой код страницы LoginPage выглядит так:

Обратите внимание, что страница кода регистрируется как объект, заинтересованный в обратном вызове от модели просмотра (строка 12), а затем — поскольку код позади себя не касается навигации — он просто передает обратный вызов на свой собственный делегат (строка 13).

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

Реализация координатора Xamarin

Учитывая это и мою третью цель дизайна, а именно…

Я хотел, чтобы реализация была простой, без большого количества принудительных объектов и без уменьшения никаких функций, которые делают RAD с Xamarin.Forms таким удивительным.

…Я решил, что страницы могут жить самостоятельно, или у них могут быть присоединенные координаторы. Мне пригодилось некоторое время, чтобы принять это решение. На самом деле я сначала начал с подхода к дизайну Координатор-первый, когда всем руководил Координатор. Я быстро понял, что это очень ограничивающее и очень сложно. Для этого требовался сложный менеджер псевдонавигации push/pop, и это ограничило мою способность легко использовать TabbedPages, MasterDetailPages и модальные. Я также нашел, что вставляю логику навигации в View Models. Мне это не понравилось.

Поэтому вместо этого я выбрал подход «Первая страница», по которому к страницам может быть присоединен координатор. Это решает большую проблему, связанную с уборкой мусора, поскольку платформа Xamarin.Forms уже обрабатывает сохранение и удаление страниц на основе жизненного цикла видимости. Если бы я перешел к подходу Coordinator-first, мне пришлось бы добавить кучу безобразной логики, чтобы самостоятельно управлять стеком.

С помощью подхода Page-first вы можете создать страницу как обычно и передать (присоединить) координатора через конструктор страницы. Итак, для страницы входа это выглядит примерно так:

Приятно в этом подходе, что я также могу ввести координатора в XAML. Это особенно полезно в ситуациях MasterDetailPage или TabbedPage, когда вы обычно не создаете экземпляры в коде. Как вот:

Прежде чем перейти к механике, давайте посмотрим на реализацию LoginCoordinator. В частности, мы сосредоточимся на том, какие обязанности берет на себя координатор, и все это инкапсулировано в методе координатора Start(). Здесь происходит вся магия – логика навигации.

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

Таким образом, этот LoginCoordinator действительно организует презентацию двух отдельных страниц — LoginPage и BandPickerPage. Каждую страницу можно использовать отдельно или как часть других рабочих процессов.

К примеру, страница BandPicker используется в другой части программы, когда пользователь хочет переключаться между своими активными диапазонами, не вводя снова. Страница BandPicker совершенно не знает, как она используется. Он просто должен сосредоточиться на том, что он делает – выбрать группы.

Рамочные материалы

Реализация самого Координатора достаточно проста. В моем подходе есть один интерфейс (называемый ICoordinator) и абстрактный базовый класс, частично реализующий этот интерфейс. Интерфейс выглядит так:

Каждый координатор должен реализовать только метод Start(). Два других метода – AttachToPage() и DetachFromPage() – реализованы в абстрактном базовом классе. Вот как это выглядит:

Координаторам программы просто необходимо расширить этот базовый класс и поменять способ Start(). Это почти все. Есть еще только одна часть каркас, являющийся простым базовым классом для подклассов ContentPage. Он не делает ничего другого, кроме как вызывающего методы AttachToPage() и DetatchFromPage() координаторов, переданных конструктору. Это оно.

Резюме

Большое спасибо Сорушу Ханлу за вдохновение. Я хотел бы услышать о том, как вы используете координаторы в своих собственных проектах Xamarin, и о любых идеях, которые могут потребоваться улучшить реализацию, которую я представил здесь.

Вы можете скачать мой образец программы с GitHub.

Добавить комментарий

Ваш адрес email не будет опубликован.