【Kotlin】SharedFlow、StateFlowを使ってアプリを作る【Coroutine】


この記事は[Kotlin Advent Calendar 2020](https://qiita.com/advent-calendar/2020/kotlin) の24日目の記事です。

## 背景

以前に[Coroutines Flowを使ってRxを卒業する記事](https://qiita.com/alpha2048/items/da51ef344a424a897114)を書き、Coroutine Flowのサンプルを書きましたが、その後、Flow界隈に新星が生まれました。

- **StateFlow**
  - subscribeで直近の値を受信する (複数回連続で値が来た場合は最新のみ受信)
  - 初期値が必要
  - Coroutinesの文脈で使うLiveData
  - RxでいうところのBehaviorSubject
- **SharedFlow**
  - subscribeで値を受信する (複数回連続で来てもすべて受信)
  - 以前にあった[BroadcastChannel](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-broadcast-channel/)とかその辺の後継
  - RxでいうところのPublishSubject

これらを使えば、RxどころかもはやLiveDataをほとんど使わずとも、Flowの世界だけでアプリを作ることができそうですよね？
というわけで、難しいことは考えずに早速基本的なアプリを作ってみたいと思います。

## Sample

GithubのRepositoryを検索するアプリです。以前の記事と処理はほぼ同じ。

[https://github.com/alpha2048/CoroutineStateSharedFlowTest](https://github.com/alpha2048/CoroutineStateSharedFlowTest)

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99605/36db93f4-df95-4a67-a5a6-2281427a5586.png" width="200">

## 環境
- Coroutine v1.4.2
- (その他Retrofit、moshi、Koinの最新版)

## 解説

### StateFlowでデータ管理する

実質`LiveData`である`StateFlow`を使って、APIから取得したデータを管理します。
取得したデータを`MutableStateFlow`に突っ込んでいきます。
UI側は公開された`StateFlow`をsubscribeして、取得したデータを表示します。初期値を見てプログレスバーの出し分けも行います。

`StateFlow`に変換して公開する部分など、`LiveData`の実装にかなり近いです。

#### ViewModel

```kotlin
class MainViewModel(
    private val usecase: GetRepositoryUsecase
) : ViewModel() {

    private val _repoItems: MutableStateFlow<List<RepoItemEntity>> = MutableStateFlow(emptyList())
    val repoItems: StateFlow<List<RepoItemEntity>> = _repoItems

    fun loadData() {
        viewModelScope.launch {
            usecase.execute("Coroutine", 1).collect {
                val list = _repoItems.value
                _repoItems.value = list + it.items
            }
        }
    }
}
```

#### View側

```kotlin
        lifecycleScope.launchWhenStarted {
            viewModel.repoItems
                .collect {
                    if (it.isNotEmpty()) {
                        binding.progress.visibility = View.INVISIBLE
                        adapter.setItem(it)
                    } else {
                        binding.progress.visibility = View.VISIBLE
                    }

                }
        }
```

### `SharedFlow`でイベントを送ってみる

`SharedFlow`でイベント送信してみます。
(少し無理矢理な使い方ですが..😓)
`OnClickListener`で、`MutableSharedFlow`にItemを渡し、View側でsubscribeします。Listenerの代わりですね。

#### Adapter側

```kotlin
        holder.binding.clickView.setOnClickListener {
            scope.launch {
                _onClick.emit(repoItem)
            }
        }
```

#### View側

```kotlin
        lifecycleScope.launchWhenStarted {
            adapter.onClick
                .collect {
                    val uri = Uri.parse(it.htmlUrl)
                    startActivity(Intent(Intent.ACTION_VIEW, uri))
                }
        }
```

## おわりに
BroadcastChannelがobsoleteになり、イベント通知などこれからどうしよう・・？となっていましたが、新しく登場した2種の使い勝手がいい感じなのでとても安心しました。

Flowはまだまだ発展途上ではありますが、ここまでくればProductionにFlowを採用するメリットもありそうだなと感じます。

みなさまもお試しいただき、もっとこういう使い方があるよ、というのがあれば是非教えてください🙏

## 参考文献
- [StateFlow と SharedFlow (公式)](https://developer.android.com/kotlin/flow/stateflow-and-sharedflow)
- [SharedFlow](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-shared-flow/index.html)
- [StateFlow](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-state-flow/index.html)
- [SharedFlow、StateFlow、Channelなどを比較して、LiveDataやEvent通知の置き換えを考える](https://qiita.com/takahirom/items/e9294bfc4f3b1bbee696)
- [【Kotlin】SharedFlowとStateFlowを読み解く](https://qiita.com/tonionagauzzi/items/43dcb01f9f120e14cc23)
- [kotlin coroutinesのFlow, SharedFlow, StateFlowを整理する](https://at-sushi.work/blog/24)
- [kotlin coroutinesでViewModelからViewにイベント通知したい](https://at-sushi.work/blog/28)