Центр Сообщений Xamarin.Forms - подписка
|

О чём Центр сообщений Xamarin.Forms мне не сообщил

Не так давно я сказал, что каждый раз, когда использую Центр Сообщений Xamarin.Forms — чувствую что жульничаю.

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

Но всегда есть особый случай…

Случай с Центром Сообщений

В основном проекте может произойти что-то, о чём он хочет чтобы знали проекты платформ… например, начало продолжительной фоновой загрузки.

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

Также в одной вью-модели может произойти что-то, о чём нужно знать другой вью-модели…

Вот это и есть веские причины для использования Центра Сообщений.

Кажется, синтаксис очень простой…

MessagingCenter.Subscribe<TSender>(object, string, Action<TSender>);

и

MessagingCenter.Send<TSender>(TSender, string);

Кажется, что это жульничество и… неправильно.

На «Evolve 2016» в выступлении о производительности Xamarin.Forms, Джейсон Смит (Jason Smith) сказал, что вместо стандартного центра сообщений предпочитает использовать что-то другое, типа Prism.

Кто я, чтобы спорить с человеком, создавшим Xamarin.Forms? Но… Я бы предпочел пользоваться базовыми возможностями Xamarin.Forms, насколько это возможно.

О чём Центр Сообщений мне не сообщил

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

Другими словами — классы не связаны друг с другом.

Но главное, о чём не сообщил мне Центр Сообщений, и причина по которой я никогда не использовал его правильно — то как я использовал аргумент TSender в функции MessagingCenter.Send.

Я всегда ставил туда имя класса-отправителя.

Например, если сообщение отправляется из LoginViewModel, а подписываются на него в UserDetailViewModel — подписка выглядела бы так:

MessagingCenter.Subscribe<LoginViewModel>(this, "successful_login", (lvm) => HandleLogin(lvm) );

Получается что UserDetailViewModel знает о LoginViewModel… и это не выглядит правильно.

Так и есть.

Не буду вдаваться в причины того, с чего я решил, что это правильный способ подписки…

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

Это может быть класс-маркер или класс без свойств и функций.

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

Отправка станет выглядеть так:

    MessagingCenter.Send<LoginMessage>(new LoginMessage, "successful_login");

А подписка так:

    MessagingCenter.Subscribe<LoginMessage>(this, "successful_login", (lm) => HandleLogin(lm));

Во-от… намного лучше — теперь вью-модели ничего не знают друг о друге.

И даже лучше — теперь сообщение может исходить от одного из проектов платформы без всяких проблем (Нет никакой возможности реализовать отправку класса из проекта платформы в основной проект).

В чём мораль сей басни?

В Центре Сообщений всегда используйте общий или маркерный класс, который не связан ни с отправителем, ни с получателем!

Дополнение от переводчика

Для упрощения подписки на сообщения я создаю классы со статическими текстовыми свойствами, значение которых совпадает с их именем. Это позволяет не запоминать текстовые «имена» сообщений и передавать полезные данные через объект сообщения, если необходимо:

    public class LoginMessage
    {
        public static string SuccessfulLogin => nameof(SuccessfulLogin);
        public static string InvalidUserName => nameof(InvalidUserName);
        public static string SessionExpired => nameof(SessionExpired);

        public LoginMessageData Data { get; }
        
        public LoginMessage(LoginMessageData data)
        {
            Data = data;
        }
    }

тогда подписка станет выглядеть вот так:

MessagingCenter.Subscribe<LoginMessage>(this, LoginMessage.InvalidUserName, (lm) => HandleLogin(lm));

а отправка так:

MessagingCenter.Send<LoginMessage>(new LoginMessage(new LoginMessageData()), LoginMessage.InvalidUserName);