Kotlin Flow. Послесловие.
Всем привет, меня зовут Алексей Гладков и я занимаюсь разработкой мобильных приложений уже около 7 лет. Недавно на моем канале вышло видео, посвященное механизмам работы такой технологии как Kotlin Flow
После публикации появился ряд замечаний, которые я хотел бы прояснить плюс я нашел сам после активной работы с Flow кое-какие моменты, которые не успел затронуть в видео. Однако, на полноценное видео они не тянут, поэтому я подумал, что формат таких коротких статей отлично бы зашел. Если это так пишите в комментариях, потому что это новый для меня формат.
Итак, начнем. Самое главное, на что я хотел бы обратить внимание это сам класс BaseFlowViewModel.
private var _viewState : S ? = null protected var viewState : S get () = _viewState ?: throw UninitializedPropertyAccessException(" \" viewState \" was queried before being initialized" ) set (value) { _viewState = value _viewStates .value = value }
Самый важный момент здесь set(value), потому что, внезапно, я обнаружил, что StateFlow не обрабатывает одинаковые значения справедливо считая их одинаковыми. Однако, нам надо в случае с ViewAction часто достаточно обрабатывать одинаковые значения и я, пока выбрал следующее решение (которое мне не очень нравится), но оно работает
set (value) { if (_viewState == value) { _viewState = null _viewStates .value = null } _viewState = value _viewStates .value = value
То есть мы, фактически, проверяем, что значение одинаковое и просто делаем switch на null и обратно на наше значение. Если вы можете предложить решение лучше - пишите в комментариях.
Второй момент, как хорошо заметили в комментариях, сама работа со стейтами и экшнами внутри фрагмента тоже не очень изящно написана. В ходе проб и ошибок я в итоге остановился на следующем использовании.
Ниже код для использования
lifecycleScope .launchWhenStarted {
reportsViewModel .viewStates()
.filterNotNull()
.collect { obtainViewState(it ) }
}
lifecycleScope .launchWhenStarted {
reportsViewModel .viewActions()
.filterNotNull ()
.collect { obtainAction(it ) }
}
Здесь важный момент, что я запускаю обработку viewState и action в разных Job и происходит это потому, что Flow дожидается обработки collect и поэтому если поместить внутрь обработку action, то просто ничего не будет происходить.
Я видел совет использовать launchIn(scope), и это помогает работе всего этого внутри одной джобы, но как говорит нам при этом сайт dev Android
Третий момент, который вызвал больше всего обсуждений - это launch { }, launchWhenStarted { }. С одной стороны люди в комментариях правы, утверждая что в описании к функции написано следующее
Однако, в описании вызова launchWhenStarted сказано следующее
То есть как мы видим функция будет переведена в suspend state как только сработает элемент жизненного цикла onStop. Это вызывает некую путаницу, потому что если Job создаваемый launchWhenStarted, WhenCreated и так далее уничтожается вместе с lifecycleScope, то в чем их разница? А разница как раз в этом suspen.
Вот что происходит на старте Fragment
Дальше при уходе с фрагмента в onStopped наша Job все еще живет и даже активна, а наш launchWhen уходит в suspend в зависимости от жизненного цикла выбранного нами (Started, Created и так далее).
Однако при уничтожении экрана мы видим
То есть вместе с уничтожением экрана, мы отменяем и lifecycleScope как и написано в документации к функции. Поэтому в целом вся прослушка новых стейтов и действий отменяется именно в привязке к уничтожению экрана.
Ну и четвертое - это небольшое улучшение самого BaseFlowViewModel, предложенное Александром Нозиком (https://t.me/noraltavir)
Ссылка на обновленный BaseFlowViewModel со всеми изменениями
https://gist.github.com/AlexGladkov/0e5bc2a888e63038a82e88b430e50b8d#file-baseflowviewmodel-kt
Всем огромное спасибо и увидимся в следующих выпусках!
Я в Youtube - https://youtube.com/c/MobileDeveloper
Я в Telegram - https://t.me/mobiledevnews
Я в Instagram - https://instagram.com/nplau