Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to implement Periodic processing of user input?

My current Android application allows users to search for content remotely.

e.g. The user is presented with an EditText which accepts their search strings and triggers a remote API call that returns results that match the entered text.

Worse case is that I simply add a TextWatcher and trigger an API call each time onTextChanged is called. This could be improved by forcing the user to enter at least N characters to search for before making the first API call.

The "Perfect" solution would have the following features:-

Once the user starts entering search string(s)

Periodically (every M milliseconds) consume the entire string(s) entered. Trigger an API call each time the period expires and the current user input is different to the previous user input.

[Is it possible to have a dynamic timeout related to the entered texts length? e.g while the text is "short" the API response size will be large and take longer to return and parse; As the search text gets longer the API response size will reduce along with "inflight" and parsing time]

When the user restarts typing into the EditText field restart the Periodic consumption of text.

Whenever the user presses the ENTER key trigger "final" API call, and stop monitoring user input into the EditText field.

Set a minimum length of text the user has to enter before an API call is triggered but combine this minimum length with an overriding Timeout value so that when the user wishes to search for a "short" text string they can.

I am sure that RxJava and or RxBindings can support the above requirements however so far I have failed to realise a workable solution.

My attempts include

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) {

            }
        });

And this with 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 + "]");
                    }
                });

Neither of which give me a conditional filter that combines entered text length and a Timeout value.

I've also replaced debounce with throttleLast and sample neither of which furnished the required solution.

Is it possible to achieve my required functionality?

DYNAMIC TIMEOUT

An acceptable solution would cope with the following three scenarios

i). The user wishes to search for the any word beginning with "P"

ii). The user wishes to search for any word beginning with "Pneumo"

iii). The user wishes to search for the word "Pneumonoultramicroscopicsilicovolcanoconiosis"

In all three scenarios as soon as the user types the letter "P" I will display a progress spinner (however no API call will be executed at this point). I would like to balance the need to give the user search feedback within a responsive UI against making "wasted" API calls over the network.

If I could rely on the user entering their search text then clicking the "Done" (or "Enter") key I could initiate the final API call immediately.

Scenario One

As the text entered by the user is short in length (e.g. 1 character long) My timeout value will be at its maximum value, This gives the user the opportunity to enter additional characters and saves "wasted API calls".

As the user wishes to search for the letter "P" alone, once the Max Timeout expires I will execute the API call and display the results. This scenario gives the user the worst user experience as they have to wait for my Dynamic Timeout to expire and then wait for a Large API response to be returned and displayed. They will not see any intermediary search results.

Scenario Two

This scenario combines scenario one as I have no idea what the user is going to search for (or the search strings final length) if they type all 6 characters "quickly" I can execute one API call, however the slower they are entering the 6 characters will increase the chance of executing wasted API calls.

This scenario gives the user an improved user experience as they have to wait for my Dynamic Timeout to expire however they do have a chance of seeing intermediary search results. The API responses will be smaller than scenario one.

Scenario Three

This scenario combines scenario one and two as I have no idea what the user is going to search for (or the search strings final length) if they type all 45 characters "quickly" I can execute one API call (maybe!), however the slower they type the 45 characters will increase the chance of executing wasted API calls.

I'am not tied to any technology that delivers my desired solution. I believe Rx is the best approach I've identified so far.

like image 271
Hector Avatar asked Mar 14 '18 12:03

Hector


1 Answers

Well, you could use something like this:

RxSearch.fromSearchView(searchView)
            .debounce(300, TimeUnit.MILLISECONDS)
            .filter(item -> item.length() > 1)
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(query -> {
                adapter.setNamesList(namesAPI.searchForName(query));
                adapter.notifyDataSetChanged();
                apiCallsTextView.setText("API CALLS: " + apiCalls++);
            });    

public class RxSearch {
    public static Observable<String> fromSearchView(@NonNull final SearchView searchView) {
        final BehaviorSubject<String> subject = BehaviorSubject.create("");

        searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {

            @Override
            public boolean onQueryTextSubmit(String query) {
                subject.onCompleted();
                return true;
            }

            @Override
            public boolean onQueryTextChange(String newText) {
                if (!newText.isEmpty()) {
                    subject.onNext(newText);
                }
                return true;
            }
        });

        return subject;
    }
}

blog referencia

like image 174
João Carlos Avatar answered Oct 27 '22 10:10

João Carlos