Внешние события#
Внешние события позволяют передать результат фоновой работы текущему состоянию
Источник 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, фиксацией и откатом владеетStateEventProcessorafterEnterиafterEnterGuiвыполняются по правилам обычной задачиonHandledвызывается на главном потоке после успешного завершения задачи, обновления контроллера и фиксации изменений Он не предназначен для работы с БДНесколько событий ставят несколько задач в общую очередь, обработчики не выполняются параллельно Можно объявить несколько подписок на разные источники и задать каждой свой обработчик
Жизненный цикл подписки#
События принимает только текущее состояние открытого StateManager, когда окно находится в состоянии RESUMED
Состояния под ним не реагируют, в сценарных тестах без интерфейса окно не требуется
Перед выполнением задачи и вызовом onHandled проверяется, что подписка ещё открыта и принадлежит текущему состоянию
Устаревшие задачи отсеиваются
Подписка принадлежит состоянию: при возврате назад она снова работает, при закрытии состояния удаляется
subscribeEvents возвращает SmEventSubscription<E>, её close() позволяет отписаться раньше
При восстановлении из резервной копии прежние подписки закрываются, новый контроллер объявляет их в своём onInit
Не регистрируйте подписку в afterEnter: этот метод повторяется после каждой задачи
Приложению не нужно вручную отписываться и подписываться при каждом входе на экран
Что источник не гарантирует#
Источник не сохраняет события для повторной доставки
События без активного подписчика, в фоне, после закрытия StateManager или во время блокирующего перехода могут быть пропущены
Если результат нельзя потерять, он должен оставаться у источника данных и запрашиваться при возвращении
Например, EAM повторно получает неподтверждённые уведомления с сервера
StateManager проверяет принадлежность подписки состоянию, но не принадлежность данных учётной записи
Проверку пользователя выполняет прикладной обработчик, в EAM за неё отвечает EamNotificationsPkg.acceptBatch
Фоновая работа и ресурсы экрана#
Фоновая служба использует собственную сессию Передавать её в обработчик подписки или напрямую вызывать обработчик на потоке службы нельзя
Объекты периодической проверки экрана принадлежат контроллеру, а не представлению Compose
В onInit ресурс привязывается к состоянию через state.registerClosable(resource)
StateManager закрывает его при удалении состояния, восстановлении из резервной копии и завершении работы
Жизненный цикл окна доступен через activityLifecycle
См также: состояние, контроллер представления, сессия