February 8, 2021

Kotlin Flow. Послесловие.

Всем привет, меня зовут Алексей Гладков и я занимаюсь разработкой мобильных приложений уже около 7 лет. Недавно на моем канале вышло видео, посвященное механизмам работы такой технологии как Kotlin Flow

Kotlin Flow. Shared Flow. State 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 или launchIn как collect, если вы собираетесь как-то апдейтить UI, так как они работают даже, когда view не виден и это может привести к крашам

Третий момент, который вызвал больше всего обсуждений - это launch { }, launchWhenStarted { }. С одной стороны люди в комментариях правы, утверждая что в описании к функции написано следующее

launchWhenStarted Documentation

Однако, в описании вызова launchWhenStarted сказано следующее

https://developer.android.com/kotlin/flow/stateflow-and-sharedflow

То есть как мы видим функция будет переведена в suspend state как только сработает элемент жизненного цикла onStop. Это вызывает некую путаницу, потому что если Job создаваемый launchWhenStarted, WhenCreated и так далее уничтожается вместе с lifecycleScope, то в чем их разница? А разница как раз в этом suspen.

Вот что происходит на старте Fragment

onViewCreated lifecycle

Дальше при уходе с фрагмента в onStopped наша Job все еще живет и даже активна, а наш launchWhen уходит в suspend в зависимости от жизненного цикла выбранного нами (Started, Created и так далее).

onStop lifecycl

Однако при уничтожении экрана мы видим

onDestroy lifecycle

То есть вместе с уничтожением экрана, мы отменяем и 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