• トップ
  • ブログ一覧
  • 過去のストリームから見るViewModelの未来:StateFlowへの移行は本当に必要か?
  • 過去のストリームから見るViewModelの未来:StateFlowへの移行は本当に必要か?

    笹川(エンジニア)笹川(エンジニア)
    2025.10.20

    IT技術

    StateFlowを積極的に採用すべき?

    (株)ライトコードでモバイルアプリケーションメインで色々開発している笹川(ささがわ)です!

    Jetpack Composeが主流となりつつある現代のAndroid開発において、ViewModelでの状態管理も進化しています。

    LiveDataとStateFlow、どちらも強力なツールですが、なぜ私たちはStateFlowを積極的に採用すべきなのでしょうか?

    今回はその理由を掘り下げて解説していきます。

    1. 過去のストリームライブラリから見る技術の変遷

    LiveDataが生まれる以前、Android開発では非同期処理やイベント通知のために様々なライブラリが使われていました。

    RxJava / RxAndroid(Rx)

    これはリアクティブプログラミングの概念をAndroidにもたらした、非常に強力なライブラリです。

    RxJavaは、イベントストリームを扱う「コールドストリーム」と「ホットストリーム」の両方をサポートしていました。

    • コールドストリームは、Flowのように購読者(Subscriber)が現れるまで処理を開始しないもので、データベースからのデータ取得やAPIコールなど、毎回新しい処理を行うのに適していました。
    • ホットストリームは、購読者の有無に関わらず常にイベントを発行し続けるもので、RxJavaではPublishSubjectやBehaviorSubjectといったクラスがそれに該当します。これはUIイベントや状態管理に適していましたが、扱いがやや複雑でした。

    EventBus

    これはPublish/Subscribeモデルを採用したイベントバスライブラリです。

    特定のイベントを購読しているすべてのコンポーネントにイベントをブロードキャストする、ホットストリームの一種と見なせます。

    コードがシンプルになる反面、イベントの発行元と購読元が疎結合になりすぎるため、デバッグが難しくなるという課題もありました。

    これらのライブラリは強力でしたが、RxJavaは学習コストが高く、EventBusはデバッグの難しさが課題でした。

    Googleはこれらの反省点を踏まえ、よりシンプルでAndroidのライフサイクルに最適化されたLiveDataを導入しました。

    LiveDataは、RxJavaのホットストリーム的な役割を、ライフサイクルと連携させることで、より安全に利用できるようにしたものです。

    そして、coroutineがKotlinの標準機能として登場したことで、FlowとStateFlowが生まれました。

    これは、RxJavaが提供していた機能(コールドストリームとホットストリーム)を、より簡潔で直感的なcoroutineベースで実現するものと言えます。

    2. coroutineとのネイティブな親和性

    LiveDataはAndroidの初期から存在するため、coroutineが普及する以前の設計思想に基づいています。

    そのため、coroutineと組み合わせて使う際には、asLiveData()などのアダプター関数を介する必要がありました。

    一方、StateFlowはcoroutineとシームレスに連携できるように設計されています。

    Flowをベースにしたホットストリームであるため、viewModelScope.launchやFlowの演算子(map、filterなど)を自然に使うことができます。

    これにより、ViewModel内の非同期処理が非常に簡潔かつ安全に記述でき、コードの可読性が大幅に向上します。

    3. ホットストリームとしての優位性

    LiveDataは「コールドストリーム」のように振る舞います。

    UIがデータを購読している間だけデータの更新が通知されるため、これはライフサイクルに配慮した設計です。

    しかし、データが流れない間は最新の状態が保持されないというデメリットがあります。

    対してStateFlowは「ホットストリーム」です。

    常に最新の状態を保持し、変更があったときにだけ通知します。

    この特性は、Composeの宣言的なUIと非常に相性が良いです。

    ComposeのUIは、StateFlowが持つ最新の状態(value)に基づいて再構築されるため、UIとデータの一貫性を簡単に保つことができます。

    4. 値の取得と更新がシンプルに

    LiveDataでは、値を更新する際にUIスレッド以外からはpostValue()を、UIスレッドからはvalueプロパティを使わなければならないという制約がありました。

    これはスレッドの安全性を保つためのものですが、コードが煩雑になる原因でもありました。

    StateFlowでは、valueプロパティをスレッドに関係なく直接読み書きできます。

    これにより、ViewModel内で状態を更新するロジックがシンプルになり、_uiState.value = newStateのような直感的なコードで状態管理が完結します。

    5. 単一のデータフロー管理

    LiveDataは複数のデータソースを統合する際に、MediatorLiveDataを使う必要があり、複雑になりがちでした。

    StateFlowでは、combineやzipなどの強力な演算子を使い、複数のStateFlowやFlowを簡単に一つにまとめることができます。

    これにより、ViewModel内の複数のデータフローを一元管理し、ロジックをシンプルに保つことが可能です。

    実装例:LiveDataとStateFlowの比較

    次に、同じような処理をLiveDataとStateFlowそれぞれで実装した場合のコードを見ていきましょう。

    ここでは、ボタンが押されるとカウンターを増やすシンプルなViewModelを想定します。

    LiveDataを使った実装

    1class LiveDataCounterViewModel : ViewModel() {
    2    private val _count = MutableLiveData(0)
    3    val count: LiveData = _count
    4
    5    fun incrementCount() {
    6        _count.value = (_count.value ?: 0) + 1
    7    }
    8}

    このViewModelをComposeで使う際は、observeAsStateを利用します。

    1@Composable
    2fun LiveDataCounterScreen(viewModel: LiveDataCounterViewModel) {
    3    val count by viewModel.count.observeAsState(0)
    4
    5    Column(Modifier.fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center) {
    6        Text(text = "Count: $count", fontSize = 24.sp)
    7        Spacer(modifier = Modifier.height(16.dp))
    8        Button(onClick = { viewModel.incrementCount() }) {
    9            Text("Increment")
    10        }
    11    }
    12}

    StateFlowを使った実装

    1class StateFlowCounterViewModel : ViewModel() {
    2    private val _count = MutableStateFlow(0)
    3    val count: StateFlow = _count.asStateFlow()
    4
    5    fun incrementCount() {
    6        _count.value = _count.value + 1
    7    }
    8}

    StateFlowをComposeで使う際は、collectAsStateを利用します。

    1@Composable
    2fun StateFlowCounterScreen(viewModel: StateFlowCounterViewModel) {
    3    val count by viewModel.count.collectAsState()
    4
    5    Column(Modifier.fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center) {
    6        Text(text = "Count: $count", fontSize = 24.sp)
    7        Spacer(modifier = Modifier.height(16.dp))
    8        Button(onClick = { viewModel.incrementCount() }) {
    9            Text("Increment")
    10        }
    11    }
    12}

    上記の例では、どちらもほとんど同じように見えます。

    しかし、coroutineとの連携が必要な複雑な非同期処理の場合、StateFlowの方がより簡潔に記述できます。

    まとめ:StateFlowはCompose時代のスタンダード

    LiveDataはAndroid開発を長く支えてくれた、本当に素晴らしいライブラリです。

    今でも既存のプロジェクトでは現役で活躍しています。

    しかし、Jetpack Composeとcoroutineが中心となった今、より効率的でモダンな状態管理を目指すなら、StateFlowはもはや避けては通れない道だと思います。

    coroutineとの親和性、ホットストリームとしての特性、そして簡潔な状態管理は、開発者の私たちにとって大きなメリットをもたらしてくれます。

    新しい技術を学ぶのは大変ですが、その先にはきっと、もっと楽しく、もっと安定したアプリ開発が待っているはずです。

    皆さん、これからも一緒に頑張っていきましょう!

    ライトコードでは、エンジニアを積極採用中!

    ライトコードでは、エンジニアを積極採用しています!社長と一杯しながらお話しする機会もご用意しております。そのほかカジュアル面談等もございますので、くわしくは採用情報をご確認ください。

    採用情報へ

    笹川(エンジニア)
    笹川(エンジニア)
    Show more...

    おすすめ記事

    エンジニア大募集中!

    ライトコードでは、エンジニアを積極採用中です。

    特に、WEBエンジニアとモバイルエンジニアは是非ご応募お待ちしております!

    また、フリーランスエンジニア様も大募集中です。

    background