Kotlin Coroutines Flow とは。

# この記事は
- Kotlin CoroutinesのFlowが何かよくわからないので**基本を知りたい**人向け
    - この記事1つでFlowの基本を網羅できればという強い気持ちによって少々長くなってしまったので流し読みでも:ok_woman: :ok_hand: 
- 私自身も勉強中で、この記事は百戦錬磨の実践から学んだものというよりかは、コツコツ座学的に勉強したことを自分用にまとめたものです

# Android開発におけるFlowを使うメリット
- Coroutinesの中には非同期処理を実行するのにワンショット系のデータを処理する場合とワンショットではない系（ストリーム系）のデータを処理する場合の大きく分けて二つあります
    - ワンショット系の場合はCoroutine scopeの中で`suspend fun`を実行します
    - ストリームタイプのデータの場合は**Flow**を使うと良いです
        - 例えば、最近Twitterでとても人気のあるツイートを見ている時、画面をリロード/pull-to-refreshをしなくても自動的にいいね数やリツイート数が更新され続けませんか？こういった連続的にデータが変化し、それをUIに反映させたい場合はFlowを用いるのが良さそうです
    - 参考：[Android での Kotlin Flow](https://developer.android.com/kotlin/flow?hl=ja)
- RoomのDAOはFlowを返すクエリメソッドを定義できます
    - Android開発ではRoomを使うシーンも多いのでFlowを覚えておくとRoomも使いやすくなるかもしれません
    ```kotlin
    @Query("SELECT * FROM EventDto WHERE isFavorite = 1")
    fun getFavoriteEvents(): Flow<List<EventDto>>
    ```
- AndroidXとして提供されているライブラリのインターフェースで、ある値を受け取る方法がFlowというケースもあります
    - [DataStore](https://developer.android.com/topic/libraries/architecture/datastore?hl=ja)
    - [WindowManager](https://developer.android.com/reference/androidx/window/layout/WindowInfoTracker#windowLayoutInfo(android.app.Activity))

- RepositoryでFlowを公開すると、どこか一ヶ所からFlowの中身を更新すれば、どこからでも最新
の値を取得できるようになります
    - 場合によっては[いいね問題](https://at-sushi.work/blog/7/)の解決にも繋がります
- （使いこなせれば）かなりの行数の処理でもFlowでは数行でかけたりします
    - ⚠️ ただ、若干わかりにくい書き方になってしまうこともあります

# Flowのイメージ
具体的なFlowの使い方を学習する前に、図解でFlowのイメージを掴んでいきましょう。
kotlin.coroutines.flowには`collect`というメソッドが生えていて、このメソッドを使って**flowを収集・実行します**。
```kotlin
fun soumen(): Flow<String> = flowOf("1", "2", "3")

fun main() {
    CoroutineScope(Dispatchers.IO).launch() {
    	soumen().collect { soumen ->
        	print("soumen:$soumen ")
    	}
    }
}
```
`collect`を実行するのに`launch`しているのは、[collectがsuspend関数](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/collect.html)だからです。

▷ [サンプルコードを実行したい場合はこちらから](https://pl.kotl.in/VG82RvCCK)

このサンプルコードを元にしたFlowのイメージが次の図です。
![スクリーンショット 2023-02-05 163446.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/906559/c2d892e6-18e0-7f11-8040-d2c9e29c1465.jpeg)
Flowは、何となく**流しそうめん**の感覚と似ています。

流しそうめんの竹の中をそうめん(データ)が流れていて、最後のお椀(collectのラムダの中)に流しそうめんが流れると、処理が実行されます。

`collect`する前は流しそうめんの竹の出口に蓋がされていて、そうめん（データ）が流れなくなっています。`collect`を開始すると蓋が取れて、竹筒の中をそうめん（データ）が流れ出します。これがFlowのイメージです。Flowという単語が「流れ」を意味するので、納得感があると思います。

蓋がとれることでお椀の中にそうめん(データ)が**収集**されて、お椀の中(collectのラムダの中)の処理が**実行**されます。[サンプルコード](https://pl.kotl.in/VG82RvCCK)でいうところの`print("soumen:$soumen ")`がラムダの中の処理です。これが`collect`のイメージです。

我々エンジニアがFlowを扱う姿、それはさながら流しそうめん職人です。

前述の例ではシンプルなコードを扱いましたが、もう少し複雑な処理をFlowに対して実行する場合、
分岐する竹を組んだり（hot flow）加熱してにゅうめんにしたり(map)そうめんを取り上げてうどんを流したり（flatMapLatest）します。

🚨hot flowの概念に関しては後ほど説明します🚨
    
# よく使うFlowの書き方
ニュースアプリを例に一般的なFlowの使い方を見ていきましょう。

## Flowからの収集
前述の章での説明にもあった通りFlowの収集は`collect`を使いますが、様々な書き方があるので紹介していきます。
参考：[Android での Kotlin Flow：Flow からの収集](https://developer.android.com/kotlin/flow?hl=ja#collect)

### launchを使う場合
```kotlin
class NewsRepository {
    val news: Flow<List<Article>> = ...
}

class NewsViewModel(
    private val newsRepository: NewsRepository,
) {
    init {
        viewModelScope.launch {
            newsRepository.news.collect { news ->
                Log.d("collected news: $news")
            }
        }
    }
}
```
▷ [単純にlaunchとcollectの挙動を確かめたい場合はこちらから](https://pl.kotl.in/guHOhO8I5)

### launchInを使う場合
`launchIn`は`launch`と同じ処理をより簡潔に書くことが可能です。
`launch`では`scope.launch {flow.collect()}`と書いていましたが、これを省略して`launchIn`だけでかけます。つまり`launchIn`には`launch{}`と`collect{}`の両方が含まれているということです。
参考：[kotlinx.coroutines.launchIn](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/launch-in.html)

実際に上記の`launch`で書いたコードを`launchIn`で書き換えてみましょう。

```kotlin
class NewsRepository {
    val news: Flow<List<Article>> = ....
}

class NewsViewModel(
    private val newsRepository: NewsRepository,
) {
    init {
        newsRepository.news.onEach { news ->
            Log.d("collected news: $news")
        }.launchIn(viewModelScope)
    }
}
```
`launch`の場合のサンプルコードと比べるとネストが一段浅くなっていますね。

また、`launch`の場合のサンプルコードと比較して、`onEach`という新しい**オペレーター**が登場しました。
これは、竹筒を流れている途中のそうめん（データ）を何もせずただ一つ一つ見てくれるようなイメージです。

なぜ今回これが必要になったのか簡単に説明します。
`launchIn`に`launch`と`collect`が含まれた結果、`collect{}`に付いていた流れてくる値を見るラムダがなくなってしまったので、代わりに`onEach`を使って流れてくる値をみましょうということです。

▷ [単純にlaunchInとonEachの挙動を確かめたい場合はこちらから](https://pl.kotl.in/NqbnqEGYq)

### ActivityやFragmentで使用する際の注意
このようにFlowからの収集では`launch`や、さらに便利な`launchIn`を使用しますが、ActivityやFragmentで使用する際には注意してください。そのActivity/Fragmentが生きているまでの間ずっと収集してしまうので、無駄にリソースを使ってしまう可能性があります。例えば、Activityが裏に回ってる時などもデータを収集してしまいますが、一般的にそのような場合はデータを収集する必要はありません。このような場合は[repeatOnLifecycle](https://developer.android.com/topic/libraries/architecture/coroutines?hl=ja#restart)を使って回避することができます。`ViewModelScope`の時は、そのような配慮は必要ありません。


## Flowの収集：よくある間違い
よくある間違いの中でも、あるViewModelの中で二種類のFlowを収集したい場合を取り上げて解説します。

### NG例
まずはNG例のサンプルコードです。

```kotlin
class NewsRepository {
    val animeNews: Flow<List<Article>> = ....
    val techNews: Flow<List<Article>> = ....
}

class ViewModel(
    private val newsRepository: NewsRepository,
) {
    init {
        viewModelScope.launch {
            newsRepository.animeNews.collect { news ->
                Log.d("collected news: $news")
            }
       
            newsRepository.techNews.collect { news ->
                Log.d("collected news: $news")
            }
        }
    }
}
```

1つの`launch`の中で２つFlowの処理を書いています。
この場合だと、`animeNews`のFlowの処理が終わらない限り(Flowをcancelしない限り)、`techNews`の`collect`は始まりません。

### NG例の修正
この問題は、Flowごとに`launch`を分けることで解決できます。

```kotlin
class NewsRepository {
    val animeNews: Flow<List<Article>> = ....
    val techNews: Flow<List<Article>> = ....
}

class ViewModel(
    private val newsRepository: NewsRepository,
) {
    init {
        viewModelScope.launch {
            newsRepository.animeNews.collect { news ->
                Log.d("collected news: $news")
            }
        }

        viewModelScope.launch {
            newsRepository.techNews.collect { news ->
                Log.d("collected news: $news")
            }
        }
    }
}
```

# Flowの作り方
Flowは**Flowビルダー**を使って作成できます。
先ほどからいくつかサンプルコードを見て、「Flowの作り方なんて今更かよ」と思うかもしれませんが、Flowの作り方は多岐に渡ります。
今までのサンプルコードでは`flowOf()`というFlowビルダーを使ってFlowを作成していましたが、`flowOf`以外にもたくさんのFlowビルダーがありますので、それらのうちのいくつかを紹介していきます。

> Flow builders
There are the following basic ways to create a flow:
    - flowOf(...) functions to create a flow from a fixed set of values.
    - asFlow() extension functions on various types to convert them into flows.
    - flow { ... } builder function to construct arbitrary flows from sequential calls to emit function.
    - channelFlow { ... } builder function to construct arbitrary flows from potentially concurrent calls to the send function.
    - MutableStateFlow and MutableSharedFlow define the corresponding constructor functions to create a hot flow that can be directly updated.

参考：[kotlinx-coroutines-core/kotlinx.coroutines.flow/Flow](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-flow/)


## flowOf
- `flowOf(1, 2, 3)`のように、直接値を入れてFlowを作成することができる
    - こういう場合はサンプルコードや、自分でFlowの動きを学びたい時に使うことが多い
- 値を返すsuspend funの結果をFlowとして受け取りたい場合にも使える
    ```kotlin
     // ログインをしている場合はそのユーザーのおすすめコンテンツを表示し、ログインしていない場合は固定の無料コンテンツを表示させるロジック
    val recommendation = userRepository.isLoggedIn.flatMapLatest { inLoggedIn ->
        if(isLoggedIn) {
            userRecommendationRepository.contents // これはFlow
        } else {
            flowOf(
                freeContentRepository.getFreeContents() // こっちはsuspend fun
            )
        }
    }
    ```
    - elseの方はsuspend funですが、`flowOf`によってFlow型に変換する事で`flatMapLatest`の返り値として使用できます

## flow {}
- `flow {}`の中で`emit{}`を呼ぶと値を流せる
    - suspend funも呼べるのでdelayと組み合わせて定期的に値を取得して流すのに便利
    ```kotlin
    // ExoPlayerによるダウンロード処理が行われているかどうかを提供する
    // exoDownloadManager.value.currentDownloadsの変化をコールバックなどで受け取る方法が無いので
    // 1secごとにexoDownloadManager.value.currentDownloadsの数を調べている
    val hasActiveDownload: Flow<Boolean> = flow {
        while (currentCoroutineContext().isActive) { // collectされている間だけ動く
            emit(exoDownloadManager.value.currentDownloads.isNotEmpty())
            delay(1000L)
        }
    }
    ```
    - このサンプルコードではBooleanですが数値にするなどすればプログレスバーの実装にも使えます
    
    ▷ [flow{}とemitで作ったFlowの挙動を確かめたい場合はこちら](https://pl.kotl.in/0mOURYw2C)

## MutableStateFlow/MutableSharedFlow
- RepositoryやViewModelでFlowを提供するときに使われることが多い
- `LiveData`のように値を流すことが可能
- [サンプルコードは公式ドキュメントを参照](https://developer.android.com/kotlin/flow/stateflow-and-sharedflow?hl=ja)

```kotlin
sealed class LatestNewsUiState {
    data class Success(val news: List<ArticleHeadline>): LatestNewsUiState()
    data class Error(val exception: Throwable): LatestNewsUiState()
}

class LatestNewsViewModel(
    private val newsRepository: NewsRepository
) : ViewModel() {

    private val _uiState = MutableStateFlow(LatestNewsUiState.Success(emptyList()))
    val uiState: StateFlow<LatestNewsUiState> = _uiState

    init {
        viewModelScope.launch {
            newsRepository.favoriteLatestNews
                .collect { favoriteNews ->
                    _uiState.value = LatestNewsUiState.Success(favoriteNews)
                }
        }
    }
}
```

### LiveDataとの違い
- `LiveData`はAndroid専用のものですが、`MutableStateFlow/MutableSharedFlow`は汎用的
    - なので、Androidのライフサイクルには気をつけなければならない
    - 逆にLiveDataはobserveする側のライフサイクルに応じて値の監視を止めてくれる
- `LiveData`はnullableだけど`MutableStateFlow`は必ず1つ値を保持している
- 一方`MutableSharedFlow`は値を保持しない
    - ある値が`MutableSharedFlow`にemitされた時、collectしている場所があればそこに値が届く
    - どこもcollectしていない場合はemitしてもその値は虚無に消えてしまう
    - ただし、`MutableSharedFlow(replay: Int = 10)`などとすると値を10個まで保持できる

### MutableStateFlow と MutableSharedFlow の違い
- 初期値が必須かどうか
    - `MutableStateFlow`は必須！
- MutableStateFlowはUIの状態を変更したり保持したりするのに向いている
    - 常に最新のデータを保持できるため
- イベント通知や画面遷移などワンショット系のデータの取り扱いはMutableSharedFlowが向いている
    - ただし、例えばMutableSharedFlowを誰もcollectしていない時にイベント通知などをおこなうと、誰もこれを処理せずにイベント通知が無かったことになってしまう
    - そういう場合はMutableStateFlowを使ってイベント通知を実装し、通知後にnullで値を更新してイベントを消すのが良い
    ```kotlin
    data class LatestNewsUiState(
        val userMessage: String? = null
    )

    class LatestNewsViewModel(/* ... */) : ViewModel() {
        private val _uiState = MutableStateFlow(LatestNewsUiState(isLoading = true))
        val uiState: StateFlow<LatestNewsUiState> = _uiState

        ....

        // TIPS: イベント通知を行うUiStateの値をnullで更新
        fun userMessageShown() {
            _uiState.update { currentUiState ->
                currentUiState.copy(userMessage = null)
            }
        }
    }

    class LatestNewsActivity : AppCompatActivity() {
        private val viewModel: LatestNewsViewModel by viewModels()
    
        override fun onCreate(savedInstanceState: Bundle?) {
            lifecycleScope.launch {
                viewModel.uiState.collect { uiState ->
                    // TIPS: ViewModelのメソッドを呼び出してイベントを消去
                    uiState.userMessage?.let {
                        viewModel.userMessageShown()
                    }
                    ....
                }
            }
        }
    }
    ```
    - 参考：[イベントの消費によって状態の更新をトリガーできる](https://developer.android.com/topic/architecture/ui-layer/events?hl=ja#consuming-trigger-updates)
    

# hot flow と cold flow
Flowの勉強をしているとhot flowとcold flowの話はよく耳にするのではないでしょうか。
これらは**Flowの特性**を表す言葉です。前の章で説明した通りFlowビルダーを使えばFlowが作成できますが、そうやって作成されたFlowは全てhotかcoldのどちらかに分類されます。

## cold flowとは
- `flowOf()` や `flow { }` ビルダーで作ったflowはcold flow
- `map`や`filter`などの**オペレーター**がつくとcold flowになる
- cold flowの特徴
    - 誰かがcollectするまでflowの処理は動かない
    - 複数の人がcollectすると、それぞれの人ごとにflowの処理が動く
<img width=70% src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/906559/46d89eba-e172-33db-efc8-f5f30adace88.jpeg">


## hot flowとは
- `shareIn`や`stateIn`でhot flowに変換出来る
- `MutableStateFlow/MutableSharedFlow`はhot flow
- hot flowの特徴
    - collectがなくても勝手にflowの処理(オペレーターの中の処理)が動く
        - [内部実装を見るとcollectしている](https://cs.android.com/android/platform/superproject/+/master:external/kotlinx.coroutines/kotlinx-coroutines-core/common/src/flow/operators/Share.kt;drc=73b7fd03421a98711176ba4001126fcfea6d3ee0;l=214?hl=ja)のがわかります
        - 内部的にcollectしているため、わざわざcollectしなくてもshareIn/stateInを呼び出した時点で自動的にflowの処理が動きます
    - 複数の人がcollectしても、flowの処理は一人分しか動かない
        - 例えば、Repositoryは複数箇所からのアクセスがあるのが一般的なので、cold flowでそれぞれのcollectごとに処理が走るのはリソース的に無駄なのでhot flowにする方が良い(`Single Source of Truth`的にも良い）

<img width=70% src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/906559/084d70cb-2d04-9656-dbf1-72e808610599.jpeg">

### shareIn/stateInでhot flowに変換する場合の注意点
shareInやstateInでcold flowをhot flowに変換する場合は、`started: SharingStarted`で指定できます。

```kotlin
fun <T> Flow<T>.stateIn(
    scope: CoroutineScope, 
    started: SharingStarted, 
    initialValue: T
): StateFlow<T>
```
参考：[StateIn](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/state-in.html?query=fun%20%3CT%3E%20Flow%3CT%3E.stateIn(scope:%20CoroutineScope,%20started:%20SharingStarted,%20initialValue:%20T):%20StateFlow%3CT%3E)

`SharingStarted`は`Eagerly`, `Lazily`, `WhileSubscribed`の3つの要素を持っています。(※[SharingStartedの内部実装](https://github.com/Kotlin/kotlinx.coroutines/blob/c686eebd996e48fe65c218a474ea80fca8ae3a92/kotlinx-coroutines-core/common/src/flow/SharingStarted.kt#L75))

- Eagerly
    - `shareIn` or `stateIn` が実行された瞬間から、アプリが死ぬまで止ま
らない
- Lazily
    - `shareIn` or `stateIn` で作成したFlowを誰かがcollectした時からア
プリが死ぬまで止まらない
- WhileSubscribed
    - `shareIn` or `stateIn` で作成したFlowを誰かがcollectしている間だけ実行す
る
    - だれもcollectしなくなったら、処理を止める
    - だれもcollectしなくなってから何msec経過したら...と指定することは可能

# まとめ
ここまでFlowとは何かというイメージから実際の活用例、Flowの作り方、hot flow/cold flowと、Flowの基礎に関して全体的に学習していきましたが、いかがでしたでしょうか。筆者も日々の座学でFlowに関しての理解は深まったものの、実務で効果的に使えるかどうかと言われるとまだまだ未熟です。これから経験を積んでいき、より実践的にFlowを扱えるようになり、ゆくゆくは流しそうめん職人になりたいと思います。