How to return FilterResults in AutoCompleteTextVie

2019-09-19 07:26发布

问题:

I am coding an autocompleteTextView adapter which is filled by an ApiRest. I'm using rxJava and Retrofit 2. but cannot get the filtered result because i dont know how to return the value in asynchronous function. here is my code

public class DiagnosticoAutoCompleteAdapter extends BaseAdapter implements Filterable {
...
@Override
public Filter getFilter() {
    Filter filter = new Filter() {
        @Override
        protected FilterResults performFiltering(CharSequence charSequence) {

            final FilterResults filterResults= new FilterResults();
            if(charSequence!=null) {
                ApiUtils.getAPIServiceDos()
                        .getDiagnosticos(charSequence.toString())
                        .subscribeOn(Schedulers.io())
                        .observeOn(AndroidSchedulers.mainThread())
                        .subscribe(new Observer<List<Diagnostico>>() {
                            @Override
                            public void onSubscribe(Disposable d) {

                            }

                            @Override
                            public void onNext(List<Diagnostico> value) {
// HERE IT SHOWS ME THE SIZE E.G. 45 so The values are received correctly
                                System.out.println("tamaño diagnostico::"+value.size());
// HERE IT SHOWS ME THE OBJECT NAME IN POSITION 0 AND ITS OK                                   
 System.out.println("contenido diagg...."+value.get(0).getNombre());
                                filterResults.values=value;
                                filterResults.count=value.size();
                            }

                            @Override
                            public void onError(Throwable e) {

                            }

                            @Override
                            public void onComplete() {

                            }
                        });
            }


//here i lost the information. the count is 0
            System.out.println("tamaño de filtered results::"+filterResults.count);
            return filterResults;
        }

The RxJava request works OK but cannot return the values.

My question is how to return the filtered results??

回答1:

The problem is that you're performing asynchronous operation where the API expected synchronous one, meaning you're launching a the async operation, while returning immediately the empty FilterResults, the async operation didn't sits and wait to the result before returning it back inside the performFiltering method, thus the result from the async operation (inside the Observer.onNext) is changed after you've already return empty FilterResults.

Filter object is abstract class that already do the heavy lifting of doing the actual work on background thread, performFiltering method invoked in a worker thread, and you should handle the results in the UI thread at publishResults method.

This API is not exactly fits to RxJava reactive model, you can invoke the server call directly at performFiltering and return the result as it's invoked in a worker thread.
If you want RxJava, you can do it by converting the stream to a blocking one and returns the result:

 @Override
 protected FilterResults performFiltering(CharSequence charSequence) {

     final FilterResults filterResults = new FilterResults();
     if (charSequence != null) {
         List<Diagnostico> value = ApiUtils.getAPIServiceDos()
                 .getDiagnosticos(charSequence.toString())
                 .toBlocking()
                 .first();
         System.out.println("tamaño diagnostico::" + value.size());
         System.out.println("contenido diagg...." + value.get(0).getNombre());
         filterResults.values = value;
         filterResults.count = value.size();
     }
     return filterResults;
 }

EDIT: Why you should not fire the async request at publishResults:

performing the request at publishResults will work as it's designed for you to update the UI after you have the results (probably notify the adapter), so after you perform the request in bg you return to main thread and update the adapter.

BUT, it will introduce some bugs, as you're not bound to the correct lifecycle of filtering, that means that the progress indication will disappear immediately, and will not shown while you actually fetch the result in the background, additionally, more sever problem, it will cause bugs when multiple requests are fired, as earlier request might arrive after later one and update the adapter with old data. that's why you need to obey the filter API and perform the request in blocking fashion at the performFiltering.



回答2:

You need to just call notifyDataSetChanged(). It will update the result. Don't forget to initialise values and count.

@NonNull
@Override
public Filter getFilter() {
    return new Filter() {
        @Override
        protected FilterResults performFiltering(CharSequence constraint) {
            final FilterResults results = new FilterResults();
            if (constraint != null) {

                mCompositeDisposable.add(NetworkClient.getRetrofit().create(NetworkInterface.class)
                .getPredictions(MapHelper.makeAutocompleteURL((BaseActivity) context, location, constraint.toString(), Config.SEARCH_RADIUS * 1000))
                        .subscribeOn(Schedulers.io())
                        .observeOn(AndroidSchedulers.mainThread()).subscribeWith(new DisposableObserver<PlaceSerializer>(){
                            @Override
                            public void onNext(PlaceSerializer placeSerializer) {
                                data = new ArrayList<Place>(placeSerializer.getPlaces());
                                results.values = data;
                                results.count = data.size();
                                notifyDataSetChanged();
                            }

                            @Override
                            public void onError(Throwable e) {
                                LogHelper.e(TAG, e.getMessage());
                            }

                            @Override
                            public void onComplete() {

                            }
                        }));

                results.values = data;
                results.count = data.size();
            }
            return results;
        }

        @Override
        protected void publishResults(CharSequence constraint, FilterResults results) {
            if (results != null && results.count > 0) {
                notifyDataSetChanged();
            } else notifyDataSetInvalidated();
        }
    };
}