UIWidget 非同期処理をDebounceする

状況

  • UniRx
  • UIWidgets

f:id:rnitta:20200217133335p:plain

こういった文字列検索フォームを作っていて、
検索ロジックは「サーバーに検索文字列をPOSTしてそのレスポンスを反映する」というものでローカルで完結しない。

サーバーにアクセスする都合上、(大きな問題にはならないと思うが)TextField のonChangeごとにPOSTを投げていたのでは、 リクエスト数が膨大になってしまうし、しかも入力途中に発生するPOSTは完全に無駄になってしまうのでイベントを間引きしたい。

debounce とは、用語として正しいのかしらないけどjqueryのコレ からきている。
一定時間実行をpendingにして最後のイベントのみを実行する ということを指しています(ぼくは。)

解決方法

汎用的な正攻法は多分 CancellationToken みたいなもので正しく非同期処理をキャンセルすること だと思うが、

今回はこれで事足りたよ、という解決法

Widget

private TextEditingController _searchTextController = new TextEditingController();

public override Widget build(BuildContext context)
        {
                return new TextField(
                    controller: _searchTextController,
                    onChanged: (string val) => { SearchingTextSubject.OnNext(val); },
                );
}
        private async UniTask fetchSearchResult()
        {
            string searchTextMemo = this.SearchingText; // 後で画面反映をキャンセルするかどうかを判定するための素敵なフラグ 
            await Task.Delay(200); // 200msのdebounce
            if (searchTextMemo != this.SearchingText) return; // 検索中に入力が変わってしまったので、画面反映をキャンセルする
            
            WWWForm form = new WWWForm();
            form.AddField("q", this.SearchingText);

            using (UnityWebRequest www = UnityWebRequest.Post(Constant.API_DOMAIN + "/api/videos/search", form))
            {
                var res = await www.SendWebRequest();
                if (searchTextMemo != this.SearchingText) return; // 検索中に入力が変わってしまったので、画面反映をキャンセルする
                if (res.isNetworkError || res.isHttpError || res.responseCode != 200)
                {
                    using (WindowProvider.of(context).getScope())
                    {
                          // エラーレスポンスを画面に反映する処理
                    }
                }
                else
                {
                     // 正常レスポンスをデシリアライズして画面に反映する処理
                }
            }
        }

POSTする前と、画面反映する前に「本当に処理継続していいっすか?」という判定ロジックをいれて解決解決。