Kotlin Flow をどこで生成するべきか

Data SourceでFlowを生成し、Repositoryで加工・統合し、ViewModelでUI Stateに変換する。

 

🧑🏻‍💻 データソースに応じて Flow の扱いを決める

データソースによって、データの取得方法は異なります。

Room / DataStore :
・ すでに Flow を提供しているため、新たに Flow でラップする必要はありません。
・ データの変更を監視する仕組みもデータソース側に備わっています。

Callback / Listener :
・ callbackFlow を使って Flow に変換します。
・ awaitClose で購読終了時に Listener を解除します。
・ Callback の登録・解除を管理するデータソースの近くで変換するのが適切です。

suspend 関数 :
・ 一度だけ結果を取得する処理なら、無理に Flow へ変換する必要はありません。
・ Flow に変換すると、購読するたびに API が実行されます。
・ 複数の suspend 処理をまとめてストリームとして扱いたい場合などに Flow を利用します。

 

🧑🏻‍💻 Flow への変換は Data Source で行う

Callback API を Flow に変換する場合、Repository ではなく Data Source で行います。

推奨する構成


Data Source
    |
    | Callback
    v
callbackFlow
    |
    v
  Flow
    |
    v
Repository

・ 基盤 API とのやり取りを担当する Data Source に属する責務です。
・ Data Source は API との接続や Listener のライフサイクルを管理します。
・ Repository は Flow を受け取り、必要に応じて加工・統合します。

 

🧑🏻‍💻 Repository では Flow を加工・統合する

Data Source から Flow を受け取った Repository では、データをアプリケーションで扱いやすい形に変換します。

主な処理は次の3つです。

・ ここで重要なのは、Repository は新しいデータソースを作る場所ではなく、既存のデータストリームをアプリケーションに適した形へ変換する場所ということです。

 

🧑🏻‍💻 ViewModel では FlowをStateFlow に変換する

Repository から受け取った Flow は、ViewModel で UI State に変換します。

ここで利用するのが stateIn() です。


val uiState: StateFlow<UiState> = combine(
    repository.items,
    query,
    repository.showAds
) { items, query, showAds ->
    UiState(
        items = items.filter { it.matches(query) },
        showAds = showAds
    )
}.stateIn(
    viewModelScope,
    SharingStarted.WhileSubscribed(5_000),
    UiState()
)

・ combine によって、Repository のデータやユーザー入力に変更があれば、新しい UI State が導出されます。
・ UI State を手動で更新する必要はありません。
・ stateIn() によって、現在の状態を保持する StateFlow に変換します。

ここでの Flow 変換は、Data Source で行う Flow 変換とは目的が異なります。

 

🧑🏻‍💻 全体のアーキテクチャ


       Data Sources
             |
 +-----------+-----------+
 |           |           |
Room      Callback   suspend API
 |           |           |
Flow    callbackFlow   suspend
 |           |           |
 +-----------+-----------+
             |
             v
        Repository
             |
      transform / combine
             |
           Flow<T>
             |
             v
         ViewModel
             |
        map / combine
             |
          stateIn()
             |
       StateFlow<UiState>
             |
             v
            UI
             |
 collectAsStateWithLifecycle()

・ suspend API は必ずしも Flow に変換する必要はありません。
・ 必要に応じて Repository や ViewModel から suspend 関数を呼び出せます。
・ この図は監視対象となるデータの一般的な流れであり、すべての処理に Flow を要求するものではありません。

 

🧑🏻‍💻 まとめ

最も重要なのは、Flow をどこでも生成できるからといって、どこでも生成すべきではないということです。


Data Source  : Flow を生成する
Repository   : Flow を加工・統合する
ViewModel    : StateFlow として UI State を導出する
UI           : StateFlow を購読する

Convert at the source. Transform in the Repository. Derive UI state in the ViewModel.

つまり、Flow を導入するタイミングは、データの取得方法と各レイヤーの責務によって決めるべきです。


Related Categories :  Android・Developmemt・JetpackCompose・KMP・Kotlin・Kotlin Multiplatform Mobile・Newbie・Recommended