Flowに対して勘違いをしていたことがいくつかあって、そのうちの1つである debounce
について試したのでまとめます。
debounceに勝手に期待していた挙動
flowでdebounce
を指定すれば、設定したms内では処理を1回に制限してくれると勘違いしていました。
例えばこういうFlowの処理があるとして、debounce(1000)
1秒に設定します。
勘違いしていた結果としては、Logcat
に 34
が出力されると思ってました。
1000ms以内で最後の処理だけ実行されるので、1
と2
はスルーされ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をクリックしたらカウントアップされるようなよくあるサンプルを作りました。
onClickの中の処理はこんな感じ。
5秒間debounce
を設定しています。
Fabを押すと押した分だけカウントされるしSnackbarが表示されます。5秒とか関係ないです。 クリックのたびにonEachしているので当然です。
こんなことするのかという感じですが、私はやってしまってました。
連打対策のつもりでやってしまっていた
サンプルなのでカウントアップにしてますが、実際には送信ボタンを押されたらAPIリクエストするなどすることがあると思います。
その時に連打対策のつもりでdebounce(1000)
などして、ユーザーが無意識にボタンをダブルクリックしてしまったときなどに、
無駄にAPIリクエストをしてしまわないように備えているつもりでしたが、実行はされるのでなにも対策になってないなと気づきました。
欲しかったのは throttle でした
色々調べる中でthrottle
を知りましたが、Flowには実装されてない(?調査が甘いだけかも)かもしれず、拡張として自前で実装するか、一度リクエストしたらタイムアウト込みでレスポンスが返ってくるでは操作させないなどが正しい対策になると思います。
debounceは使えない子なのか?
じゃあdebounce
は使えないかというとそんなこともなく、実行は連続でされてもいいけど出力は制限したい場合には有効だと思います。
例えば、ユーザーのテキスト入力に合わせてサジェストを表示するときなど、一定時間に合わせてdebounce
するのは有効です。