<?xml version="1.0" encoding="utf-8" ?><rss version="2.0" xmlns:tt="http://teletype.in/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:media="http://search.yahoo.com/mrss/"><channel><title>Алексей Гладков</title><generator>teletype.in</generator><description><![CDATA[Алексей Гладков]]></description><image><url>https://teletype.in/files/b5/cb/b5cb9e4c-fc3e-414f-afc4-2a6478e49277.jpeg</url><title>Алексей Гладков</title><link>https://teletype.in/@alexgladkov</link></image><link>https://teletype.in/@alexgladkov?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=alexgladkov</link><atom:link rel="self" type="application/rss+xml" href="https://teletype.in/rss/alexgladkov?offset=0"></atom:link><atom:link rel="next" type="application/rss+xml" href="https://teletype.in/rss/alexgladkov?offset=10"></atom:link><atom:link rel="search" type="application/opensearchdescription+xml" title="Teletype" href="https://teletype.in/opensearch.xml"></atom:link><pubDate>Mon, 15 Jun 2026 08:25:02 GMT</pubDate><lastBuildDate>Mon, 15 Jun 2026 08:25:02 GMT</lastBuildDate><item><guid isPermaLink="true">https://teletype.in/@alexgladkov/kotlin-flow-shared-state</guid><link>https://teletype.in/@alexgladkov/kotlin-flow-shared-state?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=alexgladkov</link><comments>https://teletype.in/@alexgladkov/kotlin-flow-shared-state?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=alexgladkov#comments</comments><dc:creator>alexgladkov</dc:creator><title>Kotlin Flow. Shared Flow. State Flow</title><pubDate>Mon, 08 Feb 2021 16:27:48 GMT</pubDate><media:content medium="image" url="https://teletype.in/files/ad/4d/ad4dc14e-7683-494c-b182-2c644dfba6d6.jpeg"></media:content><category>Kotlin</category><description><![CDATA[<img src="https://avatars.mds.yandex.net/get-zen_doc/3680683/pub_60215df5b73c460f6c5fcb83_60215f53b73c460f6c62f0ac/scale_1200"></img>Что такое Flow?]]></description><content:encoded><![CDATA[
  <p><strong>Что такое Flow?</strong></p>
  <p>Flow — это новая часть корутин, представленная компанией JetBrains и призванная сделать корутины более реактивным фреймворком, заменив собой Rx и LiveData. Забавно, что когда только представили корутины, одним из преимуществ, помимо легковесности, была практически синхронная работа. То есть вам не требуются колбэки и вы можете вычислять все данные прямо там, где они нужны.</p>
  <p>Но!</p>
  <p>После долгой работы внезапно выяснилось, что реактивность все-таки иногда нужна, а подходящего решения не было. Итак, что же такое флоу? Это обычная реализация паттерна наблюдатель. У вас есть источник данных и подписчик, и флоу как раз реализует источник данных. Вот сейчас вы можете видеть пример нескольких эмитированных интов.</p>
  <figure class="m_custom">
    <img src="https://avatars.mds.yandex.net/get-zen_doc/3680683/pub_60215df5b73c460f6c5fcb83_60215f53b73c460f6c62f0ac/scale_1200" width="716" />
    <figcaption>https://kotlinlang.org/docs/reference/coroutines/flow.html#asynchronous-flow</figcaption>
  </figure>
  <p>Код, который может быть приостановлен располагается внутри блока flow и он может быть suspendable. Что еще раз нас приближает к так называемому подходу DSL, который очень моден в наше время и используется практически повсеместно, например, в очень популярной библиотеке, я бы даже сказал фреймворке - Jetpack Compose.</p>
  <p><strong>Видео по Jetpack Compose</strong></p>
  <figure class="m_column">
    <iframe src="https://www.youtube.com/embed/bnQD6j9I1ag?autoplay=0&loop=0&mute=0"></iframe>
  </figure>
  <p>Здесь тоже используется этот подход и на самом деле он мне очень нравится, я бы даже сказал, он мне кажется намного более удобным, чем старый подход с простыми функциями. Он ярко выраженный, то есть, если я смотрю код, то могу читать его практически как обычную книгу.</p>
  <p>Flow имеет холодный вызов или Flows are cold, если дословно. Что это означает? Ну сами термины холодный и горячий это одно из следствий массовых заимствований в русский язык из английского. Наверняка вы слышали выражения холодный звонок или холодный клиент. В общем-то можно заменять слово холодный на неподготовленный. В случае с Flow холодный означает, что подписка не будет вызвана до тех пор пока не будет назначен подписчик.</p>
  <p>Давайте подробнее, возьмем наш пример.</p>
  <figure class="m_custom">
    <img src="https://avatars.mds.yandex.net/get-zen_doc/4569048/pub_60215df5b73c460f6c5fcb83_60215f99d1d01a0cf8ab6d7a/scale_1200" width="719" />
    <figcaption>https://kotlinlang.org/docs/reference/coroutines/flow.html#asynchronous-flow</figcaption>
  </figure>
  <p>Как мы видим, внутри функции simple мы выпускаем каждое значение i, пользуясь функцией emit. Учитывая, что я уже сказал, вы наверное уже догадались, что принт Flow started будет напечатан после Calling collect, как раз потому что запуск флоу является холодным, и на самом деле это очень круто, потому что вы таким образом можете регулировать жизненный цикл вашего флоу. Причем как вы видите на экране функция simple уже не является suspend, и это как раз потому что вы возвращаете себе как бы слепок будущих данных, а реальные данные вы начинаете получать уже в момент вызова collect.</p>
  <p>Существует несколько различных способов вызова флоу, но в целом я думаю вы итак их легко найдете в интернете, это просто разные способы как нам создать поток данных и это собственно тоже самое как и в Rx. Нам иногда нужны различные подходы к одному и тому же делу.</p>
  <figure class="m_column">
    <img src="https://avatars.mds.yandex.net/get-zen_doc/3473288/pub_60215df5b73c460f6c5fcb83_60215fceeccec86b33d74712/scale_1200" width="753" />
    <figcaption>https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/flow-of.html</figcaption>
  </figure>
  <figure class="m_custom">
    <img src="https://avatars.mds.yandex.net/get-zen_doc/3664204/pub_60215df5b73c460f6c5fcb83_60215fd9b73c460f6c642c7b/scale_1200" width="721" />
    <figcaption>https://kotlinlang.org/docs/reference/coroutines/flow.html#asynchronous-flow</figcaption>
  </figure>
  <p>Так же, как и с любым Observable, вы можете использовать и различные операторы трансформации, конкатенации и так далее. Если вы вдруг не понимаете, что за операторы я имею ввиду, то вот ссылка на <strong>видео по RxJava</strong></p>
  <figure class="m_column">
    <iframe src="https://www.youtube.com/embed/V-UkPijjJrk?autoplay=0&loop=0&mute=0&start=2434"></iframe>
  </figure>
  <p>Также, вместо onNext вы можете использовать onEach и это как бы несколько вас подвигает в сторону использования подхода работы не внутри самого subscribe или collect, а именно в аналоге doOnNext. И тоже самое при работе с ошибками.</p>
  <figure class="m_column">
    <img src="https://avatars.mds.yandex.net/get-zen_doc/4449015/pub_60215df5b73c460f6c5fcb83_6021601ed1d01a0cf8acaf23/scale_1200" width="881" />
    <figcaption>https://developer.android.com/kotlin/flow</figcaption>
  </figure>
  <h3>Backpressure</h3>
  <p>Один из самых частых вопросов чем же отличаются все-таки Observable и Flowable (не путать с Flow) - это проблема backpressure. Если вдруг вы не знали, то вот ссылка на <strong>видео по этой теме: </strong></p>
  <figure class="m_column">
    <iframe src="https://www.youtube.com/embed/Z0vB_TlvJJ4?autoplay=0&loop=0&mute=0"></iframe>
  </figure>
  <p>Вкратце, это проблема, когда у вас слишком много данных для обработки. Я люблю приводить в пример приложение, которое служит синтезатором для миди-пианино. Представьте, что вы посадили виртуоза за такое пианино, а вам нужно отрисовывать на экране нотки, которые этот виртуоз играет. Если виртуоз будет играть слишком быстро, а отрисовка слишком сложная, то рано или поздно ваш телефон перестанет справляться с этой задачей и не сможет принимать новые данные, которые генерирует наш гений.</p>
  <p>В случае с Rx это решается через Backpressure Strategy, а что с Flow? Если взять прям из коробки, то здесь все решается очень просто, ведь мы имеем дело с suspend функцией. Получается, если мы не можем обработать результат пришедший к нам из источника данных и мы имеем холодный запуск, то есть всем управляет именно коллектор, то мы можем просто держать функцию в suspend состоянии ровно до тех пор, пока наш коллектор не сможет обработать следующее значение. И это на самом деле довольно круто.</p>
  <p>Но, скажет мне требовательный читатель, а как же другие стратегии? Что если мне нужно, например, сбрасывать значения, которые не смогли обработаться, или накапливать их в буфер и так далее. И вообще, что если моя функция генерирует данные с задержкой, зачем все это делать последовательно? И читатель будет абсолютно прав, ведь делать все это последовательно совершенно не нужно.</p>
  <p>Давайте возьмем в пример функцию, которая будет так же генерировать нам инты, но при этом обработчик, то есть коллектор, тоже будет обрабатывать наш поток данных с некоторой задержкой, ну то есть наша ситуация с пианистом и отрисовкой на экране. Между нажатиями на клавиши 100 миллисекунд, и между отрисовками 300 миллисекунд.</p>
  <figure class="m_custom">
    <img src="https://avatars.mds.yandex.net/get-zen_doc/3507292/pub_60215df5b73c460f6c5fcb83_60216047390eb32b9bca9357/scale_1200" width="724" />
  </figure>
  <p>Если мы это распечатаем, то увидим, что примерно все это обработается за 1200 миллисекунд, то в общем-то логично 100 + 300 умножить на 3 будет 1200.</p>
  <p>Здесь нам поможет отличный оператор под названием buffer. Буфер это оператор для Flow, который позволяет нам запускать коллектор на другой корутине.</p>
  <figure class="m_column">
    <img src="https://avatars.mds.yandex.net/get-zen_doc/1577695/pub_60215df5b73c460f6c5fcb83_60216060eccec86b33d8aac3/scale_1200" width="757" />
    <figcaption>https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/buffer.html</figcaption>
  </figure>
  <p>И это позволяет нам как выполнять параллельно работу коллектора и эмиттера, так и выбирать разные стратегии переполнения буффера. Как вы наверное заметили у функции есть два параметра. Первый - это капасити, который является флагом.</p>
  <p>Здесь остановимся подробнее. Раз у нас эмиттер запущен в отдельной корутине и коллектор запущен в отдельной корутине, то нам нужно как-то передавать между ними данные. Для этого используется механизм Channel. Более подробно расскажу про него как-нибудь отдельно.</p>
  <p>А второй параметр у нас фактически определяет стратегию, которой мы будем пользоваться при переполнении нашего буфера и здесь все очень похоже на RxJava, поэтому особо разбирать не будем. Но очень интересно взглянуть на саму реализацию буфера внутри.</p>
  <figure class="m_column">
    <img src="https://avatars.mds.yandex.net/get-zen_doc/1704908/pub_60215df5b73c460f6c5fcb83_60216096b73c460f6c65e8e8/scale_1200" width="970" />
    <figcaption>Buffeer изнутри (https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/buffer.html)</figcaption>
  </figure>
  <p>Здесь мы видим более подробно, что capacity работает именно как флаг и что есть интересный флаг под названием <strong>Conflated</strong>, и на самом деле как мы видим из исходников это просто некая такая иконка на стратегию брать самый последний элемент из потока, то есть, в целом вы можете это реализовать через <strong>capacity = Buffered</strong> и стратегию <strong>Drop_Oldest</strong>. Также капасити принимает и любое не нулевое значение, что позволяет четко определить емкость данных для передачи в канале.</p>
  <p>То есть, возвращаясь к нашему примеру, мы получим уже не 1200 миллисекунд, а около 1000 (на самом деле в тестах все время разное, но в среднем около 1050 мс). Это получается из-за того, что у нас есть некие сдвиги и иногда все же получаются паузы, когда простаивают и эмиттер и коллектор, но все равно 15% прирост времени просто за счет одного оператора это очень неплохо. Однако, как и в любой асинхронности, как только мы начинаем работать с неким общим ресурсом, а в случае андроида с любым долгоживущим запросом (сеть, чтение из базы данных или из файла) у нас сразу же встает проблема как это все делать на разных потоках.</p>
  <h3>Dispatchers</h3>
  <p>Как мы уже знаем из корутин у нас есть различные диспетчеры на которых мы можем выполнять те или иные действия. И так как в видео про корутины, где у нас был такой быстрый беглый взгляд мы особо никак не разбирали как это работает, я думаю будет правильно сделать это здесь. <strong>Видео про корутины:</strong></p>
  <figure class="m_column">
    <iframe src="https://www.youtube.com/embed/jZ8LeeGQw1k?autoplay=0&loop=0&mute=0&start=676"></iframe>
  </figure>
  <p>Итак, что вообще такое диспетчеры? Наверняка вы делали в своем коде что-то вроде withContext(Dispatchers.Default), но возможно даже не задумывались над тем, что в этот момент происходит. В отличие от стандартной многопоточности, где мы создаем новый поток каждый раз, когда нам нужно сделать новую задачу, в корутинах мы можем иметь несколько корутин на одном потоке (и причем довольно много). Достигается этот эффект из-за тех самых точек приостановки, которые мы разбирали в <strong>видеостатье о корутинах:</strong></p>
  <figure class="m_column">
    <iframe src="https://www.youtube.com/embed/yBxaE-qAPdI?autoplay=0&loop=0&mute=0&start=149"></iframe>
  </figure>
  <p>Кто знаком с Pх, вспоминаем все эти бесчисленные subscribeOn и observeOn. И там все очень просто: вы либо стартуете в новом потоке, либо выполняетесь на том потоке, где был создан Рх объект, но как же быть с тем, когда у нас может выполняться несколько корутин? Здесь нам поможет интерфейс под названием CoroutineContext, который, включен в стандартную библиотеку котлина. Ссылка на этот класс: <a href="https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.coroutines/-coroutine-context/" target="_blank">https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.coroutines/-coroutine-context/</a></p>
  <p>Это набор различных элементов один из которых это Job, но об этом возможно расскажу как-нибудь отдельно. Самое главное, что нас интересует — это CoroutineDispatcher, который как раз и устанавливает связь между потоком или потоками и корутиной, которая должна выполниться. С помощью этого параметра мы можем запустить корутину на главном потоке, на новом созданном потоке, на каком-то специфичном потоке, который мы хотим указать и так далее.</p>
  <p>Создавая корутину, не важно через launch или асинк, вы всегда можете указать параметром на каком диспетчере это будет сделано. А можете и не указывать, потому что этот параметр опциональный.</p>
  <p>По коду ниже сразу видно что происходит когда мы указываем или не указываем диспетчер. Особый интерес здесь вызывает именно когда мы не указываем его. Потому что когда указываем тут примерно все понятно, а вот что будет если не указать в явном виде? В таком случае наша корутина унаследует контекст того места откуда была вызвана и если ее вызывать из главного потока, или точнее контекста, который привязан к главному потоку, то логично, что выполнена она будет на главном потоке.</p>
  <p>Поэтому будьте аккуратны, вот буквально недавно сталкивался с запросом от одного из людей, что у человека были постоянные ошибки, как раз связанные с тем, что работа UI операций постоянно срабатывала на диспетчере, который был привязан к воркер треду. Причем как и многое другое, связанное с потоками это может давать весьма нетривиальные ошибки. То есть, когда у вас приложение крашится это на самом деле далеко не самый плохой вариант. Вы сразу понимаете ага ну упало, берете, фиксите и дальше живете спокойной жизнью, а вот когда например у вас приложение не падает, но перерасходует батарею на 20% больше, чем могло бы вот это страшно, потому что по моему опыту такое может жить годами, прежде чем это найдут. Вообще тем performance очень недооцененная, особенно для андроид приложений. Если эта тема интересна напишите в комментариях и я сделаю видео.</p>
  <figure class="m_custom">
    <img src="https://avatars.mds.yandex.net/get-zen_doc/4721351/pub_60215df5b73c460f6c5fcb83_6021618deccec86b33db7e99/scale_1200" width="731" />
  </figure>
  <p>Итак я думаю более-менее с диспетчерами мы разобрались, но давайте теперь посмотрим как это работает в Flow. На самом деле довольно просто, но как всегда есть нюанс. Может показаться, что раз мы используем внутри нашего flow билдера suspend функции, то и контекст нужно переключать внутри них. Однако, это не так. При таком вот подходе</p>
  <figure class="m_custom">
    <img src="https://avatars.mds.yandex.net/get-zen_doc/4447423/pub_60215df5b73c460f6c5fcb83_602161a3eccec86b33dbb18c/scale_1200" width="620" />
    <figcaption>https://kotlinlang.org/docs/reference/coroutines/flow.html#flows-are-cold</figcaption>
  </figure>
  <p>вы получите Exception, который в явном виде вам скажет, что у вас коллектор и эмиттер работают на разных диспетчерах и это мол недопустимо, поэтому такой способ переключения не работает. Правильный способ — использовать оператор flowOn и уже в нем указывать диспетчер, на котором произойдет эмиссия новых значений.</p>
  <figure class="m_custom">
    <img src="https://avatars.mds.yandex.net/get-zen_doc/4449015/pub_60215df5b73c460f6c5fcb83_602161ce390eb32b9bce2a65/scale_1200" width="700" />
    <figcaption>https://kotlinlang.org/docs/reference/coroutines/flow.html#flows-are-cold</figcaption>
  </figure>
  <p>Момент, который на мой взгляд заслуживает отдельного внимания это то, что оператор flowOn является обычным цепочечным оператором билдера. К слову о том, что сейчас билдеры уже не используют и все пишут на конструкторах с параметрами. Если не понятно о чём речь, то вот ссылка на <strong>видео про Строитель:</strong></p>
  <figure class="m_column">
    <iframe src="https://www.youtube.com/embed/k_P7SDWopIw?autoplay=0&loop=0&mute=0"></iframe>
  </figure>
  <p>Раз оператор является цепочечным, значит логично, что мы можем вызвать его сколько угодно раз для различных других цепочечных операций таких как фильтр, маппинг и так далее. Если вы хотите, чтобы операции по фильтрации у вас проходили например на воркере, а операции по принту всего этого дела на главном потоке, то важно помнить, что оператор flowOn действует только на те операторы, которые предшествуют оператору flowOn.</p>
  <figure class="m_column">
    <img src="https://avatars.mds.yandex.net/get-zen_doc/4759087/pub_60215df5b73c460f6c5fcb83_60216211b73c460f6c6967fc/scale_1200" width="751" />
  </figure>
  <p>Как вы видите выше, мы выполняем флоу и мап на потоке ввода вывода, потому что они идут перед диспетчер ИО. Ну и видно как выполняются следующие операции.</p>
  <p>Это очень важная штука для понимания, потому как я много раз на практике сталкивался с бездумно написанными сабскрайбами, обсервами и т.д., авторы которых не понимают как именно это работает под капотом. Я, конечно, понимаю, что в мобильной разработке у вас вряд ли может возникнуть подобная ситуация, однако, может когда-нибудь и вам это пригодится.</p>
  <p>Здесь может возникнуть интересный вопрос — что будет если написать несколько раз подряд flowOn с разными диспатчерами. Отвечаю: операция выполнится в итоге на том диспетчере, который был первым в этом списке.</p>
  <h3>Combining</h3>
  <p>Так как Flow позиционируется как полноценная замена Rx (как и Лайвдата кстати тоже), то естественно у флоу есть аналоги операторов комбинирования нескольких флоу, так как без этого трудно представить ни одно серьезное приложение - разные источники данных, микросервисные архитектуры, сервисы всякие левые, все это порождает необходимость женить потоки данных между собой.</p>
  <p>Здесь у тех, кто знаком с Рх все будет предельно узнаваемо и знакомо. Оператор зип перекочевал в своем первозданном виде и сюда.</p>
  <figure class="m_custom">
    <img src="https://avatars.mds.yandex.net/get-zen_doc/3531091/pub_60215df5b73c460f6c5fcb83_60216227d1d01a0cf8b17ae7/scale_1200" width="656" />
  </figure>
  <p>Как видите, все до боли знакомо. Хотя не совсем. Всё-таки сам вызов отличается и это на самом деле больно, потому что если вам надо соединить три, четыре или более флоу, возникнет проблема. Однако, это как правило не нужно, так как в мобильных приложениях мы больше оперируем синглами и это можно сделать через обычные корутины. Тем не менее, я нашел вполне себе нормальное решение как сделать подобную функцию самому. Надеюсь, JetBrains добавят такую реализацию к себе, ведь она может быть полезной.</p>
  <p>Не буду задерживаться и на других реализациях операторов комбинации, их вы сможете найти по ссылке, там можно посмотреть различные combine, flatten и так далее операторы: <a href="https://kotlinlang.org/docs/reference/coroutines/flow.html#flows-are-cold" target="_blank">https://kotlinlang.org/docs/reference/coroutines/flow.html#flows-are-cold</a></p>
  <p>А теперь давайте поговорим про совершенно новые штуки, которые нам относительно недавно представили JetBrains, а так же попробуем все это на практике.</p>
  <h3>SharedFlow. StateFlow</h3>
  <p>Чтобы разобраться с этими понятиями нам нужно будет вернуться в историю. Если хотите разобраться более подробно, я рекомендую прочитать статью на хабре: <a href="https://habr.com/ru/post/529944/" target="_blank">https://habr.com/ru/post/529944/</a></p>
  <p>Сказанное мной далее будет частично пересекаться с этой статьей, потому как это перевод статьи автора всего этого добра Романа Елизарова.</p>
  <p>Если не вдаваться в детали, то для общения между корутинами ранее использовалась штука под названием Channel (уже второй раз встречается) и она была достаточно дорогостоящим решением с точки зрения производительности. В целом можете себе представить ситуацию, когда у вас есть здоровенный такой цех и общение между отдельными операторами станков происходит через письма отправляемые по трубкам. А вам иногда очень важно, чтоб ваши операторы работали, где-то синхронно, где-то асинхронно. Думаю можете представить себе масштаб бедствия.</p>
  <p>Как мы помним из ранее сказанного, наши флоу холодные, то есть пока не появится подписчик данные не начнут эмиттиться. Но что делать с данными, которые эмиттятся вне зависимости от того подписан на них кто-то или нет. Ну, например, датчик геолокации. Ваш телефон перемещается в пространстве постоянно и соответственно датчик генерит новые данные.</p>
  <p>Тут нам помогает сущность под названием Shared Flow. Это достаточно интересная штука в плане реализации, как мне показалось. Дело в том, что Shared Flow хранит в себе некий кэш, из которого достаются данные когда на них будет подписан коллектор.</p>
  <figure class="m_column">
    <img src="https://avatars.mds.yandex.net/get-zen_doc/1888335/pub_60215df5b73c460f6c5fcb83_60216279eccec86b33dd8f7a/scale_1200" width="781" />
    <figcaption>https://habr.com/ru/post/529944/</figcaption>
  </figure>
  <p>И только после того как будет воспроизведен кэш вы начнете получать новые данные. То есть как бы получается, пока нет коллекторов SharedFlow просто как бы подписан сам на себя и просто пишет себе данные в кэш. Очень изящно и круто сделано, как по-мне. Это может быть нам полезно в случае так называемых горячих данных (датчики, события операционной системы и так далее).</p>
  <p>Но также вы можете реализовать и стандартную шину событий через это, то есть что-то такое, что будет источником данных во всем приложении. Ну, например, если вам надо реализовать стандартную штуку, связанную с отображением некоторых служебных сообщений для пользователя когда он вернулся в аппку. Представьте, вы находитесь в приложении по подбору неких авто на продажу. Пока пользователь свернул приложение у вас сгенерировалось несколько новых вариантов, а когда он вернется в аппку вы сможете отобразить ему сообщения.</p>
  <figure class="m_custom">
    <img src="https://avatars.mds.yandex.net/get-zen_doc/3179652/pub_60215df5b73c460f6c5fcb83_60216296b73c460f6c6a8b30/scale_1200" width="700" />
    <figcaption>https://habr.com/ru/post/529944/</figcaption>
  </figure>
  <p>Причем, используя параметры, вы можете играться с тем что именно увидит пользователь: все ли события отсеивать, дропать первые или последние и так далее. Если shared flow переполнится, то он приостановит эмиттеры до тех пор, пока их не обработает, но при этом он не дожидается обработки их коллекторами. То есть у вас получается очень быстрая шина данных внутри вашего приложения.</p>
  <p>Также вместе с SharedFlow ребята из JetBrains сделали MutableSharedFlow, который позволяет нам, например, получать данные одновременно из suspend источников и из non-suspend источников. Ещё он позволяет нам сбросить кэш при необходимости.</p>
  <p>Вообще мне это всё напомнило добрые старые стратегии мокси. Фактически, теперь вы можете играясь с replayCache у SharedFlow реализовывать те самые стратегии, только применяя их например к viewState. Более подробно мы рассмотрим это в примере. Но подумав о стратегиях, я не могу не вспомнить одну из самых популярных стратегий SingleStateStrategy. Кстати, если вдруг кто не видел<strong> видео про мокси:</strong></p>
  <figure class="m_column">
    <iframe src="https://www.youtube.com/embed/OHzTBEBYNAM?autoplay=0&loop=0&mute=0&start=86"></iframe>
  </figure>
  <p>Так вот, чтобы реализовать эту стратегию, а она заключается в демонстрации последнего полученного значения из источника данных, был сделан специальный интерфейс под названием StateFlow. Всю механику работы StateFlow можно увидеть на следующем изображении.</p>
  <figure class="m_column">
    <img src="https://avatars.mds.yandex.net/get-zen_doc/3524431/pub_60215df5b73c460f6c5fcb83_602162c7d1d01a0cf8b2e4ec/scale_1200" width="1200" />
    <figcaption>https://habr.com/ru/company/redmadrobot/blog/325816/</figcaption>
  </figure>
  <p>Здесь в примере можно увидеть что будет, если реализовать передачу данных через StateFlow. То есть к нам приходило три события 1, 2, и 3, событие 1 было последним, соответственно, при подписке на StateFlow мы получим событие 1, потому что он было последним. Вот такая вот простая механика.</p>
  <p>Новые подходы SharedFlow и StateFlow обеспечивают более удобную механику взаимодействия с потоками, а также серьезно экономят ресурсы процессора, поэтому старые механики через ConflateBroadcastChannel и BroadcastChannel были объявлены устаревшими. Однако, сама механика Channel оставлена, так как многое в работе флоу и shared flow по прежнему полагается на этот механизм.</p>
  <p>Я очень люблю инструменты, которые позволяют мне работать сразу с кучей функционала и очень не люблю, когда мы берем одно из котлина, другое из jetpack (намекаю тут на LiveData), третье из груви, четвертое из хмл, пятое еще откуда-нибудь и так далее. Котлин этим меня и привлекает. Он позволяет работать с какими-то механиками, которые друг друга усиливают. По этой же причине нативная разработка никогда не будет полностью вытеснена любыми не нативными решениями. Любое нативное решение будет быстрее и качественнее и лучше совместимо. Поэтому я для себя решил, что короткое время, когда я использовал livedata закончилось и я лучше буду использовать полностью инструменты языка. По этой же причине я топлю за Jetpack Compose, по этой же причине я топлю на kotlin kts и так далее. Просто потому, что это удобно и проще пишется. В нашем мире бесконечно развивающихся технологий и меняющихся подходов пытаться уследить вообще за всем — самоубийство. Вы просто взорвёте себе мозг. Потому очень аккуратно нужно подходить к тому, что вы изучаете. Хорошо, что здесь на канале вы всегда найдете отобранные и отфильтрованные мной подходы ))</p>
  <p>Всем огромное спасибо и увидимся в следующих выпусках!</p>
  <p><strong>Практическую часть вы можете увидеть здесь:</strong></p>
  <figure class="m_column">
    <iframe src="https://www.youtube.com/embed/dlgaO3T96eY?autoplay=0&loop=0&mute=0"></iframe>
  </figure>
  <p>Я в Youtube - <a href="https://youtube.com/c/MobileDeveloper" target="_blank">https://youtube.com/c/MobileDeveloper</a></p>
  <p>Я в Telegram - <a href="https://t.me/mobiledevnews" target="_blank">https://t.me/mobiledevnews</a></p>
  <p>Я в Instagram - <a href="https://instagram.com/nplau" target="_blank">https://instagram.com/nplau</a></p>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@alexgladkov/kotlin-flow-aftermath</guid><link>https://teletype.in/@alexgladkov/kotlin-flow-aftermath?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=alexgladkov</link><comments>https://teletype.in/@alexgladkov/kotlin-flow-aftermath?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=alexgladkov#comments</comments><dc:creator>alexgladkov</dc:creator><title>Kotlin Flow. Послесловие.</title><pubDate>Mon, 08 Feb 2021 07:09:17 GMT</pubDate><media:content medium="image" url="https://teletype.in/files/6f/1b/6f1b0a85-bacc-4a37-a14b-1501fa5a5ca9.png"></media:content><description><![CDATA[<img src="https://teletype.in/files/18/99/1899d912-0f2e-463e-aebf-ef06301aae56.png"></img>Всем привет, меня зовут Алексей Гладков и я занимаюсь разработкой мобильных приложений уже около 7 лет. Недавно на моем канале вышло видео, посвященное механизмам работы такой технологии как Kotlin Flow]]></description><content:encoded><![CDATA[
  <p>Всем привет, меня зовут Алексей Гладков и я занимаюсь разработкой мобильных приложений уже около 7 лет. Недавно на моем канале вышло видео, посвященное механизмам работы такой технологии как <strong>Kotlin Flow</strong></p>
  <figure class="m_column">
    <iframe src="https://www.youtube.com/embed/dlgaO3T96eY?autoplay=0&loop=0&mute=0"></iframe>
    <figcaption>Kotlin Flow. Shared Flow. State Flow</figcaption>
  </figure>
  <p>После публикации появился ряд замечаний, которые я хотел бы прояснить плюс я нашел сам после активной работы с Flow кое-какие моменты, которые не успел затронуть в видео. Однако, на полноценное видео они не тянут, поэтому я подумал, что формат таких коротких статей отлично бы зашел. Если это так пишите в комментариях, потому что это новый для меня формат.</p>
  <p>Итак, начнем. <strong>Самое главное</strong>, на что я хотел бы обратить внимание это сам класс BaseFlowViewModel.</p>
  <pre>private var _viewState : S ? = null
protected var viewState : S
   get () = _viewState ?: throw UninitializedPropertyAccessException(&quot; \&quot; viewState \&quot; was queried before being initialized&quot; )
   set (value) {
      _viewState = value
      _viewStates .value = value
}</pre>
  <p>Самый важный момент здесь set(value), потому что, внезапно, я обнаружил, что StateFlow не обрабатывает одинаковые значения справедливо считая их одинаковыми. Однако, нам надо в случае с ViewAction часто достаточно обрабатывать одинаковые значения и я, пока выбрал следующее решение (которое мне не очень нравится), но оно работает</p>
  <pre>set (value) {
    if (_viewState == value) {
        _viewState = null
        _viewStates .value = null
}
_viewState = value
_viewStates .value = value</pre>
  <p>То есть мы, фактически, проверяем, что значение одинаковое и просто делаем switch на null и обратно на наше значение. Если вы можете предложить решение лучше - пишите в комментариях.</p>
  <p><strong>Второй момент, </strong>как хорошо заметили в комментариях, сама работа со стейтами и экшнами внутри фрагмента тоже не очень изящно написана. В ходе проб и ошибок я в итоге остановился на следующем использовании.</p>
  <p><strong>Ниже код для использования</strong></p>
  <p><code><em>lifecycleScope</em> .launchWhenStarted {</code></p>
  <p><code>   reportsViewModel .viewStates()<br />   .filterNotNull()<br />   .collect <strong>{ </strong>obtainViewState(<strong>it</strong> ) <strong>}</strong><br /><strong>}</strong></code></p>
  <p><code><em>lifecycleScope</em> .launchWhenStarted <strong>{</strong><br />     reportsViewModel .viewActions()<br />     .<em>filterNotNull</em> ()<br />     .collect <strong>{ </strong>obtainAction(<strong>it</strong> ) <strong>}</strong><br /><strong>}</strong></code></p>
  <figure class="m_column">
    <img src="https://teletype.in/files/18/99/1899d912-0f2e-463e-aebf-ef06301aae56.png" width="1200" />
    <figcaption>Для удобства восприятия</figcaption>
  </figure>
  <p>Здесь важный момент, что я запускаю обработку viewState и action в разных Job и происходит это потому, что Flow дожидается обработки collect и поэтому если поместить внутрь обработку action, то просто ничего не будет происходить.</p>
  <p>Я видел совет использовать<strong> launchIn(scope)</strong>, и это помогает работе всего этого внутри одной джобы, но как говорит нам при этом сайт dev Android</p>
  <figure class="m_column">
    <img src="https://teletype.in/files/23/03/2303fa1b-b729-4066-a6df-e75e52d03a96.png" width="1738" />
    <figcaption>Никогда не используйте launch или launchIn как collect, если вы собираетесь как-то апдейтить UI, так как они работают даже, когда view не виден и это может привести к крашам</figcaption>
  </figure>
  <p><strong>Третий момент,</strong> который вызвал больше всего обсуждений - это launch { }, launchWhenStarted { }. С одной стороны люди в комментариях правы, утверждая что в описании к функции написано следующее</p>
  <figure class="m_column">
    <img src="https://avatars.mds.yandex.net/get-zen_doc/3985984/pub_601b8cf89117d423edf55664_601ba2d49117d423ed19c528/scale_2400" width="1542" />
    <figcaption>launchWhenStarted Documentation</figcaption>
  </figure>
  <p>Однако, в описании вызова launchWhenStarted сказано следующее</p>
  <figure class="m_column">
    <img src="https://avatars.mds.yandex.net/get-zen_doc/1703756/pub_601b8cf89117d423edf55664_601ba328571f7906b26df83e/scale_1200" width="1198" />
    <figcaption>https://developer.android.com/kotlin/flow/stateflow-and-sharedflow</figcaption>
  </figure>
  <p>То есть как мы видим функция будет переведена в suspend state как только сработает элемент жизненного цикла onStop. Это вызывает некую путаницу, потому что если Job создаваемый launchWhenStarted, WhenCreated и так далее уничтожается вместе с lifecycleScope, то в чем их разница? А разница как раз в этом suspen.</p>
  <p>Вот что происходит на старте Fragment</p>
  <figure class="m_column">
    <img src="https://avatars.mds.yandex.net/get-zen_doc/1907878/pub_601b8cf89117d423edf55664_601bb2e3ce93531089ee2187/scale_1200" width="920" />
    <figcaption>onViewCreated lifecycle</figcaption>
  </figure>
  <p>Дальше при уходе с фрагмента в <strong>onStopped</strong> наша Job все еще живет и даже активна, а наш <strong>launchWhen</strong> уходит в <strong>suspend</strong> в зависимости от жизненного цикла выбранного нами (Started, Created и так далее).</p>
  <figure class="m_column">
    <img src="https://avatars.mds.yandex.net/get-zen_doc/1709006/pub_601b8cf89117d423edf55664_601bb2b8571f7906b28dd103/scale_1200" width="880" />
    <figcaption>onStop lifecycl</figcaption>
  </figure>
  <p>Однако при уничтожении экрана мы видим</p>
  <figure class="m_column">
    <img src="https://avatars.mds.yandex.net/get-zen_doc/2749135/pub_601b8cf89117d423edf55664_601bb29f9117d423ed3a1d8e/scale_1200" width="1004" />
    <figcaption>onDestroy lifecycle</figcaption>
  </figure>
  <p>То есть вместе с уничтожением экрана, мы отменяем и lifecycleScope как и написано в документации к функции. Поэтому в целом вся прослушка новых стейтов и действий отменяется именно в привязке к уничтожению экрана.</p>
  <p>Ну и <strong>четвертое </strong>- это небольшое улучшение самого BaseFlowViewModel, предложенное Александром Нозиком (https://t.me/noraltavir)</p>
  <p>Ссылка на обновленный BaseFlowViewModel со всеми изменениями</p>
  <p><a href="https://gist.github.com/AlexGladkov/0e5bc2a888e63038a82e88b430e50b8d#file-baseflowviewmodel-kt" target="_blank">https://gist.github.com/AlexGladkov/0e5bc2a888e63038a82e88b430e50b8d#file-baseflowviewmodel-kt</a></p>
  <p>Всем огромное спасибо и увидимся в следующих выпусках!</p>
  <p>Я в <strong>Youtube</strong> - https://youtube.com/c/MobileDeveloper</p>
  <p>Я в <strong>Telegram</strong> - https://t.me/mobiledevnews</p>
  <p>Я в <strong>Instagram</strong> - <a href="https://instagram.com/nplau" target="_blank">https://instagram.com/nplau</a></p>

]]></content:encoded></item></channel></rss>