たまにしか呼ばないけど連打されるリスクがある処理をFlowで(略)

2021/09/07に公開

使われる頻度はとても低いけど、悪意を含めて連打可能な処理があった時に、
無駄にリクエストを投げないために対応した方法が面白く発展させられそうな気がしたのでメモ

とりあえずコード

private val _repositoryResponseState = MutableStateFlow<List<Repo>?>(null)
private val repositoryFlow: Flow<List<Repo>?> = _repositoryResponseState.transform {
    if (it == null) {
        val response = repository.getRepo("sobaya-0141")?.body()
        _repositoryResponseState.value = response
        emit(response)
    } else {
        emit(it)
    }
}

fun getRepo(user: String) {
    viewModelScope.launch {
        val test = repositoryFlow.firstOrNull()
        print(test?.size)
    }
}

内容は一度リクエストを投げたら取特結果を覚えておくStateFlowの_repositoryResponseStateがいて、
_repositoryResponseStateに値が入っていたら保存値を、無ければリクエストを投げるようにtransformしたrepositoryFlowを用意しました。

あとはrepositoryFlowを購読したり値を取特するだけです。
今回の例だとgetRepoが連打で呼ばれる可能性がある処理の立ち位置です(本来引数が無いイメージだった)

とりあえず酒飲んでみた

fun <T> MutableStateFlow<T?>.getOrRequest(func: (String) -> Flow<Response<T>>): Flow<T?> =
    transform { it ->
        if (it == null) {
            val response = func("sobaya-0141").firstOrNull()?.body()
            response?.let { response ->
                value = response
            }
            emit(response)
        } else {
            emit(it)
        }
    }

なんと妖精さんがこんな拡張関数を用意してくれました。

class MainViewModel(private val repository: GithubRepository) : ViewModel() {

    private val repositoryResponseState = MutableStateFlow<List<Repo>?>(null).getOrRequest {
        repository.getRepo(it)
    }

    fun getRepo(user: String) {
        viewModelScope.launch {
            val test = repositoryResponseState.firstOrNull()
            print(test?.size)
        }
    }
}

fun <T> MutableStateFlow<T?>.getOrRequest(func: (String) -> Flow<Response<T>>): Flow<T?> =
    transform { it ->
        if (it == null) {
            val response = func("sobaya-0141").firstOrNull()?.body()
            response?.let { response ->
                value = response
            }
            emit(response)
        } else {
            emit(it)
        }
    }

元々のRepositoryはsuspend funにしてましたが、拡張関数に渡す時にsuspend funからしか呼べないよと怒られてしまったので妥協しました。
Flow内だから良いじゃん!!って思っても許してくれなかった・・・
と妖精さんが言ってた

最初の書き方と比べて拡張関数を一つ用意するだけでだいぶスッキリしましたね。

振り返り

ここまで書いてStateFlowの良さを殺してそうな気がしてもきた。
せっかくのStateFlowをFlowで返したらセットと同時に値が欲しい時に無駄ですね。

値のセットと取特が別で行われるのであればワンチャン
でも、大人しくStateFlow.valueで取ればいいしこんな書き方をする必要が無い気がしてきた。

結論は酒の力は怖い

Discussion