Kotlin Flow для Android - Краткое руководство

RxJava существует уже несколько лет и на сегодняшний день является одним из основных строительных блоков для многих проектов Android. Kotlin's Flow делает серьезные попытки побороться за это место, но может ли он заменить RxJava или он еще слишком незрелый, чтобы соответствовать нашим потребностям? Давайте сразу перейдем к основам, получая от вызовов API к нашему пользовательскому интерфейсу только поток Kotlin с LiveData, ViewModel и шаблоном репозитория!

Что это такое? 🤔

Kotlin Flow - это дополнение к Kotlin Coroutines. Сопрограммы спасают нас от ада обратных вызовов, давая нам возможность запускать асинхронный код, как если бы он был синхронным. Flow делает еще один шаг вперед, добавляя в смесь потоки. Вы говорите, почему бы не использовать каналы Coroutines? Каналы горячие, то есть, если мы вызываем канал, но не потребляем его данные (пока), он теряется. Потоки холодные, поэтому мы получаем данные, как только начинаем их собирать (как мы это делали с подпиской на потоки в RxJava).

Архитектура

Давайте начнем с рассмотрения нашей архитектуры. Здесь мы будем придерживаться шаблона MVVM в сочетании с шаблоном репозитория для нашего уровня данных.
Обычно с RxJava мы использовали бы Observables (или их варианты: Singles, Completables и т. д.) на всем уровне данных, обрабатывали бы это в нашей ViewModel и использовали LiveData, чтобы протолкнуть их в наш пользовательский интерфейс. В Kotlin Flow мы отойдем от Observables и будем использовать suspend функции в сочетании с Flow. Это означает, что наша архитектура будет выглядеть примерно так:

Сетевые вызовы с дооснащением

Retrofit - это наша стандартная библиотека для использования REST API, с которой очень легко работать. Если мы хотим использовать Flow на нашем уровне данных, нам не нужны дополнительные зависимости для его работы. Дооснащение поддерживает suspend функций, начиная с версии 2.6.0, и это как раз то, что нам нужно! Нет необходимости возвращать Observables, Singles, Flowables, Completables или даже Maybes, вы просто возвращаете желаемый объект или ответ и добавляете suspend к функции:

Сетевой уровень готово! ✅

Репозиторий

Итак, допустим, мы хотим вернуть ответ от нашего сетевого вызова через наш репозиторий в Flow. Мы можем начать с обертывания чего-нибудь в flow { ... }. Там мы можем выполнить необходимую работу (например, выполнить вызов API) и использовать emit для ввода нового значения.

В качестве альтернативы у вас есть такие функции, как flowOf(), или функции расширения, такие как asFlow(), которые также преобразуют объекты в поток и напрямую испускают его содержимое. Просто посмотрите, что вам подходит 🙂

Когда у нас есть Flow, мы можем выполнить некоторое сопоставление в нашем репозитории с помощью оператора .map { ... } и добавить потоки с помощью .flowOn(...).

Итак, наш репозиторий теперь выполняет вызов API и сопоставляет его с объектами пользовательского интерфейса в потоке IO, и все это обрабатывается через Flow!

ViewModel

У нас есть несколько вариантов в нашей ViewModel для обработки наших потоков. Мы начнем с простой версии, которая больше похожа на то, как мы привыкли делать что-то в RxJava. Чтобы использовать liveData Builder, проверьте часть ниже.

Итак, у нас есть репозиторий, который возвращает Flow, но как нам обработать эти данные и отправить их в наш пользовательский интерфейс?
Начнем с наших обычных подозреваемых: как изменяемый, так и неизменный LiveData объект. Что мы хотим сделать, так это передать значения в изменяемую переменную, поэтому первый вопрос - как получить данные через наш репозиторий. Получить данные потока просто: используйте функцию collect { ... }! Помните сопоставленный список, который мы поместили в наш репозиторий с помощью emit? Эти данные попадут в collect.

Поскольку мы можем вызывать collect только из сопрограммы или функции приостановки, мы будем использовать viewModelScope для запуска нашего потока и сбора данных:

Круто, мы просто передали данные из нашего API-вызова в LiveData для наблюдения в нашем пользовательском интерфейсе! Но как насчет того, когда возникает ошибка или мы хотим начать с других данных? Flow вас прикрывает! Мы можем emit значение, когда поток начинает сбор данных через onStart { ... }, а если возникает ошибка, мы можем поймать ее через catch { ... }.

Потрясающие! Теперь мы обрабатываем данные и загружаем несколько значений в пользовательский интерфейс!

catch поглотит исключение, но вы также можете использовать onCompletion, чтобы обработать исключение и продолжить работу вниз по течению. onCompletion будет запущен, когда поток завершится, и вернет исключение, если произошла ошибка, или null, если поток завершился успешно.

Конструктор LiveData

Вместо того, чтобы устанавливать значение LiveData вручную, как мы это делали в предыдущем примере, мы также можем использовать liveData builder. Взгляните на это как на область действия вашего объекта LiveData: он срабатывает, когда какой-либо пользовательский интерфейс начинает наблюдать за LiveData, и отменяется, если до его завершения нет (больше) наблюдателей. Использовать LiveData Builder очень просто! У нас есть два варианта: заключить код, который мы хотим выполнить, в liveData { ... } и emit значение или использовать функцию расширения asLiveData() для потока (которая в основном делает то же самое).

Вот и все, используя Flow с LiveData Builder! 👌 Обратите внимание, что после успешного выполнения блока кода в LiveData Builder он не будет перезапущен на более позднем этапе. Он будет перезапущен только в том случае, если построитель сам отменил выполнение (например, из-за неактивных наблюдателей).

Совет: LiveData Builder имеет настраиваемый тайм-аут, то есть вы можете настроить время, необходимое для отмены блока кода в сборщике, когда больше нет активных наблюдателей.

Вот и все! Вот как вкратце использовать Kotlin Flow в Android-проекте. Реализовать это было очень легко! Мы поместили его в одно из наших производственных приложений здесь, в PINCH, и мне оно очень понравилось. Некоторый опыт работы с RxJava действительно помогает, но я определенно думаю, что кривая обучения менее крутая. Кроме того, это избавляет нас от множества зависимостей в нашем проекте. Я определенно рекомендую использовать его, по крайней мере, хотя бы раз испытать его.

Удачного кодирования! 💪