Ali Ali - 3 months ago 22
Android Question

Contacts with thumbnails(photos) in AutoCompleteTextView

What I want to achieve is straight forward. When I type it should list the contacts from my phone along with their thumbnail (photo). At first I was able to make the app only list the contact names. But after adding the below codes autocomplete doesnt work. There are no errors.

Here are some code:

This is how i set my adapter

AsyncTask.execute(new Runnable() {
@Override
public void run() {
adapter = new ContactsAdapter(getApplicationContext(),
getAllContactNamesAndThumbs());
mNameEditText.setAdapter(adapter);
}
});


The getAllContactNamesAndThumbs method:

private List<Map<String, Object>> getAllContactNamesAndThumbs() {
List<Map<String, Object>> namesAndThumbs;

// Check the SDK version and whether the permission is already granted or not.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && checkSelfPermission(Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) {
requestPermissions(new String[]{Manifest.permission.READ_CONTACTS}, PERMISSIONS_REQUEST_READ_CONTACTS);
namesAndThumbs = new ArrayList<Map<String, Object>>();
//After this point you wait for callback in onRequestPermissionsResult(int, String[], int[]) overriden method
} else {
//The permissions are granted so Get all contacts
namesAndThumbs = new ArrayList<Map<String, Object>>();
try {
Cursor contactCursor = getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
new String[]{ContactsContract.CommonDataKinds.Phone.CONTACT_ID,
ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME,
ContactsContract.CommonDataKinds.Phone.NUMBER},
null, null, ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME + " ASC");

if (contactCursor != null) {

while (contactCursor.moveToNext()) {

long id = contactCursor.getLong(contactCursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.CONTACT_ID));

InputStream inputStream = openThumbnail(id);
Bitmap thumbnail;

if (inputStream != null) {
thumbnail = BitmapFactory.decodeStream(inputStream);
} else {
thumbnail = BitmapFactory.decodeResource(getResources(), R.drawable.ic_person_black_24dp);
}

//Add contact name into the list
Map<String, Object> datum = new HashMap<String, Object>(2);
datum.put("name", contactCursor.getString(contactCursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME)));
datum.put("thumbnail", thumbnail);
namesAndThumbs.add(datum);
}

}
} catch (NullPointerException e) {
Log.e("ContactNamesAndThumbs", e.getMessage());
}
}
return namesAndThumbs;
}


I used an ArrayAdapter with the AutoCompleteTextView. Here is the code

public ContactsAdapter(Context context, List<Map<String, Object>> data) {
super(context, -1);
this.context = context;
this.data = data;
}


@Override
public View getView(int position, View convertView, ViewGroup parent) {

LayoutInflater inflater = (LayoutInflater)
context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

View rowView = inflater.inflate(R.layout.single_contact, parent, false);

TextView cName = (TextView) rowView.findViewById(R.id.tv_ContactName);
ImageView cThumb = (ImageView) rowView.findViewById(R.id.iv_contact_thumbnail);

cName.setText(data.get(position).get("name").toString());
cThumb.setImageBitmap((Bitmap) data.get(position).get("thumbnail"));

return rowView;

}

Ali Ali
Answer

Finally figured it out! I had to extend the ContactsAdapter form BaseAdapter instead of ArrayAdapter and implement the Filterable interface. And instead of setting the adapter using AsycTask I had to run it on the ui thread in the oncreate method of the activity.

Here is how i set my adapter in the oncreate without AsyncTask:

adapter = new ContactsAdapter(getApplicationContext(), getAllContactNamesAndThumbs());
autoCompleteTextView.setAdapter(adapter);

And the getAllContactNamesAndThumbs() method

private List<Map<String, Object>> getAllContactNamesAndThumbs() {
    List<Map<String, Object>> namesAndThumbs;

    // Check the SDK version and whether the permission is already granted or not.
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && checkSelfPermission(Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) {
        requestPermissions(new String[]{Manifest.permission.READ_CONTACTS}, PERMISSIONS_REQUEST_READ_CONTACTS);
        namesAndThumbs = new ArrayList<Map<String, Object>>();
        //After this point you wait for callback in onRequestPermissionsResult(int, String[], int[]) overriden method
    } else {
        //The permissions are granted so Get all contacts
        namesAndThumbs = new ArrayList<Map<String, Object>>();
        try {
            Cursor contactCursor = getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
                    new String[]{ContactsContract.CommonDataKinds.Phone.CONTACT_ID,
                            ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME,
                            ContactsContract.CommonDataKinds.Phone.NUMBER},
                    null, null, ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME + " ASC");

            if (contactCursor != null) {

                while (contactCursor.moveToNext()) {

                    long id = contactCursor.getLong(contactCursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.CONTACT_ID));

                    InputStream inputStream = openThumbnail(id);
                    Bitmap thumbnail;

                    if (inputStream != null) {
                        thumbnail = BitmapFactory.decodeStream(inputStream);
                    } else {
                        thumbnail = BitmapFactory.decodeResource(getResources(), R.drawable.ic_person_black_24dp);
                    }

                    //Add contact name into the list
                    Map<String, Object> datum = new HashMap<String, Object>(2);
                    datum.put("name", contactCursor.getString(contactCursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME)));
                    datum.put("thumbnail", thumbnail);
                    namesAndThumbs.add(datum);
                }
            }
        } catch (NullPointerException e) {
            Log.e("ContactNamesAndThumbs", e.getMessage());
        }
    }
    return namesAndThumbs;
}

my ContactsAdapter class:

public class ContactsAdapter extends BaseAdapter implements Filterable{

private Context context;
private List<Map<String, Object>> originalData;
private List<Map<String, Object>> suggestionData = new ArrayList<>();
private Filter filter = new CustomFilter();

public ContactsAdapter(Context context, List<Map<String, Object>> data) {
    super();
    this.context = context;
    this.originalData = data;
}

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

@Override
public Object getItem(int position) {
    return suggestionData.get(position).get("name").toString();
}

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

@Override
public View getView(int position, View convertView, ViewGroup parent) {

    ViewHolder holder;

    if (convertView == null) {

        LayoutInflater layoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        convertView = layoutInflater.inflate(R.layout.single_contact, parent, false);

        holder = new ViewHolder();
        holder.cName = (TextView) convertView.findViewById(R.id.tv_ContactName);
        holder.cThumb = (ImageView) convertView.findViewById(R.id.iv_contact_thumbnail);

        convertView.setTag(holder);
    } else {
        holder = (ViewHolder) convertView.getTag();
    }

    holder.cName.setText(suggestionData.get(position).get("name").toString());
    holder.cThumb.setImageBitmap((Bitmap) suggestionData.get(position).get("thumbnail"));

    return convertView;

}

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


private static class ViewHolder {
    TextView cName;
    ImageView cThumb;
}

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

        if (originalData != null && constraint != null) { // Check if the Original List and Constraint aren't null.
            for (int i = 0; i < originalData.size(); i++) {
                if (Pattern.compile(Pattern.quote(constraint.toString()),
                        Pattern.CASE_INSENSITIVE).matcher
                        (originalData.get(i).get("name").toString()).find()) { // Compare item in original list if it contains constraints.
                    suggestionData.add(originalData.get(i)); // If TRUE add item in Suggestions.
                }
            }
        }
        FilterResults results = new FilterResults(); // Create new Filter Results and return this to publishResults;
        results.values = suggestionData;
        results.count = suggestionData.size();

        return results;
    }

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

p.s

Even though this code works the way it should, there are a couple of hiccups. one is that if you have many contacts like a 1000 then its a big process so the app might hang for while and display I/Choreographer: Skipped xxx frames! The application may be doing too much work on its main thread.

The second is that if you type some name and hold on to the backspace key to erase fast then there will be an error saying:

java.lang.IllegalStateException: The content of the adapter has changed but ListView did not receive a notification. Make sure the content of your adapter is not modified from a background thread, but only from the UI thread. Make sure your adapter calls notifyDataSetChanged() when its content changes.

I still consider this an answer because my main problem was solved.

Comments