🐡

SharedFlowを使って複数画面で値を共有する

に公開

Android開発をしていると複数画面で値を共有したい場合があります。
例えば以下のように、画面Aで表示されているテキストを画面Bで編集できるようなケースです。
画面Bでテキストを編集すると、画面Aに反映されるという仕様です。

業務でこのようなケースに遭遇した場合に、適切な実装方法が分からず、共有するViewModelを作成して、そこに共通のリソースを持たせる作りにしたのですが、以下のような扱いづらさがありました。

  • 共有のViewModelに持たせているリソースを各画面のViewModelで処理したい場合にややこしくなる
  • 1画面に対応するViewModelが複数あることで見通しが悪くなる

画面A

画面B

そこで、SharedFlowを利用した実装方法だと、上記の問題が解決されていそうなので、サンプルコードを書いてまとめておきます。

登場人物は、以下の3人です。

  • SharedFlow (ShareUiState.kt)
  • SharedFlowに対してデータを出力するEmitter (SubViewModel.kt)
  • SharedFlowから流れてくるデータを受け取るSubscriber (HomeViewModel.kt)

ShareUiState.kt

  • 共有するリソースをShareUiStateクラスとして作成
  • MutableSharedFlowのコンストラクタに渡すreplyは1とします。
    • replyはSubscriberがsubscribeしている時に流れてくるデータの数です。デフォルト値は0なのでこのままだと流れてきません。
  • MutableSharedFlow<T>.emit(value: T)を呼び出すことで、データをFlowに出力する事ができます
class ShareUiState {
    private val _shareText: MutableSharedFlow<String> = MutableSharedFlow(1)
    val shareText: SharedFlow<String> = _shareText

    suspend fun send(value: String) {
        _shareText.emit(value)
    }
}

HomeViewModel.kt

  • 画面Aに対応するViewModelであり、FlowをSubscribeするSubscriberです。
  • shareUiState.shareText.collect{}でSharedFlowからデータを受け取り、uiStateを更新します。
class HomeViewModel(
    private val shareUiState: ShareUiState
): ViewModel() {
    private val _uiState: MutableStateFlow<HomeUiState> = MutableStateFlow(getInitialUiState())
    val uiState: StateFlow<HomeUiState> = _uiState

    init {
        listenUpdate()
    }

    private fun listenUpdate() {
        viewModelScope.launch {
            shareUiState.shareText.collect { value ->
                _uiState.value = _uiState.value.copy(text = value)
            }
        }
    }
}

SubViewModel.kt

  • 画面Bに対応するViewModelであり、Flowに対してEmitするEmitterです。
  • ユーザーイベントにより渡されてくるテキストをSharedFlowにEmitします。
class SubViewModel(
    private val shareUiState: ShareUiState
): ViewModel() {
    private val _uiState: MutableStateFlow<SubUiState> = MutableStateFlow(getInitialUiState())
    val uiSate: StateFlow<SubUiState> = _uiState

    fun onTextChange(new: String) {
        viewModelScope.launch {
            _uiState.value = _uiState.value.copy(text = new)
            shareUiState.send(new)
        }
    }
}

Discussion