#目次
- Kotlin Flow とは
- サンプルコード
- まとめ
##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の呼び出しを実装してみました。ざっくりとしか理解ができていない部分があるので、深堀をやっていきます。