Question

I've got to set an autocompletion for my application.

I've already understood the AutoCompleteTextView operation, but I'd like to dynamically modify the String[] used by android autocompletion.

What I wanna do : call a PHP page which will give me a String[] that I'll use in my AutoCompleteTextView, but i wanna do that ONLY if a key was pressed at least 500 milliseconds after the last one.


EDIT :

Okay, I was wrong. I want to have my asynctask running if NO KEY is pressed in the 500 milliseconds after the last press (you know, avoiding overcharging our servers by calling a request on every character typed in.)


Here is how I think I'll do :

zipBox.setAdapter(myAdapter); //zipBox is an AutoCompleteTextView
zipBox.addTextChangedListener(new TextWatcher(){

    @Override
    public void onTextChanged(CharSequence s,
             int start, int before, int count){

        d = new Date();
        GetAutocompletion ac = new GetAutocompletion();
        ac.execute(zipBox.getText().toString());
    }
    // Other methods to implement
});


private class GetAutocompletion extends AsyncTask<String, String, String>{

    @Override
    protected String doInBackground(String... params){

        //adapter.notifyDataSetChanged();

        try{
             wait(500);
        } catch(InterruptedException e){}

        Date d1 = new Date();

        if(d1.getTime() - d.getTime() > 500){
            String res = "";

            try{

                URLConnection conn = new URL("myUrl.php?term=" + params[0]).openConnection();
                InputStream in = conn.getInputStream();
                Scanner s = new Scanner(in).useDelimiter("\\A");

                while (s.hasNext())
                    res += s.next();

                s.close();
                in.close();

            } catch(Exception e){ Log.d("eduine", "error during autocompletion receive"); }

            return json;
        } else return null;
    }

    @Override
    protected void onPostExecute(String result){

        super.onPostExecute(result);

        Log.d("eduine", "json result : " + result);
    }

}

What do you think? Is there anyway I could use Timer class?

Was it helpful?

Solution

I'd keep a long named lastPress as a field on my TextWatcher. When you press a key, set lastPress = System.currentTimeMillis(). Then just wrap your entire onTextChanged in a if with condition if(System.currentTimeMillis() - lastPress>500) and set lastPress again in that if.


new TextWatcher(){
    long lastPress = 0l;
    @Override
    public void onTextChanged(CharSequence s,
             int start, int before, int count){
        if(System.currentTimeMillis() - lastpress > 500){
            lastPress= System.currentTimeMillis();
            GetAutocompletion ac = new GetAutocompletion();
            ac.execute(zipBox.getText().toString());
        }
    }
    // Other methods to implement
}

OTHER TIPS

Can't comment on the answer selected as correct so here it goes. This solution will only work if you press a key after the specified delay has elapsed. If you write very fast and then stop no request will be sent to the server.

You can make that happen using a Handler and sending messages delayed:

public void onTextChanged(CharSequence s, int start, int before,
                           int count) {

    final String term = s.toString();
    if( term.length() > 3 ){
        autocompleteHandler.removeMessages(0);
        autocompleteHandler.postDelayed(new Runnable(){
            @Override
            public void run() {
            new SearchLocationAsyncTask(context, term).execute();
        }}, 350);
    }
}

Where SearchLocationAsynTask calls your server and updates the view accordingly.

Basically what this does is add a new message to the handler everytime the text changes, if there are any messages in the queue they get deleted and a new one is added to execute in 350ms.

It can be easily done using handler. Just need to remove all runnable before API calls. Below code is for same

/**
* Created by Shahbaz Hashmi on 12/01/19.
*/
public class AutoCompleteAdapter extends ArrayAdapter<Place> implements Filterable {

private static final String TAG = AutoCompleteAdapter.class.getSimpleName();

private static final int DELAY = 800; // 0.8 seconds delay

private Handler handler;

private Runnable runnable;

private ArrayList<Place> data;
private Context context;
private Location location;
private CompositeDisposable mCompositeDisposable;

public AutoCompleteAdapter(Context context, int resource, Location location) {
    super(context, resource);
    this.context = context;
    this.data = new ArrayList<>();
    this.location = location;
    handler = new Handler();
    mCompositeDisposable = new CompositeDisposable();
}

@Override
public int getCount() {
    return data.size();
}

@Nullable
@Override
public Place getItem(int position) {
    return data.get(position);
}

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

                handler.removeCallbacksAndMessages(null);

                mCompositeDisposable.clear();

                handler.postDelayed(runnable , DELAY);

                runnable = new Runnable() {
                    @Override
                    public void run() {
                        ///

                        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) {
                                        LogHelper.d(TAG, "got data form prediction API");
                                        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();

                        ///
                    }
                };

            }else{
                if(data.size()>0) data.clear();
            }
            return results;
        }

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


@Override
public View getView(int position, View convertView, ViewGroup parent) {
    Place place = getItem(position);
    if (convertView == null) {
        convertView = LayoutInflater.from(getContext()).inflate(R.layout.row_autocomplete, parent, false);
    }

    TextView primaryText = (TextView) convertView.findViewById(R.id.primary_text);
    TextView secondaryText = (TextView) convertView.findViewById(R.id.secondary_text);

    primaryText.setText(place.getStructuredFormatting().getMainText());
    secondaryText.setText(place.getStructuredFormatting().getSecondaryText());

    return convertView;
  }
}

I have got the same problem. The data comes but the autocompleteTextView doesn't update. The reason why the data is not updated is because the adapter didn't inform that the dataset has been changed.

 //use this at the initialization part for the autocompleteTextView
 autocompleteTextView.setThreshold(1);
 myAdapter.setNotifyOnChange(true);
 autocompleteTextView.setAdapter(myAdapter);

 // use this when you get new dataset and will show on autocompleteTextView
 myAdapter.notifyDataSetChanged();

I might be late at party, but I have implemented similar scenario, where if user enters text, the listener will not perform any operation until user stops typing. Once user stop typing, a module of code will get execute. But if user enters text again, even one character before specified time elapsed, the time set will refresh.

Below is the method I used, I used RXBinding for it, its simple.

Dependency :::

implementation 'com.jakewharton.rxbinding:rxbinding:0.4.0'

Code to Do text watching :::

RxTextView.textChanges(mEdtNo1)
      .debounce(TIME_DELAY, TimeUnit.MILLISECONDS)
      .observeOn(AndroidSchedulers.mainThread())
      .subscribe(
        charSequence -> {
            //YOUR CODE WHICH NEED TO BE PERFORMED AFTER USER DONE TYPING WILL BE HERE...
        }
      );

in above code,

  1. mEdtNo1 is editText view which we are observing.
  2. TIME_DELAY is miliseconds (i.e. 5000 or 4000 [5 or 4 sec]) which will act as waiting time, this waiting time will refresh if user typed any character in editText.
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top