如何实现用户输入的定期处理?

Hec*_*tor 12 android rx-android rx-binding rx-java2

我当前的Android应用程序允许用户远程搜索内容.

例如,向用户显示一个EditText接受其搜索字符串并触发远程API调用的用户,该调用返回与输入的文本匹配的结果.

更糟糕的情况是,我只需添加一个TextWatcher并在每次调用时触发API调用onTextChanged.这可以通过在进行第一次API调用之前强制用户输入至少N个字符来进行搜索来改进.

"完美"解决方案具有以下特点: -

一旦用户开始输入搜索字符串

定期(每M毫秒)消耗输入的整个字符串.每当周期到期并且当前用户输入与先前的用户输入不同时触发API调用.

[是否有可能与输入的文本长度相关的动态超时?例如,当文本"短"时,API响应大小将很大并且需要更长时间才能返回和解析; 随着搜索文本变得越来越长,API响应大小将随着"飞行"和解析时间而减少

当用户重新键入EditText字段时,重新启动Periodic消耗文本.

每当用户按下ENTER键触发"最终"API调用,并停止监视用户输入到EditText字段.

设置用户在触发API调用之前必须输入的文本的最小长度,但将此最小长度与覆盖的超时值组合,以便在用户希望搜索"短"文本字符串时可以.

我确信RxJava和/或RxBindings可以支持上述要求,但到目前为止我还没有实现一个可行的解决方案.

我的尝试包括

private PublishSubject<String> publishSubject;

  publishSubject = PublishSubject.create();
        publishSubject.filter(text -> text.length() > 2)
                .debounce(300, TimeUnit.MILLISECONDS)
                .toFlowable(BackpressureStrategy.LATEST)
                .subscribe(new Consumer<String>() {
                    @Override
                    public void accept(final String s) throws Exception {
                        Log.d(TAG, "accept() called with: s = [" + s + "]");
                    }
                });


       mEditText.addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(final CharSequence s, final int start, final int count, final int after) {

            }

            @Override
            public void onTextChanged(final CharSequence s, final int start, final int before, final int count) {
                publishSubject.onNext(s.toString());
            }

            @Override
            public void afterTextChanged(final Editable s) {

            }
        });
Run Code Online (Sandbox Code Playgroud)

这与RxBinding

 RxTextView.textChanges(mEditText)
                .debounce(500, TimeUnit.MILLISECONDS)
                .subscribe(new Consumer<CharSequence>(){
                    @Override
                    public void accept(final CharSequence charSequence) throws Exception {
                        Log.d(TAG, "accept() called with: charSequence = [" + charSequence + "]");
                    }
                });
Run Code Online (Sandbox Code Playgroud)

这两个都没有给我一个结合输入的文本长度和超时值的条件过滤器.

我还用throttleLast取代了debounce,并且样品都没有提供所需的解决方案.

是否有可能实现我所需的功能?

动态超时

可接受的解决方案将应对以下三种情况

一世).用户希望搜索以"P"开头的任何单词

ⅱ).用户希望搜索以"Pneumo"开头的任何单词

III).用户希望搜索"Pneumonoultramicroscopicsilicovolcanoconiosis"这个词

在所有三种情况下,一旦用户键入字母"P",我将显示进度微调器(但此时不会执行API调用).我想平衡在响应式用户界面中提供用户搜索反馈的需求,而不是通过网络进行"浪费"的API调用.

如果我可以依赖用户输入他们的搜索文本然后单击"完成"(或"输入")键,我可以立即启动最终的API调用.

情景一

由于用户输入的文本长度很短(例如,1个字符长),我的超时值将处于其最大值,这使用户有机会输入其他字符并保存"浪费的API调用".

由于用户希望单独搜索字母"P",一旦Max Timeout到期,我将执行API调用并显示结果.此方案为用户提供最差的用户体验,因为他们必须等待我的Dynamic Timeout过期,然后等待返回并显示Large API响应.他们不会看到任何中间搜索结果.

情景二

这个场景结合了场景一,因为我不知道用户要搜索的内容(或搜索字符串的最终长度),如果他们"快速"键入所有6个字符,我可以执行一个API调用,但是他们进入6的速度越慢字符将增加执行浪费的API调用的机会.

此方案为用户提供了改进的用户体验,因为他们必须等待我的动态超时过期,但他们确实有机会看到中间搜索结果.API响应将小于方案一.

情景三

这个场景结合了场景一和二,因为我不知道用户将要搜索什么(或搜索字符串最终长度)如果他们"快速"键入所有45个字符我可以执行一个API调用(可能!),但是他们键入较慢的45个字符将增加执行浪费的API调用的机会.

我与任何提供我所需解决方案的技术无关.我相信Rx是迄今为止我发现的最佳方法.

yos*_*riz 8

像这样的东西应该工作(没有真正尝试)

 Single<String> firstTypeOnlyStream = RxTextView.textChanges(mEditText)
            .skipInitialValue()
            .map(CharSequence::toString)
            .firstOrError();

    Observable<CharSequence> restartTypingStream = RxTextView.textChanges(mEditText)
            .filter(charSequence -> charSequence.length() == 0);

    Single<String> latestTextStream = RxTextView.textChanges(mEditText)
            .map(CharSequence::toString)
            .firstOrError();

    Observable<TextViewEditorActionEvent> enterStream =
            RxTextView.editorActionEvents(mEditText, actionEvent -> actionEvent.actionId() == EditorInfo.IME_ACTION_DONE);

    firstTypeOnlyStream
            .flatMapObservable(__ ->
                    latestTextStream
                            .toObservable()
                            .doOnNext(text -> nextDelay = delayByLength(text.length()))
                            .repeatWhen(objectObservable -> objectObservable
                                    .flatMap(o -> Observable.timer(nextDelay, TimeUnit.MILLISECONDS)))
                            .distinctUntilChanged()
                            .flatMap(text -> {
                                if (text.length() > MINIMUM_TEXT_LENGTH) {
                                    return apiRequest(text);
                                } else {
                                    return Observable.empty();
                                }
                            })
            )
            .takeUntil(restartTypingStream)
            .repeat()
            .takeUntil(enterStream)
            .mergeWith(enterStream.flatMap(__ ->
                    latestTextStream.flatMapObservable(this::apiRequest)
            ))
            .subscribe(requestResult -> {
                //do your thing with each request result
            });
Run Code Online (Sandbox Code Playgroud)

我们的想法是根据您对每个X时间采样的要求,基于采样而不是文本更改事件本身构建流.

我在这里做的方法是构造一个流(firstTypeOnlyStream用于事件的初始触发(第一次用户输入文本),此流将在用户的第一次输入时启动整个处理流,接下来,当第一次输入时触发器到达时,我们将基本上使用latestTextStream.latestTextStream来定期对编辑文本进行采样.实际上并不是一段时间内的流,而是EditText使用InitialValueObservableRxBinding属性的当前状态的采样(它只是在订阅时发出当前文本EditText)换句话说,这是获取当前文本的一种奇特方式,它等同于:
Observable.fromCallable(() -> mEditText.getText().toString());
接下来,对于动态超时/延迟,我们nextDelay根据文本长度更新,并使用repeatWhen计时器等待所需的时间.同时distinctUntilChanged,它应该根据文本长度给出所需的抽样.接下来,我们将根据文本触发请求(如果足够长).

停止输入 -使用takeUntilenterStream将上输入被触发,它也将触发最终的查询.

重新启动 - 当用户'重新启动'键入时 - 即文本为空,.takeUntil(restartTypingStream)+ repeat()将在空字符串输入时停止流,并重新启动它(重新订阅).