Amit Tiwari
Amit Tiwari

Reputation: 3692

Search api using Retrofit

I need to implement searching functionality in my android app. The API is not a proper search API and it is like this

https://example.com/api/list/photos/?start=0&end=30&search=something

So, basically, I have to query the backend for each letter of search query and the endpoint is paginated so that I can control how many entries to display.

I read some code that uses RxJava to add a time limit constraint for searching so I can at least minimise the number of calls to backend. But the problem that I face is, if I type 's' a request goes and if I type 'so', then another request goes(assuming I am typing with delays), so it just might be the case that I get the result for 'so' before 's' for some reason and then this whole search will be broken. Also, if the request has been made, the response will come at some point and I will need to change the UI according that response even if that is not the ultimate query that I wanted to search.

I am using Retrofit 1.9 and whenever I get the response, I update the recyclerView according to the results. Is there a way to cancel Retrofit requests based on the above situation? What would be the best design or implementation for making this thing work correctly? Any help would be highly appreciated.

Edit : Here is what I have written myself. Please let me know if it can be improved in any way.

Subscription subscription = RxTextView.textChangeEvents(inputSearch)
                .debounce(400, TimeUnit.MILLISECONDS)// default Scheduler is Computation
                .filter(new Func1<TextViewTextChangeEvent, Boolean>() {
                    @Override
                    public Boolean call(TextViewTextChangeEvent changes) {
                        return !TextUtils.isEmpty(inputSearch.getText().toString());
                    }
                })
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(getSearchObserver());


private void getSearchResults(String s) {
        LogUtil.i(TAG, "getSearchResults called");
        indicator.setVisibility(View.VISIBLE);
        DiscoverAPI discoverAPI = restAdapter.create(DiscoverAPI.class);
        discoverAPI.getSearchResults(0, 10, s, new Callback<List<Result>>() {
            @Override
            public void success(List<Result> results, Response response) {
                LogUtil.i(TAG, "discover api successful");
                list.clear();
                if (adapter != null) {
                    adapter.notifyDataSetInvalidated();
                }
                list.addAll(results);
                if (adapter == null) {
                    adapter = new SearchGalleryAdapter(TestActivity.this, list);
                    listView.setAdapter(adapter);
                } else {
                    adapter.notifyDataSetChanged();
                }
                indicator.setVisibility(View.GONE);
            }

            @Override
            public void failure(RetrofitError error) {
                LogUtil.i(TAG, "discover api failed");
            }
        });
    }

Upvotes: 4

Views: 2956

Answers (2)

Amit Tiwari
Amit Tiwari

Reputation: 3692

Finally, I figured out the solution, so posting here for reference.

RxTextView.textChangeEvents(inputSearch)
                .debounce(400, TimeUnit.MILLISECONDS)
                .map(new Func1<TextViewTextChangeEvent, String>() {
                    @Override
                    public String call(TextViewTextChangeEvent textViewTextChangeEvent) {
                        return  textViewTextChangeEvent.text().toString();
                    }
                })
                .switchMap(new Func1<String, Observable<List<Result>>>() {
                    @Override
                    public Observable<List<Result>> call(String s) {
                        DiscoverAPI discoverAPI = restAdapter.create(DiscoverAPI.class);
                        return discoverAPI.getRxSearchResult(0, 9, s);
                    }
                })
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(getRxSearchObserver());


private Observer<List<Result>> getRxSearchObserver() {
        return new Observer<List<Result>>() {
            @Override
            public void onCompleted() {
                LogUtil.i(TAG, "onCompleted called");
            }

            @Override
            public void onError(Throwable e) {
                LogUtil.i(TAG, "onError called");
            }

            @Override
            public void onNext(List<Result> results) {
                LogUtil.i(TAG, "onNext called");
                list.clear();
                if (adapter != null) {
                    adapter.notifyDataSetInvalidated();
                }
                list.addAll(results);
                if (adapter == null) {
                    adapter = new SearchGalleryAdapter(TestActivity.this, list);
                    listView.setAdapter(adapter);
                } else {
                    adapter.notifyDataSetChanged();
                }
                indicator.setVisibility(View.GONE);
            }
        };
    }

Hope it helps.

Upvotes: 2

krp
krp

Reputation: 2247

you may try something like this :

 RxTextView.afterTextChangeEvents(mTextView)
        .map(new Func1<TextViewAfterTextChangeEvent, String>() {
            @Override
            public String call(TextViewAfterTextChangeEvent textViewAfterTextChangeEvent) {
                return textViewAfterTextChangeEvent.editable().toString();
                }
        })
        .switchMap(new Func1<String, Observable<SearchResponse>>() {
            @Override
            public Observable<SearchResponse> call(String query) {
                return mApiService.search(query);
            }
        })
  • afterTextChangeEvents creates a stream of text changed events on your TextView
  • map maps TextViewAfterTextChangeEvent to String that has been typed
  • switchMap works almost like flatMap, so it maps your event to another Stream of events (Api request in this case), however, after another event is emitted, it unsubscribes from previous stream (so previous Api request result will be ignored)

Upvotes: 2

Related Questions