
みなさまこんにちは〜!
メモリアインクのすだです。
Kotlinでの開発における状態管理やイベント処理は「StateFlow」と「SharedFlow」がとても便利です。
今回はそれぞれの使い方をわかりやすくお伝えしていきます。
この記事を読んでわかること…
・StateFlowとは&使い方
・SharedFlowとは&使い方
・StateFlowとSharedFlowの実践的な使い方の例
Flowに関しては以下の記事で詳しく解説しておりますので、
ぜひ合わせてご覧ください。


環境
- Kotlin (ver 1.9.0)
- Android Studio (Giraffe | 2022.3.1 Patch 3)
StateFlow、SharedFlowとは?
StateFlow も SharedFlow も、どちらも Kotlin Flow の一種(拡張)です。
Flow(基本形)
├── StateFlow:常に最新の1つの「状態」を保持する Flow
└── SharedFlow:「イベント」を複数に届けるための Flow
どちらも Flow インターフェースを継承しているので、collect {}
で使える&Flowの演算子(map
・filter
など)も使えます。
StateFlowについて
StateFlow は「常に最新の状態(1つの値)を持ち続ける Flow」です。
UIとの相性が良いという点で、LiveDataと似ている機能といえます。
・普通の Flow
:1→2→3→… と値が「過去から現在に向かって流れてくる」
・StateFlow
:今の値=「最新の状態」をいつでも取得できる(LiveDataっぽい)
class CounterViewModel : ViewModel() {
private val _count = MutableStateFlow(0)
val count: StateFlow<Int> = _count
fun increment() {
_count.value = _count.value + 1
}
}
lifecycleScope.launchWhenStarted {
viewModel.count.collect { value ->
textView.text = value
}
}
LiveDataとの使い分け
StateFlowとLiveDataは、どちらも「データの変化を監視する」ための仕組みという点では似ていますが
この二つには使い分けるべきシーンがあります。
簡単にまとめると、
・非同期処理やコルーチン中心の設計なら StateFlow
が◎
・シンプルに UI とつなぎたいだけなら LiveData
もあり
ということになります。
「StateFlow」のUIとの接続 ▼
Kotlin Croutinesを使っている場合や、通信処理・非同期処理の結果を扱いたい場合はこちらを使ってください。
lifecycleScope.launchWhenStarted {
viewModel.count.collect { value ->
textView.text = value
}
}
「LiveData」のUIとの接続 ▼
シンプルに UI を更新したい場合はこちらを使ってください。
viewModel.liveData.observe(viewLifecycleOwner) { value ->
textView.text = value
}
SharedFlowについて
SharedFlow は「1回だけ流れるイベントを複数のリスナーに共有するための Flow」 です。
ここでいう「イベント」とは、状態ではなく「一瞬だけ起きる出来事」を意味します。
たとえば:
- トーストメッセージの表示
- 画面遷移のトリガー
- ダイアログの表示
- ログイン成功の通知
- ボタンが押されたことを通知
→これらは「表示したら終わり」の一時的な処理=「イベント」です。
こういう一時的な通知を LiveData や StateFlow で扱おうとすると、
「画面回転でもう一度通知されてしまう」などの問題が起きやすいです。
StateFlow は「最新の状態を常に保持する」ので、
→ UIが再表示されたときにも前の値がまた届いてしまう。
SharedFlow は「値を保持しない」ので、そういった問題を避けられます。
class EventViewModel : ViewModel() {
private val _event = MutableSharedFlow<String>()
val event: SharedFlow<String> = _event
fun sendEvent() {
viewModelScope.launch {
_event.emit("ボタンが押されました")
}
}
}
lifecycleScope.launchWhenStarted {
viewModel.eventFlow.collect { message ->
Toast.makeText(context, message, Toast.LENGTH_SHORT).show()
}
}
→このように、ボタンを押したときだけメッセージを流し、UIでは1回だけ受け取るというパターンに非常に便利です。
StateFlowを使った実践的な処理の例
以下に、StateFlowを使った実践的な処理の例として、
「APIで取得したユーザー一覧を UI にリアルタイムで表示する」というシンプルかつ実用的な構成をご紹介します。
ViewModel
class UserViewModel : ViewModel() {
private val _users = MutableStateFlow<List<User>>(emptyList())
val users: StateFlow<List<User>> = _users
init {
loadUsers()
}
private fun loadUsers() {
viewModelScope.launch {
// 実際は API から取得する想定
delay(1000) // 疑似通信
_users.value = listOf(
User(1, "山田太郎"),
User(2, "佐藤花子")
)
}
}
}
val users: StateFlow> = _users
によって、 APIから取得したデータを UI側でStateFlowとして受け取れるよう準備をします。
そして、API通信後に.value
を使ってStateFlowの状態を更新(= 新しく保持)しています。
Fragment(StateFlowをcollectしてUIに表示)
class UserFragment : Fragment() {
private val viewModel: UserViewModel by viewModels()
private lateinit var adapter: UserAdapter
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
adapter = UserAdapter()
recyclerView.adapter = adapter
lifecycleScope.launchWhenStarted {
viewModel.users.collect { list ->
adapter.submitList(list)
}
}
}
}
UserViewModel by viewModels()
で初期化されたタイミングで、ViewModel内のloadUsers()
処理が走ります。launchWhenStarted
は Lifecycle に連動してコルーチンを安全に実行するための関数で、
画面が表示されてるときだけ処理を動かしてくれます。
そしてcollect
で、StateFlowから値が届いたときにそれを受け取っています。
SharedFlowを使った実践的な処理の例
以下に、SharedFlowを使った実践的な処理の例として、
「フォーム送信後の完了メッセージ表示を1回だけ表示する」というシンプルかつ実用的な構成をご紹介します。
ViewModel(完了イベントを送信)
class FormViewModel : ViewModel() {
private val _formSubmitted = MutableSharedFlow<String>()
val formSubmitted: SharedFlow<String> = _formSubmitted
fun submitForm(name: String, email: String) {
viewModelScope.launch {
try {
// ここで実際はAPIやDBに送信処理を行う(疑似処理)
delay(1000) // 通信の代わりに遅延
_formSubmitted.emit("送信が完了しました")
} catch (e: Exception) {
_formSubmitted.emit("送信に失敗しました")
}
}
}
}
val formSubmitted: SharedFlow = _formSubmitted
によって、UI側でイベント通知を SharedFlow
として受け取れるように公開しています。
そして送信処理の完了後に .emit()
を使って、そのイベントを1回だけ流します。
Fragment(SharedFlow を collect してメッセージ表示)
class FormFragment : Fragment() {
private val viewModel: FormViewModel by viewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
val nameInput = view.findViewById<EditText>(R.id.editName)
val emailInput = view.findViewById<EditText>(R.id.editEmail)
val sendButton = view.findViewById<Button>(R.id.sendButton)
// ボタンクリック時に ViewModel へ入力値を送信
sendButton.setOnClickListener {
val name = nameInput.text.toString()
val email = emailInput.text.toString()
viewModel.submitForm(name, email)
}
// フォーム送信完了メッセージを SharedFlow で受け取って表示
viewLifecycleOwner.lifecycleScope.launchWhenStarted {
viewModel.formSubmitted.collect { message ->
Snackbar.make(view, message, Snackbar.LENGTH_LONG).show()
}
}
}
}
クリックリスナー内のviewModel.submitForm(name, email)
で送信処理が走り、
送信処理後に通知されたイベントをviewModel.formSubmitted.collect
で受け取って
送信完了メッセージが表示される仕組みです。
まとめ
おつかれさまでした。いかがでしたでしょうか!
Kotlin Flow を使いこなすには、StateFlow と SharedFlow を目的に応じて使い分けることが大切です。
StateFlowが向いている用途は
「常にUIと同期させたい状態(カウント、ロード中など)」、
SharedFlowが向いている用途は
「一度だけ通知すればよいイベント(トースト、ナビゲーションなど)」
と覚えてください!



技術者としてのキャリアパスを次のレベルへと進めたい皆様、
<未経験からIT・Webエンジニアを目指すなら【ユニゾンキャリア】>
自分の市場価値をさらに向上させてみませんか?
それではまた次回の記事でお会いしましょう!
コメント