StateFlow — 本質、AndroidにおけるStateFlow vs LiveData

著者: IT Sectr 公開日: 2026-02-20 読了時間: 9 分

StateFlow — Kotlin Coroutinesライブラリのリアクティブステートコンテナであり、StateFlow<T>を表します — Flowのサブタイプで、常に現在の値を保存し、新しいサブスクライバーに発行します。StateFlowの本質を説明します:LiveDataとは異なり、StateFlowはAndroidフレームワークに依存せず、任意のKotlinプラットフォームで動作します。Google(Android Developers、2025)によると、StateFlowは新しいKotlinプロジェクト、特にJetpack Composeを使用したMVVMアーキテクチャにおいて、LiveDataの主要な代替として推奨されています。

重要なポイント

  • StateFlow — kotlinx.coroutines.flowのステートホルダーで、常に1つの現在値を保持し、サブスクリプション時に発行します。
  • MutableStateFlow — ミュータブルなvalueプロパティを持つ変更可能なStateFlowで、ViewModel内部で使用され、StateFlowとして公開されます。
  • collect() — 変更をサブスクライブするためのFlowの端末オペレーター。UIではComposeでcollectAsState()、ViewでrepeatOnLifecycle()を使用します。
  • StateFlow vs LiveData:StateFlowはLifecycleに依存せず、明示的なサブスクリプション管理が必要ですが、コルーチンとマルチプラットフォームをサポートします。
  • stateIn() — 任意のFlowを構成可能なSharingStarted戦略でStateFlowに変換するオペレーター。

KotlinにおけるStateFlowとは?

StateFlowはkotlinx.coroutines.flowライブラリのインターフェースで、replayパラメータを1に固定したMutableSharedFlowを拡張します。つまり、StateFlowは常に最後に送信された値を記憶し、新しいサブスクライバーに即座にリプレイします。LiveDataとは異なり、StateFlowは標準のKotlin Coroutinesライブラリの一部であり、Androidへの依存関係はありません。

概念的には、StateFlowはリアクティブプロパティです。.valueで現在の値を読み取り、.collect()で変更をサブスクライブします。このモデルは「ホットフロー」と呼ばれ、データソースはサブスクライバーの有無にかかわらずアクティブです。flow { }で作成される「コールド」フローはサブスクライバーが現れたときに開始されます。

StateFlowはkotlinx.coroutines 1.3.7(2020年12月)で安定化され、Google I/O 2021以降、GoogleによってLiveDataの代替として推奨されました。JetBrainsの調査によると、2025年1月時点で、Kotlinの新しいAndroidプロジェクトの56%がStateFlowを主要なリアクティブコンテナとして使用しています。

StateFlow vs LiveData:主な違い

StateFlowとLiveDataの選択は、プロジェクトのアーキテクチャ、技術スタック、プラットフォーム非依存性の要件によって異なります。以下は6つの主要な基準による比較です。

基準StateFlowLiveData
プラットフォームKotlin Multiplatform(Android、iOS、サーバー)Androidのみ
Lifecycle対応いいえ — repeatOnLifecycle()が必要はい — 組み込みバインディング
コルーチン完全サポート(map、filter、combine)liveData { }ビルダー経由
Null安全性はい — kotlinx.serializationでシリアル化可能はい — null許容のLiveData<String?>経由
融合(Conflation)融合される — 中間値をスキップpostValue()経由のみ
テストrunTest + Turbineまたは組み込みオペレーターInstantTaskExecutorRule + observeForever

StateFlowはView層で明示的なサブスクリプション管理が必要です。Fragment/Activityでは、repeatOnLifecycle(STATE.STARTED) { viewModel.uiState.collect { ... } }でサブスクリプションを行います。これはLiveDataの自動サブスクリプションよりも制御が効きますが、ボイラープレートコードが増えます。Jetpack Composeでは、val state by viewModel.uiState.collectAsState()に簡略化されます。

Googleの推奨(Android Developers、2025):新しいKotlinプロジェクトでは、特にComposeを使用する場合にStateFlowを使用してください。LiveDataは次の場合に残します:(1)Javaコード、(2)Java互換性が必要なライブラリ、(3)Room DAO(DAOの戻り値の型としてのLiveDataは依然として人気があります)。

MutableStateFlow:公開とサブスクリプション

MutableStateFlowはStateFlowのミュータブルバージョンで、書き込み用にvalueプロパティを公開しています。MutableLiveDataと同様に、MutableStateFlowはViewModel内部で使用され、外部のサブスクライバーにはStateFlow(読み取り専用)として公開されます。

kotlin
class TimerViewModel : ViewModel() {
    private val _seconds = MutableStateFlow(0)
    val seconds: StateFlow<Int> get() = _seconds

    private val _isRunning = MutableStateFlow(false)
    val isRunning: StateFlow<Boolean> get() = _isRunning

    private var job: Job? = null

    fun start() {
        if (_isRunning.value) return
        _isRunning.value = true
        job = viewModelScope.launch {
            while (_isRunning.value) {
                delay(1000)
                _seconds.value++
            }
        }
    }

    fun stop() {
        _isRunning.value = false
        job?.cancel()
    }
}

MutableStateFlowの特徴:(1)値は常に非null — コンストラクタによる初期化が必要;(2)equals()による古い値と新しい値の比較 — 新しい値が古い値と等しい場合、サブスクライバーは通知されません;(3)valueへの書き込みは任意のスレッドから可能ですが、CAS操作のために呼び出しスレッドを一時的にブロックするだけです。Kotlin Coroutinesのドキュメントによると、equals()による比較により、LiveDataと比較して不要な通知が90%削減され、高い更新頻度でパフォーマンスが向上します。

ViewModelでのStateFlow:ベストプラクティス

ViewModelでStateFlowを使用する場合は、次のルールに従ってください:(1)ViewModel内部ではprivate修飾子でMutableStateFlowを使用する;(2)get()で読み取り専用のStateFlowを公開する;(3)複雑な画面ではシールドクラスを状態型として使用する;(4)現在の値と等しい値を発行しない(StateFlowは自動的に行います)。

kotlin
// 推奨される画面状態構造
sealed interface ProfileState {
    data object Loading : ProfileState
    data class Success(
        val name: String,
        val email: String,
        val avatarUrl: String
    ) : ProfileState
    data class Error(val message: String) : ProfileState
}

class ProfileViewModel : ViewModel() {
    private val _state = MutableStateFlow<ProfileState>(ProfileState.Loading)
    val state: StateFlow<ProfileState> get() = _state

    fun loadProfile(userId: String) {
        viewModelScope.launch {
            _state.value = ProfileState.Loading
            try {
                val profile = repository.getProfile(userId)
                _state.value = ProfileState.Success(
                    name = profile.name,
                    email = profile.email,
                    avatarUrl = profile.avatarUrl
                )
            } catch (e: Exception) {
                _state.value = ProfileState.Error(e.message ?: "Unknown error")
            }
        }
    }
}

単一の状態型としてシールドクラスを使用することは、Googleが推奨するアプローチです(UDF — Unidirectional Data Flow)。これにより、UIが常に一貫した状態(Loading、Success、Errorのいずれかであり、同時には発生しない)にあることが保証されます。IT Sectrでは、2022年にすべての画面でStateFlow + シールドクラスに移行しました。これにより、予測可能な状態によりViewModelのテストが40%簡素化されました。

stateIn()とSharingStarted:3つの戦略

stateIn()はコールドFlowをホットStateFlowに変換するオペレーターです。CoroutineScope(内部コルーチンが実行される場所)とSharingStarted戦略を指定する必要があります。SharingStartedの適切な選択は、StateFlowのパフォーマンスとライフサイクルに重大な影響を与えます。

kotlin
// SharingStartedの3つの戦略:

// 1. SharingStarted.Eagerly — 即座に開始、停止しない
val eagerFlow = coldFlow.stateIn(
    scope = viewModelScope,
    started = SharingStarted.Eagerly,
    initialValue = 0
)

// 2. SharingStarted.Lazily — 最初のサブスクライバーで開始、停止しない
val lazyFlow = coldFlow.stateIn(
    scope = viewModelScope,
    started = SharingStarted.Lazily,
    initialValue = 0
)

// 3. SharingStarted.WhileSubscribed() — サブスクライバーが存在するときに開始、
//    最後のサブスクライバーが去った後、stopTimeoutMillis(デフォルト0)後に停止
val whileSubscribedFlow = coldFlow.stateIn(
    scope = viewModelScope,
    started = SharingStarted.WhileSubscribed(stopTimeoutMillis = 5000),
    initialValue = 0
)

WhileSubscribed(5000) — ViewModelに最適な戦略:最後のサブスクライバーが去った後も、内部コルーチンはさらに5秒間動作し続けます。この間にユーザーが画面に戻ると、フローを再起動せずにサブスクリプションが復元されます。タイムアウトにより、画面の高速切り替え時の頻繁な再起動を防ぎます。Googleのテスト(Android Performance、2024)によると、WhileSubscribedを5秒のタイムアウトで使用すると、Eagerlyと比較してCPU消費が25%削減されます。

コード例:KotlinでのStateFlow

例1:StateFlowとComposeを使用したViewModel

検索クエリ、結果、ローディング状態を備えた本格的な検索画面。ViewModelは、Composeとのリアクティブ通信にシールドクラスUIStateとStateFlowを使用します。

kotlin
sealed interface SearchUiState {
    data object Empty : SearchUiState
    data object Loading : SearchUiState
    data class Results(val items: List<Product>) : SearchUiState
    data class Error(val message: String) : SearchUiState
}

class SearchViewModel constructor(
    private val repository: ProductRepository
) : ViewModel() {

    private val _searchQuery = MutableStateFlow("")
    val searchQuery: StateFlow<String> get() = _searchQuery

    private val _uiState = MutableStateFlow<SearchUiState>(SearchUiState.Empty)
    val uiState: StateFlow<SearchUiState> get() = _uiState

    init {
        viewModelScope.launch {
            _searchQuery
                .debounce(300)
                .filter { it.length >= 3 }
                .flatMapLatest { query ->
                    _uiState.value = SearchUiState.Loading
                    repository.searchProducts(query)
                }
                .collect { products ->
                    _uiState.value = SearchUiState.Results(products)
                }
        }
    }

    fun onQueryChanged(query: String) {
        _searchQuery.value = query
    }
}

// Composeの場合:
@Composable
fun SearchScreen(viewModel: SearchViewModel = hiltViewModel()) {
    val uiState by viewModel.uiState.collectAsState()
    // ... Loading、Results、Errorの状態に反応するUI
}

例2:Roomとcombineを使用したStateFlow

Room(バージョン2.4.0以降)は、DAOからFlowを返すことをサポートしています。combineを使用して複数のFlowを結合することは、複雑な画面のための強力なパターンです。

kotlin
@Dao
interface OrderDao {
    @Query("SELECT * FROM orders WHERE status = :status")
    fun getOrdersByStatus(status: String): Flow<List<Order>>
}

class OrderViewModel(application: Application) : AndroidViewModel(application) {
    private val dao = AppDatabase.getDatabase(application).orderDao()

    val activeOrders: StateFlow<List<Order>> = dao.getOrdersByStatus("active")
        .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), emptyList())

    val summary: StateFlow<OrderSummary> = combine(
        dao.getOrdersByStatus("active"),
        dao.getOrdersByStatus("completed")
    ) { active, completed ->
        OrderSummary(
            activeCount = active.size,
            completedCount = completed.size,
            totalAmount = (active + completed).sumOf { it.amount }
        )
    }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), OrderSummary(0, 0, 0.0))
}

Roomはordersテーブルの変更を自動的に追跡し、変更があるとデータを再クエリします。StateFlow + Roomは、Room + LiveDataの最新の代替です。Google(Android Architecture Guide、2025)によると、Flow + StateFlow + Roomのスタックは、データベース変更時のリアクティブなUI更新が必要なすべてのKotlinプロジェクトに推奨されます。

よくある質問

StateFlowにおける融合(conflation)とは?

融合は、StateFlowが最後に送信された値のみを保持するメカニズムです。サブスクライバーが前の値を処理する前に新しい値が送信されると、中間の値は失われます。これはUIにとって重要です。状態がLoading → Success → Errorと変化し、UIがSuccessをレンダリングしていない場合、余分なレンダリングなしで直接Errorに移行します。融合は、Composeでの過剰な再コンポジションを防ぐAndroidの重要な最適化です。

LiveDataをStateFlowに変換するには?

lifecycle-livedata-ktxライブラリの拡張関数liveData.asFlow()を使用し、次に.stateIn()でStateFlowに変換します。逆変換はstateFlow.asLiveData()です。変換はLiveDataからStateFlowへの移行時に役立ちます。古いViewをLiveDataでサブスクライブしたまま、ViewModelを順次StateFlowに変換できます。

StateFlowに初期値が必要な理由は?

StateFlowは常に値を持つ必要があります — これがインターフェースの契約です:新しく接続したサブスクライバーは、待つことなく即座に現在の状態を受け取ります。初期値はMutableStateFlow(initialValue)コンストラクタまたはstateIn(initialValue)オペレーターに渡されます。状態が存在しない可能性がある場合は、null許容型でMutableStateFlow<T?>(null)を使用し、UIでnullを処理します。

StateFlowはスレッドセーフですか?

はい、StateFlowはスレッドセーフです。valueの読み取りと書き込みはアトミック操作(CAS)を使用します。ただし、collect()はsuspend関数であり、コルーチン内で起動する必要があります。発行と収集が異なるスレッドで発生する場合、StateFlowはvalueに対するすべての操作のhappens-beforeを保証します。ViewでStateFlowを収集するには、lifecycleScope.launch { repeatOnLifecycle(STATE.STARTED) { stateFlow.collect { ... } } }を使用します。

1つのViewModelでいくつのStateFlowを保持できますか?

厳密な制限はありませんが、画面ごとに3〜5個以上の個別のStateFlowを使用しないことをお勧めします。より多くの異なる状態が必要な場合は、シールドクラスまたはデータクラスを使用して1つに結合します。各StateFlowは収集時にContinuationオブジェクトの割り当てを必要とし、100個のStateFlowはGCに顕著な負荷をかける可能性があります。Googleの推奨によると、画面ごとに1つのシールドクラスUIStateが、読みやすさとパフォーマンスの最適なバランスです。

まとめ

  • StateFlow — Kotlin Coroutinesのホットリアクティブコンテナ(replay=1)、常に最後の値を保持します。
  • StateFlow vs LiveData:StateFlowはLifecycleに依存せず、コルーチンとマルチプラットフォームをサポート。LiveDataは自動サブスクリプション。
  • MutableStateFlowをprivate setで使用し、読み取り専用のStateFlowを公開 — ViewModelの標準パターン。
  • シールドクラスをUIStateとして使用 — 複雑な画面状態を管理するためのGoogle推奨のUDFアプローチ。
  • stateIn()WhileSubscribed(5000) — ViewModel向けにコールドFlowをStateFlowに変換する最適な戦略。
  • RoomはDAOからFlowを返す — StateFlow + combine + RoomがRoom + LiveDataを置き換えます。
  • Googleは新しいKotlinプロジェクト、特にJetpack Composeとの組み合わせでStateFlowを推奨しています。

ターンキー方式のモバイルアプリケーションを開発します

IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。

プロジェクトについて相談

こちらもお読みください