Search code examples
androidretrofitrx-javarx-android

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.


Solution

  • 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())