【正社員】還元率83%【フリーランス】マージン一律5万円で案件をご紹介させていただきます。 詳細はこちら

【Android】<Kotlin>StateFlowとSharedFlowの使い方から実践的な処理の例まで徹底解説!

【Android】<Kotlin>StateFlowとSharedFlowの使い方から実践的な処理の例まで徹底解説!
すだ

みなさまこんにちは〜!
メモリアインクのすだです。

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の演算子(mapfilterなど)も使えます。

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
}

StateFlow は LiveData に比べて Kotlin Coroutines との連携がしっかりしているため、
通信などの非同期処理で取得したデータをリアルタイムに UI に反映させたい場合には、
StateFlow を使うのが適していると思います。
ただ、状況によっては LiveData の方が扱いやすいケースもあるため、無理に置き換える必要はありません。

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エンジニアを目指すなら【ユニゾンキャリア】
を通じて、
自分の市場価値をさらに向上させてみませんか?

それではまた次回の記事でお会いしましょう!

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

弊社テックブログをご愛読いただきありがとうございます。
当テックブログを運用している株式会社メモリアインクは、
【正社員】還元率83%
【フリーランス】マージン一律5万円で案件のご紹介
と、エンジニアの皆様に分かりやすい形で稼げる仕組みを構築し提供させていただいております。

コメント

コメントする

目次