Kotlin Flow 勉強まとめ

#目次

1. Kotlin Flow とは
2. サンプルコード
3. まとめ

##1. Kotlin Flow とは

**suspend fun**は、返す値が1つのみで、**Flow**は、複数の値を順に出力することができることが、大きな違いである。リアルタイムで、更新処理を行いたい場合に有効である。(UI の状態が変化したときに、変更を受け取る等)
また、**Flow**は、**suspend fun**を使うことで、メインスレッドを妨げることなく、ネットワークリクエストを行える。

**Flow**は、大きく分けて3つの役割を持っています。

・**プロデューサ**‥非同期に出力する値の作成を行う
・**インターミディアリ**‥出力されたデータに変更を加えることができる
・**コンシューマ**‥得られた出力値を利用する

``Repository``,``ViewModel``に当てはめると、出力値の作成を``Repository``で行い、使いやすい状態に変換する(mapで、リストの中身を置き換えるなど)、次に、得られた出力値を適切なスコープで呼び出し、UInへと反映させる役割を、``ViewModel``が持つ。という風に、解釈しています。

それでは、サンプルコードを説明します。

##2. サンプルコード

サンプルに使用するコードは、GithubのRepositoryを検索クエリを用いて、一覧を表示するというものになっています。

使用したいAPIを、以下のように定義します。

```kotlin
interface GithubApi {

    @GET("repositories?")
    suspend fun searchGithubRepository(@Query("q") query: String): ApiResult
}
```

次に、プロデューサ、インターミディアリの役割を持った``Repository``層からです。

```kotlin
class SearchRepository {

    fun searchGithubRepository(query: String): Flow<List<DetailItem>> = flow<List<DetailItem>> {

        // List<DetailItem>>形式に変換
        val detailItemList = RetrofitInstance.githubApi.searchGithubRepository(query).items.map{ it.toDetailItem() }
        emit(detailItemList)

        Log.d("API Call", "called API")

    }.flowOn(Dispatchers.IO)
}
```

ここで、重要になってくるのが、``flowOn(スレッドを指定する)``、``map``です。

まずは、簡単な``map``の説明から、この``map``を使えることは、Flowを利用する点で非常に便利だなと感じる点の一つです。それでは、説明に移ります。

ここでは、Flowを使って、List< DetailItem >形式に変換したいと考えています。ですが、使用したいAPIのレスポンスをインターフェースから確認すると、ApiResultクラスであることから、このままでは求める形式のオブジェクトを得ることができません。
そこで、Flow(プロデューサ)によって出力された出力値に、mapでDetailItemへ変更を加えることで利用したい、List< DetailItem >へ変換することができました。

適切にオブジェクトを変換し、出力できるFlowの威力が伝わったかと思います。

次に、``flowOn()``です。コンシューマに影響を与えずに、プロデューサの``CoroutineContext``を変更できる中間演算子のことです。``Repository``層の処理を、最終的に``viewModelScope``内で起動する必要があり、これはUIスレッド(メインスレッド)であり、このスレッドでオペレショーンを行うとクラッシュ、フリーズなどの原因になります。そこで、``viewModelScope``内で呼び出すが、実際は``IOスレッド``で呼び出されているという風に、``flowOn()``を用いて行うことができます。つまり、``collect``を行う、コルーチンのスコープでプロデューサが実行されているようにみえるが、実際は、別の``CoroutineContext``を利用していることでスレッドの問題が発生しないことがわかる。

最後に、出力された結果を収集するコンシューマである``ViewModel``の説明に移ります。

```kotlin
class SearchViewModel(
    private val searchRepository: SearchRepository
) : ViewModel() {

    private val _searchResult: MutableStateFlow<Result> = MutableStateFlow(Result.Idle)
    val searchResult: StateFlow<Result> = _searchResult


    // Avoid using GlobalScope
    // Flow の例外キャッチを、catch を用いて行う
    fun searchGithubRepository(query: String) = viewModelScope.launch {

        _searchResult.value = Result.Loading

        searchRepository.searchGithubRepository(query)
            .catch { e ->
                _searchResult.value = Result.Error(e.toString())
            }.collect {
                    data ->
                _searchResult.value = Result.Success(data)
            }

    }
}
```

``viewModelScope(Dispatchers.Main)``UIスレッドで、コルーチンを起動し、collectしていることがわかります。

ここで、注目する点として例外のキャッチ方法、収集方法です。

まず、キャッチ方法から、中間演算子``catch``を呼び出すことで、処理中の例外をキャッチすることができます。ここでは、``sealed class``を用いて、例外が発生したときにその内容をキャッチし、状態を保持するような処理を記述しています。

そして、``collect``です。これを、viewModelScopeで呼び出すことで、UIスレッドの変更を読み取ることができ、``view``の更新を行うことができます。

## 3. まとめ

Flowを用いてAPIの呼び出しを実装してみました。ざっくりとしか理解ができていない部分があるので、深堀をやっていきます。