10

I've been having difficulty creating a custom ArrayAdapter for AutoCompleteTextView such errors that would come up despite following code found on the internet would be:

  • Dropdown would not appear.
  • Custom Objects and their details would not appear.

So for those who are having or had the same problem as me, I recommend using BaseAdapter for AutoCompleteTextView instead.

BNK
  • 23,994
  • 8
  • 77
  • 87
Hiroga Katageri
  • 1,345
  • 3
  • 21
  • 40
  • How did you get data from web service? By httpurlconnection or okhttp or volley...? Some of them are async. – BNK Oct 09 '15 at 21:14
  • I use OkHttp. I create a handler and a runnable, the handler is created from Looper.getMainLooper(); I initiate it in onCreate() of the Activity. – Hiroga Katageri Oct 09 '15 at 21:43

2 Answers2

52

The following is my working code using ArrayAdapter.

Let's assume the reponse data from web service looks like the following:

[
    {
        "id": "1",
        "name": "Information Technology"
    },
    {
        "id": "2",
        "name": "Human Resources"
    },
    {
        "id": "3",
        "name": "Marketing and PR"
    },
    {
        "id": "4",
        "name": "Research and Developement"
    }
]

Then in your Android client:

Department class:

public class Department {
    public int id;
    public String name;
}

Custom Adapter class:

public class DepartmentArrayAdapter extends ArrayAdapter<Department> {
    private final Context mContext;
    private final List<Department> mDepartments;
    private final List<Department> mDepartmentsAll;
    private final int mLayoutResourceId;

    public DepartmentArrayAdapter(Context context, int resource, List<Department> departments) {
        super(context, resource, departments);
        this.mContext = context;
        this.mLayoutResourceId = resource;
        this.mDepartments = new ArrayList<>(departments);
        this.mDepartmentsAll = new ArrayList<>(departments);
    }

    public int getCount() {
        return mDepartments.size();
    }

    public Department getItem(int position) {
        return mDepartments.get(position);
    }

    public long getItemId(int position) {
        return position;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        try {
            if (convertView == null) {                    
                LayoutInflater inflater = ((Activity) mContext).getLayoutInflater();
                convertView = inflater.inflate(mLayoutResourceId, parent, false);
            }
            Department department = getItem(position);
            TextView name = (TextView) convertView.findViewById(R.id.textView);
            name.setText(department.name);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return convertView;
    }

    @Override
    public Filter getFilter() {
        return new Filter() {
            @Override
            public String convertResultToString(Object resultValue) {
                return ((Department) resultValue).name;
            }

            @Override
            protected FilterResults performFiltering(CharSequence constraint) {
                FilterResults filterResults = new FilterResults();
                List<Department> departmentsSuggestion = new ArrayList<>();
                if (constraint != null) {
                    for (Department department : mDepartmentsAll) {
                        if (department.name.toLowerCase().startsWith(constraint.toString().toLowerCase())) {
                            departmentsSuggestion.add(department);
                        }
                    }
                    filterResults.values = departmentsSuggestion;
                    filterResults.count = departmentsSuggestion.size();
                }
                return filterResults;
            }

            @Override
            protected void publishResults(CharSequence constraint, FilterResults results) {
                mDepartments.clear();
                if (results != null && results.count > 0) {
                    // avoids unchecked cast warning when using mDepartments.addAll((ArrayList<Department>) results.values);
                    for (Object object : (List<?>) results.values) {
                        if (object instanceof Department) {
                            mDepartments.add((Department) object);
                        }
                    }
                    notifyDataSetChanged();
                } else if (constraint == null) {
                    // no filter, add entire original list back in
                    mDepartments.addAll(mDepartmentsAll);
                    notifyDataSetInvalidated();
                }
            }
        };
    }
}

Main Activity:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    mAutoCompleteTextView = (AutoCompleteTextView) findViewById(R.id.autoCompleteTextView);
    mAutoCompleteTextView.setThreshold(1);

    new DepartmentRequest().execute();
}

private class DepartmentRequest extends AsyncTask<Void, Void, JSONArray> {
        @Override
        protected JSONArray doInBackground(Void... voids) {
            OkHttpJsonArrayRequest request = new OkHttpJsonArrayRequest();
            try {
                return request.get("http://...");
            } catch (IOException | JSONException e) {
                e.printStackTrace();
            }
            return null;
        }

        @Override
        protected void onPostExecute(JSONArray jsonArray) {
            super.onPostExecute(jsonArray);
            if (jsonArray != null && jsonArray.length() > 0) {
                Gson gson = new Gson();
                Department[] departments = gson.fromJson(jsonArray.toString(), Department[].class);
                mDepartmentList = Arrays.asList(departments);
                mDepartmentArrayAdapter = new DepartmentArrayAdapter(mContext, R.layout.simple_text_view, mDepartmentList);
                mAutoCompleteTextView.setAdapter(mDepartmentArrayAdapter);
            }
        }
    }

    private class OkHttpJsonArrayRequest {
        OkHttpClient client = new OkHttpClient();
        // HTTP GET REQUEST
        JSONArray get(String url) throws IOException, JSONException {
            Request request = new Request.Builder()
                    .url(url)
                    .build();
            Response response = client.newCall(request).execute();
            return new JSONArray(response.body().string());
        }
    }

Here's the screenshot:

BNK's screenshot

Hope this helps!

Jing Li
  • 14,547
  • 7
  • 57
  • 69
BNK
  • 23,994
  • 8
  • 77
  • 87
  • @BNK, was trying to implement this in multiAutoCompleteTextView. In your example, there are four departments. What if user types in a department other than those four? Can we stop user from entering a department other than those four? – mrtpk Sep 01 '16 at 12:10
  • 1
    @mr_tkp if user types in a department other than those four, nothing displays – BNK Sep 02 '16 at 09:37
  • @BNK Thanks for replying. nothing displays in dropdown? I want to make sure that user cannot enter other than those four departments. – mrtpk Sep 02 '16 at 09:50
  • @mr_tkp I think you should choose something like the spinner instead :) – BNK Sep 02 '16 at 14:29
  • 1
    2 hours looking for a good example. And it is complete and the unique that works for me. Thank you! – JCarlosR May 05 '17 at 22:35
  • This is not working. I waste my 3 hours doing this. But it's useless code. – Sandeep Londhe Jun 28 '20 at 15:32
  • 1
    A complete answer. Works like a charm in the first run. Khudos @BNK – PravyNandas Aug 06 '20 at 04:05
  • 1
    Hello @BNK you are a real life saver, if stack overflow would have allowed transferring of reputation i would have definitely done that – Emmanuel Njorodongo Feb 26 '21 at 11:47
24

Custom BaseAdapter Class

public class ObjectAdapter extends BaseAdapter implements Filterable {

    private Context context;
    private ArrayList<Object> originalList;
    private ArrayList<Object> suggestions = new ArrayList<>();
    private Filter filter = new CustomFilter();

    /**
     * @param context      Context
     * @param originalList Original list used to compare in constraints.
     */
    public ObjectAdapter(Context context, ArrayList<Object> originalList) {
        this.context = context;
        this.originalList = originalList;
    }

    @Override
    public int getCount() {
        return suggestions.size(); // Return the size of the suggestions list.
    }

    @Override
    public Object getItem(int position) {
        return suggestions.get(position).getCountryName();
    }


    @Override
    public long getItemId(int position) {
        return 0;
    }

    /**
     * This is where you inflate the layout and also where you set what you want to display.
     * Here we also implement a View Holder in order to recycle the views.
     */
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        LayoutInflater inflater = LayoutInflater.from(context);

        ViewHolder holder;

        if (convertView == null) {
            convertView = inflater.inflate(R.layout.adapter_autotext,
                    parent,
                    false);
            holder = new ViewHolder();
            holder.autoText = (TextView) convertView.findViewById(R.id.autoText);
            convertView.setTag(holder);
        } else {
            holder = (ViewHolder) convertView.getTag();
        }

        holder.autoText.setText(suggestions.get(position).getCountryName());

        return convertView;
    }


    @Override
    public Filter getFilter() {
        return filter;
    }

    private static class ViewHolder {
        TextView autoText;
    }

    /**
     * Our Custom Filter Class.
     */
    private class CustomFilter extends Filter {
        @Override
        protected FilterResults performFiltering(CharSequence constraint) {
            suggestions.clear();

            if (originalList != null && constraint != null) { // Check if the Original List and Constraint aren't null.
                for (int i = 0; i < originalList.size(); i++) {
                    if (originalList.get(i).getCountryName().toLowerCase().contains(constraint)) { // Compare item in original list if it contains constraints.
                        suggestions.add(originalList.get(i)); // If TRUE add item in Suggestions.
                    }
                }
            }
            FilterResults results = new FilterResults(); // Create new Filter Results and return this to publishResults;
            results.values = suggestions;
            results.count = suggestions.size();

            return results;
        }

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

Main Activity Class

public class MainActivity extends AppCompatActivity{

    private SGetCountryListAdapter countryAdapter;
    private ArrayList<SGetCountryList> countryList;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        country = (AutoCompleteTextView) findViewById(R.id.country);
        countryAdapter = new SGetCountryListAdapter(getApplicationContext(),
                    ConnectionParser.SGetCountryList);

        country.setAdapter(countryAdapter);
        country.setThreshold(1);

    }

}

Drop down layout

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <TextView
        android:id="@+id/autoText"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginBottom="8dp"
        android:layout_marginEnd="16dp"
        android:layout_marginLeft="16dp"
        android:layout_marginRight="16dp"
        android:layout_marginStart="16dp"
        android:layout_marginTop="8dp"
        android:textColor="@color/black" />

</LinearLayout>

My Original List has data taken from web service so let's just assume that it already has data. Of course you can customize the dropdown even more by adding more views, just don't forget to update the adapter in order to incorporate the new views.

Hiroga Katageri
  • 1,345
  • 3
  • 21
  • 40
  • Good answer. But it doesn't select item from DD & set to AutoCompleteTextView. Can you please provide solution for this? – VVB Jun 16 '16 at 04:38
  • yup i cant select from dropdown any1 find solution? – Jayman Jani Jul 02 '16 at 07:43
  • 1
    However i have find anotherway i have maintain # holder.autoText.SetOnClick and then apply logic on it and it work perfectly – Jayman Jani Jul 02 '16 at 09:26
  • 1
    where did you get SGetCountryListAdapter? – rmpt Sep 26 '16 at 21:27
  • SGetCountryListAdapter is the name of my custom class in one of my project it's the same as ObjectAdapter. – Hiroga Katageri Sep 27 '16 at 23:34
  • When I use this code as a template, the method "performFiltering" is called a second time after publishResults() calls notifyDataSetChanged(). Is that normal? Why would it force the filtering twice? – John Ward Dec 31 '16 at 21:50
  • 1
    Only thing I would add here is that getItem(int position) should not return null. Instead, you should return suggestions.get(position).getCountryName() - this will be the value you want to see populated in the AutoCompleteTextView when you click on the item in the list. – Will Kung Jan 09 '17 at 23:15
  • very good solution! I tested with a 4000 element array and it is really fast! thank you – Giulio Pettenuzzo Mar 08 '18 at 14:34