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

【Android】<Kotlin>イベント通知処理におけるSharedFlowとChannelの違いを比較

【Android】<Kotlin>イベント通知処理におけるSharedFlowとChannelの違いを比較
すだ

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

今回は、イベント通知処理の実装に便利な「SharedFlow」と「Channel」について、その違いとそれぞれの使い方をわかりやすく解説していきます。

この記事を読んでわかること…
・SharedFlowとChannelの仕様
・SharedFlowとChannelの比較

目次

環境

  • Kotlin (ver 1.9.0)
  • Android Studio (Giraffe | 2022.3.1 Patch 3)

SharedFlow と Channel の基本的な理解

Kotlinには、非同期でデータを送受信するための機能として SharedFlowChannel があります。
どちらもコルーチンを活用した非同期プログラミングにおいて重要な役割を果たしますが、用途や動作の違いを理解することで、より適切な設計が可能になります。

SharedFlow

SharedFlow は、同じデータを複数の購読者(コルーチン)に同時に配信できる仕組みです。
たとえるなら、「拡声器」のようなものです。発信されたメッセージは、それを聞いているすべての人に一斉に届きます。

Channel

Channel は1対1の通信を目的としたデータの受け渡しの仕組みです。
こちらは「手渡しの郵便」に近いイメージで、1つのデータを1人にだけ確実に渡します。

SharedFlow と Channel の使い分け

SharedFlowよりChannelを使ったほうがいい例

たとえば SharedFlowを使ってしまっている以下の処理があるとします。

例)ユーザー情報を表示する画面の表示処理で、ユーザー情報を取得する&「ようこそ〜〜さん」のトースト表示をする

class UserViewModel : ViewModel() {
    private val _userEvent = MutableSharedFlow<String>() // replay = 0
    val userEvent = _userEvent.asSharedFlow()

    init {
        viewModelScope.launch {
            val userName = "Taro" // 即時取得できたと仮定
            _userEvent.emit(userName)
        }
    }
}
class UserFragment : Fragment() {

    private val viewModel: UserViewModel by viewModels() // これで取得

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        lifecycleScope.launch {
            viewModel.userEvent.collect { name ->
                Toast.makeText(requireContext(), "ようこそ $name さん", Toast.LENGTH_SHORT).show()
            }
        }
    }
}

この構成では、UserViewModel のインスタンスが生成されるとすぐに init ブロックが実行され、
userNameのemitが発生します。

しかし、Fragment側でのcollect 処理はまだ始まっていないため、この時点ではSharedFlowに購読者が存在しません。
加えてSharedFlowのreplayが0に設定されている場合、過去に発行された値は保持されません。
そのため、イベントはそのまま流れて失われてしまい、結果としてトーストは表示されないという事態になります

これを防ぐことができるのが Channelの仕様です。▼

class UserViewModel : ViewModel() {
    private val _userEvent = Channel<String>(Channel.BUFFERED)
    val userEvent = _userEvent.receiveAsFlow()

    init {
        viewModelScope.launch {
            val userName = "Taro"
            _userEvent.send(userName)
        }
    }
}

Channelの特徴として、まだ購読者がいなかった場合に待ってくれます。
そして購読者が現れたタイミングで1回のみイベントを送信してくれるのです。

ChannelよりSharedFlowを使ったほうがいい例

たとえば Channelを使ってしまっている以下の処理があるとします。

例)「保存」ボタンを押下した際に画面にトーストを表示し、同時にバックグラウンドでトラッキングを送信

class SaveViewModel : ViewModel() {
    private val _saveEvent = Channel<String>(Channel.BUFFERED)
    val saveEvent = _saveEvent.receiveAsFlow()

    fun onSaveSuccess() {
        viewModelScope.launch {
            _saveEvent.send("保存に成功しました")
        }
    }
}
class SaveFragment : Fragment() {

    private val viewModel: SaveViewModel by viewModels()

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        binding.saveButton.setOnClickListener {
            viewModel.onSaveSuccess()
        }

        // ① UIトースト通知
        viewLifecycleOwner.lifecycleScope.launch {
            viewModel.saveEvent.collect { message ->
                Toast.makeText(requireContext(), message, Toast.LENGTH_SHORT).show()
            }
        }

        // ② トラッキング送信
        viewLifecycleOwner.lifecycleScope.launch {
            viewModel.saveEvent.collect { message ->
                sendTrackingEvent("save_success", mapOf("message" to message))
            }
        }
    }
}

この構成では、ボタン押下により ViewModel側でイベントがsend()され、
それをFragment側で2つのcollectが受け取ろうとします

しかし Channel は「最初に値を受け取った1人だけが処理を行い、それ以外には届かない(消費される)」という性質を持つため、

  • ①のトースト表示 または
  • ②のトラッキング送信

のどちらか一方しか実行されず、もう一方はスルーされてしまうという問題が発生します。

これを防ぐことができるのがSharedFlowの仕様です▼

class SaveViewModel : ViewModel() {
    private val _saveEvent = MutableSharedFlow<String>(replay = 0)
    val saveEvent = _saveEvent.asSharedFlow()

    fun onSaveSuccess() {
        viewModelScope.launch {
            _saveEvent.emit("保存に成功しました")
        }
    }
}

SharedFlowの特徴として、複数購読者に同じイベントを同時に届けることができます。
値は消費されずどのcollectにも通知が届くので、イベントの多重実行が必要な場合はSharedFlowを使うと良いでしょう。

まとめ

おつかれさまでした。いかがでしたでしょうか!

一度だけ、かつひとつの処理だけに通知したいイベントには Channel を、
複数の処理に同時に通知したいイベントには SharedFlow を使うのが適切です。
この使い分けをぜひ覚えておいてください。

すだ

技術者としてのキャリアパスを次のレベルへと進めたい皆様、
未経験からIT・Webエンジニアを目指すなら【ユニゾンキャリア】
を通じて、
自分の市場価値をさらに向上させてみませんか?

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

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

この記事を書いた人

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

コメント

コメントする

目次