Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I send only last requst with Rx and Retrofit?

I've got an EditText view and TextWatcher for it, in onTextChanged method I have to requst server for result with query from EditText field. In my presenter I use rx for that, but i need to delay search until user's input ends. At this moment i've got this:

service.getData(query)
            .delaySubscription(REQUEST_DELAY_FROM_SERVER, TimeUnit.MILLISECONDS, Schedulers.io())
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(
                    data-> {
                        getViewState().showData(data);
                    },
                    error -> {
                        Log.e(this.getClass().getSimpleName(), error.getMessage(), error);
                    }
            );

But delaySubscription does not work as desired. It collects all call and after delay sends every of them. I have to do same as if I had used handler.postDelayed(), when only once request will be send.

like image 848
shagi Avatar asked Feb 28 '17 08:02

shagi


2 Answers

Edit 2:

The saple of a presenter in RxJava2

class Presenter {
    private PublishSubject<String> queryPublishSubject = PublishSubject.create();

    public Presenter() {
        queryPublishSubject
                .debounce(1000, TimeUnit.MILLISECONDS)
                // You might want to skip empty strings
                .filter(new Predicate<CharSequence>() {
                    @Override
                    public boolean test(CharSequence charSequence) {
                        return charSequence.length() > 0;
                    }
                })
                // Switch to IO thread for network call and flatMap text input to API request
                .observeOn(Schedulers.io())
                .flatMap(new Function<CharSequence, Observable<...>() {
                    @Override
                    public Observable<...> apply(final CharSequence charSequence) {
                        return ...; // Call API
                    }
                })
                // Receive and process response on Main thread (if you need to update UI)
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(...);
    }

    public void onSearchTextChanged(String query) {
        queryPublishSubject.onNext(query);
    }
}

Edit 1:

The same code in RxJava 1:

class Presenter {
    private PublishSubject<String> queryPublishSubject = PublishSubject.crate();

    public Presenter() {
        queryPublishSubject
            .debounce(1000, TimeUnit.MILLISECONDS)
            // You might want to skip empty strings
            .filter(new Func1<CharSequence, Boolean>() {
                @Override
                public Boolean call(CharSequence charSequence) {
                    return charSequence.length() > 0;
                }
            })
            // Switch to IO thread for network call and flatMap text input to API request
            .observeOn(Schedulers.io())
            .flatMap(new Func1<CharSequence, Observable<...>() {
                @Override
                public Observable<...> call(final CharSequence charSequence) {
                    return ... // Call API
                }
            })
            // Receive and process response on Main thread (if you need to update UI)
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(...);
    }

    public void onSearchTextChanged(String query) {
        queryPublishSubject.onNext(query);
    } 
}  

Initial answer (with RxBinding and RxJava 1)

The correct answer is to use Debounce, but besides that there are some other tricks you might find useful

textChangeListener = RxTextView
    .textChanges(queryEditText)
    // as far as I know, subscription to textChanges is allowed from Main thread only
    .subscribeOn(AndroidSchedulers.mainThread()) 
    // On subscription Observable emits current text field value. You might not need that
    .skip(1) 
    .debounce(1000, TimeUnit.MILLISECONDS)
    // You might want to skip empty strings
    .filter(new Func1<CharSequence, Boolean>() {
        @Override
        public Boolean call(CharSequence charSequence) {
            return charSequence.length() > 0;
        }
    })
    // Switch to IO thread for network call and flatMap text input to API request
    .observeOn(Schedulers.io())
    .flatMap(new Func1<CharSequence, Observable<...>() {
        @Override
        public Observable<...> call(final CharSequence charSequence) {
            return ... // Call API
        }
    })
    // Receive and process response on Main thread (if you need to update UI)
    .observeOn(AndroidSchedulers.mainThread())
like image 195
ULazdins Avatar answered Sep 24 '22 10:09

ULazdins


I have something similar for an address research combining with RxAndroid could give something like that :

 RxTextView.textChanges(searchEditText)
                        .debounce(100, TimeUnit.MILLISECONDS)
                        .subscribe(....);

The debounce operator will wait in this case that the observable stop to emit for 100ms before emitting the next value.

like image 29
agonist_ Avatar answered Sep 22 '22 10:09

agonist_