Внешние события#

Внешние события позволяют передать результат фоновой работы текущему состоянию Источник SmEventSource<E> не знает о сессии, окне приложения и контроллере представления Вызов publish(event) сообщает о событии подпискам, а StateManager ставит обработку в очередь обычных задач

Источник и подписка#

Источник создаётся в прикладном модуле и передаётся первым аргументом в subscribeEvents Например, в EAM источник объявлен так:

object EamNotificationEvents {
    val received = SmEventSource<EamPushBatch>()
}

Фоновая служба сообщает о полученных уведомлениях через EamNotificationEvents.received.publish(batch) Публиковать события можно с любого потока

Контроллер представления объявляет подписку один раз в onInit через getSts():

override fun onInit(state: HomeState) {
    getSts().subscribeEvents(EamNotificationEvents.received, "receiveHomeNotifications") { current, batch ->
        current.dbs.getPkg(EamNotificationsPkg::class).acceptBatch(current.notificationOwner, batch)
    }.onHandled {
        // Необязательная реакция интерфейса после успешной обработки
        // Здесь можно поставить следующую задачу или переход через getSts()
    }
}

Обработчик получает текущее состояние и данные события В нём выполняются запросы и изменения БД через сессию состояния Обновление интерфейса обычно происходит в afterEnter контроллера, поэтому onHandled не обязателен

Порядок обработки#

  • Обработчик выполняется как postSharedTask, без изменения стека состояний

  • БД и файлы меняются на рабочем потоке StateManager, фиксацией и откатом владеет StateEventProcessor

  • afterEnter и afterEnterGui выполняются по правилам обычной задачи

  • onHandled вызывается на главном потоке после успешного завершения задачи, обновления контроллера и фиксации изменений Он не предназначен для работы с БД

  • Несколько событий ставят несколько задач в общую очередь, обработчики не выполняются параллельно Можно объявить несколько подписок на разные источники и задать каждой свой обработчик

Жизненный цикл подписки#

События принимает только текущее состояние открытого StateManager, когда окно находится в состоянии RESUMED Состояния под ним не реагируют, в сценарных тестах без интерфейса окно не требуется Перед выполнением задачи и вызовом onHandled проверяется, что подписка ещё открыта и принадлежит текущему состоянию Устаревшие задачи отсеиваются

Подписка принадлежит состоянию: при возврате назад она снова работает, при закрытии состояния удаляется subscribeEvents возвращает SmEventSubscription<E>, её close() позволяет отписаться раньше При восстановлении из резервной копии прежние подписки закрываются, новый контроллер объявляет их в своём onInit

Не регистрируйте подписку в afterEnter: этот метод повторяется после каждой задачи Приложению не нужно вручную отписываться и подписываться при каждом входе на экран

Что источник не гарантирует#

Источник не сохраняет события для повторной доставки События без активного подписчика, в фоне, после закрытия StateManager или во время блокирующего перехода могут быть пропущены Если результат нельзя потерять, он должен оставаться у источника данных и запрашиваться при возвращении Например, EAM повторно получает неподтверждённые уведомления с сервера

StateManager проверяет принадлежность подписки состоянию, но не принадлежность данных учётной записи Проверку пользователя выполняет прикладной обработчик, в EAM за неё отвечает EamNotificationsPkg.acceptBatch

Фоновая работа и ресурсы экрана#

Фоновая служба использует собственную сессию Передавать её в обработчик подписки или напрямую вызывать обработчик на потоке службы нельзя

Объекты периодической проверки экрана принадлежат контроллеру, а не представлению Compose В onInit ресурс привязывается к состоянию через state.registerClosable(resource) StateManager закрывает его при удалении состояния, восстановлении из резервной копии и завершении работы Жизненный цикл окна доступен через activityLifecycle

См также: состояние, контроллер представления, сессия