kako.dev

開発、自作アプリのこと

Flowのdebounceについて勘違いしていたので調べた

Flowに対して勘違いをしていたことがいくつかあって、そのうちの1つである debounce について試したのでまとめます。

debounceに勝手に期待していた挙動

flowでdebounceを指定すれば、設定したms内では処理を1回に制限してくれると勘違いしていました。

例えばこういうFlowの処理があるとして、debounce(1000) 1秒に設定します。 勘違いしていた結果としては、Logcat34 が出力されると思ってました。

1000ms以内で最後の処理だけ実行されるので、12はスルーされ3だけ実行される。 4は1001秒で1秒より後の遅延処理なのでそのまま実行される みたいな思考をしてました。

実際の出力

1
2
3
4

実際の出力は 1234 で emit内はすべて処理されてます。

色々試してみた

もうちょっと実際のアプリケーションでの動きを想定をしてみます。

簡単なこういうリポジトリがあるとして

こういうテストコード書きます。

最後に Assert.assertTrue(repository.getCount() == 2) で countが2であることを期待してますが、このテストは通るでしょうか。

通りません。

このテストコードの最終的なcountの値は4です

flowの中で4回repository.countUp() しています。これらはすべて実行されます。

その後で flow.collect {} をしていますが、ここには2回分の出力しかされません。

count = 3
count = 4

debounceは指定された秒数内の最後の結果を返すのであって実行はされます。
よってcountは最後4となりテストに失敗します。

実際のアプリケーションでよくやってしまっていた間違い

Fabをクリックしたらカウントアップされるようなよくあるサンプルを作りました。

f:id:h3-birth:20210904182101p:plain:w250

onClickの中の処理はこんな感じ。 5秒間debounceを設定しています。

Fabを押すと押した分だけカウントされるしSnackbarが表示されます。5秒とか関係ないです。 クリックのたびにonEachしているので当然です。

こんなことするのかという感じですが、私はやってしまってました。

連打対策のつもりでやってしまっていた

サンプルなのでカウントアップにしてますが、実際には送信ボタンを押されたらAPIリクエストするなどすることがあると思います。

その時に連打対策のつもりでdebounce(1000)などして、ユーザーが無意識にボタンをダブルクリックしてしまったときなどに、 無駄にAPIリクエストをしてしまわないように備えているつもりでしたが、実行はされるのでなにも対策になってないなと気づきました。

欲しかったのは throttle でした

色々調べる中でthrottleを知りましたが、Flowには実装されてない(?調査が甘いだけかも)かもしれず、拡張として自前で実装するか、一度リクエストしたらタイムアウト込みでレスポンスが返ってくるでは操作させないなどが正しい対策になると思います。

debounceは使えない子なのか?

じゃあdebounce は使えないかというとそんなこともなく、実行は連続でされてもいいけど出力は制限したい場合には有効だと思います。 例えば、ユーザーのテキスト入力に合わせてサジェストを表示するときなど、一定時間に合わせてdebounceするのは有効です。

サンプル公開

github.com