5
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Kotlin Flow 勉強まとめ

Last updated at Posted at 2022-02-05

#目次

  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を、以下のように定義します。

interface GithubApi {

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

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

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の説明に移ります。

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の呼び出しを実装してみました。ざっくりとしか理解ができていない部分があるので、深堀をやっていきます。

5
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?