Reputation: 3692
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
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
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);
}
})
Upvotes: 2