LoginSignup
2

More than 1 year has passed since last update.

posted at

updated at

【Kotlin】SharedFlow、StateFlowを使ってアプリを作る【Coroutine】

この記事はKotlin Advent Calendar 2020 の24日目の記事です。

背景

以前にCoroutines Flowを使ってRxを卒業する記事を書き、Coroutine Flowのサンプルを書きましたが、その後、Flow界隈に新星が生まれました。

  • StateFlow
    • subscribeで直近の値を受信する (複数回連続で値が来た場合は最新のみ受信)
    • 初期値が必要
    • Coroutinesの文脈で使うLiveData
    • RxでいうところのBehaviorSubject
  • SharedFlow
    • subscribeで値を受信する (複数回連続で来てもすべて受信)
    • 以前にあったBroadcastChannelとかその辺の後継
    • RxでいうところのPublishSubject

これらを使えば、RxどころかもはやLiveDataをほとんど使わずとも、Flowの世界だけでアプリを作ることができそうですよね?
というわけで、難しいことは考えずに早速基本的なアプリを作ってみたいと思います。

Sample

GithubのRepositoryを検索するアプリです。以前の記事と処理はほぼ同じ。

https://github.com/alpha2048/CoroutineStateSharedFlowTest

環境

  • Coroutine v1.4.2
  • (その他Retrofit、moshi、Koinの最新版)

解説

StateFlowでデータ管理する

実質LiveDataであるStateFlowを使って、APIから取得したデータを管理します。
取得したデータをMutableStateFlowに突っ込んでいきます。
UI側は公開されたStateFlowをsubscribeして、取得したデータを表示します。初期値を見てプログレスバーの出し分けも行います。

StateFlowに変換して公開する部分など、LiveDataの実装にかなり近いです。

ViewModel

class MainViewModel(
    private val usecase: GetRepositoryUsecase
) : ViewModel() {

    private val _repoItems: MutableStateFlow<List<RepoItemEntity>> = MutableStateFlow(emptyList())
    val repoItems: StateFlow<List<RepoItemEntity>> = _repoItems

    fun loadData() {
        viewModelScope.launch {
            usecase.execute("Coroutine", 1).collect {
                val list = _repoItems.value
                _repoItems.value = list + it.items
            }
        }
    }
}

View側

        lifecycleScope.launchWhenStarted {
            viewModel.repoItems
                .collect {
                    if (it.isNotEmpty()) {
                        binding.progress.visibility = View.INVISIBLE
                        adapter.setItem(it)
                    } else {
                        binding.progress.visibility = View.VISIBLE
                    }

                }
        }

SharedFlowでイベントを送ってみる

SharedFlowでイベント送信してみます。
(少し無理矢理な使い方ですが..😓)
OnClickListenerで、MutableSharedFlowにItemを渡し、View側でsubscribeします。Listenerの代わりですね。

Adapter側

        holder.binding.clickView.setOnClickListener {
            scope.launch {
                _onClick.emit(repoItem)
            }
        }

View側

        lifecycleScope.launchWhenStarted {
            adapter.onClick
                .collect {
                    val uri = Uri.parse(it.htmlUrl)
                    startActivity(Intent(Intent.ACTION_VIEW, uri))
                }
        }

おわりに

BroadcastChannelがobsoleteになり、イベント通知などこれからどうしよう・・?となっていましたが、新しく登場した2種の使い勝手がいい感じなのでとても安心しました。

Flowはまだまだ発展途上ではありますが、ここまでくればProductionにFlowを採用するメリットもありそうだなと感じます。

みなさまもお試しいただき、もっとこういう使い方があるよ、というのがあれば是非教えてください🙏

参考文献

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
2