How to Implement Auto complete In Android with Remote Server Data

Implementing auto complete on your android app may seem easy with the AutoCompleteTextView documentation using local data, but it becomes relatively difficult when your data source is on a remote server.

I recently experienced this problem while working on a mobile application for my client. The Courier Mobile Solution I developed keeps track of items from pickup to delivery between the dispatch rider and the administrators at the office. When a dispatch needs to initiate a pickup, he will select from a list of about 1000 Shippers, which is ridiculous to present with a drop-down option. So we agreed to do auto-complete as a dispatcher starts to type the name of the Shipper the text view should suggest the related shippers from the database.

A Google search landed me on a Stackoverflow answer which showed me how to use a custom adapter with AutoCompleteTextView, but without how to do remote connection inside the adapter.

In this tutorial, I will walk you through using the AutoCompleteTextView with a custom ArrayAdapter and AsyncTask to get your data.

First you need to have AutoCompleteTextView in your layout file

<AutoCompleteTextView
    android:layout_width="0dp"
    android:layout_height="wrap_content"
    android:layout_weight="0.90"
    android:id="@+id/shipper"/>

Now we will create a custom ArrayAdapter for our AutoCompleteTextView.

public class AutoCompleteAdapter extends ArrayAdapter<String> implements Filterable {

    ArrayList<String> shippers;

    public AutoCompleteAdapter(Context context, int textViewResourceId){
        super(context, textViewResourceId);
        shippers = new ArrayList<String>();


    }

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

    @Override
    public String getItem(int index){
        return shippers.get(index);
    }


    @Override
    public Filter getFilter(){

        Filter myFilter = new Filter(){

            @Override
            protected FilterResults performFiltering(CharSequence constraint){
                FilterResults filterResults = new FilterResults();
                if(constraint != null) {
                    // A class that queries a web API, parses the data and returns an ArrayList<Style>
//
                    try {

                        shippers = new DownloadShippers().execute(new String[]{constraint.toString()}).get();
                    }
                    catch(Exception e) {
//                        Log.e("myException", e.getMessage());
                    }
                    // Now assign the values and count to the FilterResults object
                    filterResults.values = shippers;
                    filterResults.count = shippers.size();
                }
                return filterResults;
            }

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

        };

        return myFilter;

    }


    private class DownloadShippers extends AsyncTask<String, Void, ArrayList<String>>{

        @Override
        protected ArrayList<String> doInBackground(String... constraint) {
            Client client = new Client();
            ArrayList<String> shippersNames = new ArrayList<String>();


                try {

                    ArrayList<Shipper> shippers = client.downloadShippers(constraint[0]);

                    for(Shipper ship: shippers){
                        shippersNames.add(ship.getName());
                    }

                } catch (Exception e) {
                    e.printStackTrace();
                }

            return shippersNames;
        }

        @Override
        protected void onPostExecute(ArrayList<String> result) {

        }

    }



}

If you are familiar with custom adapters on Android, you probably do not need an explanation on what getCount and getItem methods do. Notice the method getFilter, which returns a filter object which is responsible for filtering results and also responsible for publishing filtered results. In performFiltering method, you will be supplied with a charsequence object which represents what the user types into the AutoCompleteTextView and you will use it to send a request to that database.

We need to get data from the database. What I did was to create a custom AsyncTask class as an inner class to the custom ArrayAdapter. If you look carefully in the code above, you will see the asyntask class but here it is again below:

private class DownloadShippers extends AsyncTask<String, Void, ArrayList<String>>{

    @Override
    protected ArrayList<String> doInBackground(String... constraint) {
        Client client = new Client();
        ArrayList<String> shippersNames = new ArrayList<String>();


            try {

                ArrayList<Shipper> shippers = client.downloadShippers(constraint[0]);

                for(Shipper ship: shippers){
                    shippersNames.add(ship.getName());
                }

            } catch (Exception e) {
                e.printStackTrace();
            }

        return shippersNames;
    }

    @Override
    protected void onPostExecute(ArrayList<String> result) {

    }

}

Below here is the trick:

shippers = new DownloadShippers().execute(new String[]{constraint.toString()}).get();

What the code above does is call the DownloadShippers async task with the letters given to it by the autocompletetextview as a parameter but the tricky part here is the get(). Calling AsyncTask with get freezes the current thread until the AsyncTask is done and results returned before continuing to the next line that way performFiltering can have result to display in your ui thread to the user.

Nothing will work yet because you have not told the AutoCompleteTextView about the custom ArrayAdapter. You will now need to go to your activity and do the following below:

shipper = (AutoCompleteTextView) findViewById(R.id.shipper);
shipperAdapter = new AutoCompleteAdapter(this, android.R.layout.simple_dropdown_item_1line);
shipper.setAdapter(shipperAdapter);

What I did above is to tell the AutoCompleteTextView to use our AutoCompleteAdapter as its adapter.

Once that is done and everything else works fine, you are good to go. If you come across any problem, you can contact me on twitter @ridwan_olalere.

Leave a comment