Kotlin Flow 実践メモ:StateFlow の .value と .emit() の使い分け
Kotlin の StateFlow を使っていると、
「値をセットするときは .emit() を使うべき?」という疑問に出会います。
結論から言えば、使い分けが必要です。
特に通信処理やUI更新などで誤信号を防ぎたいとき、
この区別を理解しておくことが重要です。
⚙️ .value と .emit() の違い
| 項目 | .value |
.emit() |
|---|---|---|
| 使用場所 | 通常関数・非suspend関数でも可 | suspend関数内でのみ可 |
| 更新タイミング | 即時(同期的) | suspendにより順序制御される |
| 戻り動作 | 単なる代入 | suspend処理として一時停止 |
| 適用対象 | MutableStateFlow |
MutableStateFlow / MutableSharedFlow
|
| 主な用途 | 状態更新(単発) | イベント通知(連続) |
🧩 ① .value の使いどころ
MutableStateFlow の値を即座に更新したいときに使います。
通信結果やUI状態など、「最新値だけ保持」したい場合に最適です。
private val _state = MutableStateFlow("初期値")
val state: StateFlow<String> = _state
fun updateValue(newValue: String) {
_state.value = newValue // 即時反映、シンプルで安全
}
- スレッドセーフ
- 即時反映
- suspend不要
💡 単発通信の結果など、1回だけ確定的に更新したいときに最適です。
🧭 ② .emit() の使いどころ
emit() は suspend関数の中で逐次的に値を流すための関数です。
イベントや進捗など、“連続的な流れ”を扱う場合に向いています。
private val _state = MutableStateFlow("初期値")
val state: StateFlow<String> = _state
suspend fun fetchData() {
_state.emit("通信開始")
delay(1000)
_state.emit("通信完了")
}
- suspend関数のため、順序が保証される
- Flowの「流れの中で値を出す」用途に最適
💡 進捗表示・連続イベント通知など、複数段階で値を送るときに使います。
⚡ 通信処理の実践例(誤信号防止)
単発通信では、emit() よりも .value = ... の方が安全で高速です。
以下のように書くと、「1発通信・1発通知」になります。
class TcpRepository {
private val _responseFlow = MutableStateFlow<String?>(null)
val responseFlow: StateFlow<String?> = _responseFlow
suspend fun sendAndReceive(request: String) {
withContext(Dispatchers.IO) {
val socket = Socket("192.168.1.10", 4000)
socket.getOutputStream().write(request.toByteArray(Charsets.SJIS))
val buffer = ByteArray(1024)
val len = socket.getInputStream().read(buffer)
val response = buffer.copyOf(len).toString(Charsets.SJIS)
_responseFlow.value = response // ← emitではなくvalueで即時反映
socket.close()
}
}
}
🔸 emit() を使うと suspend のタイミングで競合が起きやすく、
通信結果が複数回発火することがあります。
「1発通信→1通知」にしたい場合は .value 一択です。
🧱 ③ Eventラッパとの併用(再通知防止)
StateFlow は最新値を保持するため、同じ値が再通知される場合があります。
その防止には「イベントラッパ」を使います。
data class Event<out T>(private val content: T) {
private var handled = false
fun getIfNotHandled(): T? =
if (handled) null else content.also { handled = true }
}
ViewModel側:
private val _eventFlow = MutableStateFlow<Event<String>?>(null)
val eventFlow = _eventFlow.asStateFlow()
fun notifyOnce(msg: String) {
_eventFlow.value = Event(msg)
}
UI側:
viewModel.eventFlow.collect { event ->
event?.getIfNotHandled()?.let { showToast(it) }
}
✅ まとめ:使い分けの指針
| 処理内容 | 推奨メソッド | 理由 |
|---|---|---|
| 単発通信・UI状態更新 | .value = ... |
即時反映・誤信号防止 |
| 進捗通知・イベント配信 | .emit(...) |
suspendで順序保証 |
| SharedFlowイベント送信 | .emit(...) |
逐次配信 |
| ViewModelの状態保持 | .value |
最新値を常に保持 |
💬 まとめの一言
Flow は「通信の通路」ではなく「状態の流れ」を扱うツールです。
通信(I/O)は suspend、通知は Flow。
この責務分離こそが、安定した通信設計と誤信号防止の鍵になります。
✍️ 書き手メモ
1発通信で誤信号が出ないようにするには、
“Flowをリアルタイム通知の手段として使いすぎない” こと。
.valueは「即時に結果を反映するスイッチ」として非常に有効です。