How load all the contacts with minimum time in Android-ThrowExceptions

Exception or error:

In my project getting contacts is taking a long time to load.

  • What are ways to reduce the time of getting contacts
  • Assume there are 1000 contacts in my phone.
  • Right now it is taking more than 2 minutes to load all the contacts

How can I reduce the time to load contacts ?
Any Thoughts?

I referred to the the following link when programming the initial method.

http://www.coderzheaven.com/2011/06/13/get-all-details-from-contacts-in-android/

How to solve:

Total time will depend upon what fields you are trying to access from the Contacts table.
Accessing less field means less looping , less processing and hence faster results.

Also to speed up your contacts fetch operation you can use the ContentProvideClient instead of calling query on ContentResolver every time. This will make you query the specific table rather than querying first for the required ContentProvider and then to table.

Create an instance of ContentProviderClient

ContentResolver cResolver=context.getContextResolver();
ContentProviderClient mCProviderClient = cResolver.acquireContentProviderClient(ContactsContract.Contacts.CONTENT_URI);

Then reuse this mCProviderClient to get Contacts(data from any ContentProvider) data on your call.
For example in following method, I am accessing only one field.

private ArrayList<String> fetchContactsCProviderClient()
    {
        ArrayList<String> mContactList = null;
        try
        {
            Cursor mCursor = mCProviderClient.query(ContactsContract.Contacts.CONTENT_URI, null, null, null, null);
            if (mCursor != null && mCursor.getCount() > 0)
            {
                mContactList = new ArrayList<String>();
                mCursor.moveToFirst();
                while (!mCursor.isLast())
                {
                    String displayName = mCursor.getString(mCursor.getColumnIndexOrThrow(ContactsContract.Contacts.DISPLAY_NAME));
                    mContactList.add(displayName);
                    mCursor.moveToNext();
                }
                if (mCursor.isLast())
                {
                    String displayName = mCursor.getString(mCursor.getColumnIndexOrThrow(ContactsContract.Contacts.DISPLAY_NAME));
                    mContactList.add(displayName);
                }
            }

            mCursor.close();
        }
        catch (RemoteException e)
        {
            e.printStackTrace();
            mContactList = null;
        }
        catch (Exception e)
        {
            e.printStackTrace();
            mContactList = null;
        }

        return mContactList;
    }

###

BETTER SOLUTION HERE…..

private static final String[] PROJECTION = new String[] {
        ContactsContract.CommonDataKinds.Phone.CONTACT_ID,
        ContactsContract.Contacts.DISPLAY_NAME,
        ContactsContract.CommonDataKinds.Phone.NUMBER
    };
.
.
.

ContentResolver cr = getContentResolver();
        Cursor cursor = cr.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, PROJECTION, null, null, null);
        if (cursor != null) {
            try {
                final int nameIndex = cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME);
                final int numberIndex = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER);

                String name, number;
                while (cursor.moveToNext()) {
                    name = cursor.getString(nameIndex);
                    number = cursor.getString(numberIndex);
                }
            } finally {
                cursor.close();
            }
        }

CHEERS…:)

###

Load Contact faster like other apps doing.

I have tested this code with multiple contacts its working fine and faster like other apps within 500 ms (within half second or less) I am able to load 1000+ contacts.

Total time will depend upon what fields you are trying to access from the Contacts table.

Mange your query according to your requirement do not access unwanted fields. Accessing less field means less looping , less processing and hence faster results.

Accessing right table in contact it also help to reduce contact loading time.

Query Optimization to load contact more faster use projection

 String[] projection = {
            ContactsContract.Data.MIMETYPE,
            ContactsContract.Data.CONTACT_ID,
            ContactsContract.Contacts.DISPLAY_NAME,
            ContactsContract.Contacts.PHOTO_URI,
            ContactsContract.Contacts.STARRED,
            ContactsContract.RawContacts.ACCOUNT_TYPE,
            ContactsContract.CommonDataKinds.Contactables.DATA,
            ContactsContract.CommonDataKinds.Contactables.TYPE
    };

Selection and selection argument

String selection = ContactsContract.Data.MIMETYPE + " in (?, ?)" + " AND " /*+ ContactsContract.Contacts.IN_VISIBLE_GROUP + " = '" + 1 + "' AND "*/ +
            ContactsContract.Data.HAS_PHONE_NUMBER + " = '" + 1 + "'";

    String[] selectionArgs = {
            ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE,
            ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE,
    };

To order contacts alphabetically use following code

 try {
        Collections.sort(listview_address, new Comparator<ContactBook>() {
            @Override
            public int compare(ContactBook lhs, ContactBook rhs) {
                return lhs.name.toUpperCase().compareTo(rhs.name.toUpperCase());
            }
        });
    } catch (Exception e) {
        e.printStackTrace();
    }

Following is complete source code

 public void initeContacts() {
    List<ContactBook> listview_address = new LinkedList<ContactBook>();
    SparseArray<ContactBook> addressbook_array = null;
    {
        addressbook_array = new SparseArray<ContactBook>();

        long start = System.currentTimeMillis();

        String[] projection = {
                ContactsContract.Data.MIMETYPE,
                ContactsContract.Data.CONTACT_ID,
                ContactsContract.Contacts.DISPLAY_NAME,
                ContactsContract.Contacts.PHOTO_URI,
                ContactsContract.Contacts.STARRED,
                ContactsContract.RawContacts.ACCOUNT_TYPE,
                ContactsContract.CommonDataKinds.Contactables.DATA,
                ContactsContract.CommonDataKinds.Contactables.TYPE
        };

        String selection = ContactsContract.Data.MIMETYPE + " in (?, ?)" + " AND " /*+ ContactsContract.Contacts.IN_VISIBLE_GROUP + " = '" + 1 + "' AND "*/ +
                ContactsContract.Data.HAS_PHONE_NUMBER + " = '" + 1 + "'";

        String[] selectionArgs = {
                ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE,
                ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE,
        };

        String sortOrder = ContactsContract.Contacts.SORT_KEY_ALTERNATIVE;

        Uri uri = null;
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR2) {
            uri = ContactsContract.CommonDataKinds.Contactables.CONTENT_URI;
        } else {
            uri = ContactsContract.Data.CONTENT_URI;

        }
        // we could also use Uri uri = ContactsContract.Data.CONTENT_URI;
        // we could also use Uri uri = ContactsContract.Contact.CONTENT_URI;

        Cursor cursor = getActivity().getContentResolver().query(uri, projection, selection, selectionArgs, sortOrder);


        final int mimeTypeIdx = cursor.getColumnIndex(ContactsContract.Data.MIMETYPE);
        final int idIdx = cursor.getColumnIndex(ContactsContract.Data.CONTACT_ID);
        final int nameIdx = cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME);
        final int dataIdx = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Contactables.DATA);
        final int photo = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Contactables.PHOTO_URI);
        final int typeIdx = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Contactables.TYPE);
        final int account_type = cursor.getColumnIndex(ContactsContract.RawContacts.ACCOUNT_TYPE);
        while (cursor.moveToNext()) {
            int contact_id = cursor.getInt(idIdx);
            String photo_uri = cursor.getString(photo);
            String contact_name = cursor.getString(nameIdx);
            String contact_acc_type = cursor.getString(account_type);
            int contact_type = cursor.getInt(typeIdx);
            String contact_data = cursor.getString(dataIdx);
            ContactBook contactBook = addressbook_array.get(contact_id);

           /* if (contactBook == null) {
                //list  contact add to avoid duplication
                //load All contacts fro device
                //to add contacts number with name add one extra veriable in ContactBook as number and pass contact_data this give number to you (contact_data is PHONE NUMBER)
                contactBook = new ContactBook(contact_id, contact_name, getResources(), photo_uri, contact_acc_type, "phone number");
                addressbook_array.put(contact_id, contactBook);
                listview_address.add(contactBook);

            }*/

            String Contact_mimeType = cursor.getString(mimeTypeIdx);
            //here am checking Contact_mimeType to get mobile number asociated with perticular contact and email adderess asociated
            if (Contact_mimeType.equals(ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE)) {

                if (contactBook != null) {
                    contactBook.addEmail(contact_type, contact_data);
                }

            } else {
                if (contactBook == null) {
                    //list  contact add to avoid duplication
                    //load All contacts fro device
                    //to add contacts number with name add one extra veriable in ContactBook as number and pass contact_data this give number to you (contact_data is PHONE NUMBER)
                    contactBook = new ContactBook(contact_id, contact_name, getResources(), photo_uri, contact_acc_type, "phone number");
                    addressbook_array.put(contact_id, contactBook);
                    listview_address.add(contactBook);

                }
               // contactBook.addPhone(contact_type, contact_data);


            }


        }

        cursor.close();


        try {
            Collections.sort(listview_address, new Comparator<ContactBook>() {
                @Override
                public int compare(ContactBook lhs, ContactBook rhs) {
                    return lhs.name.toUpperCase().compareTo(rhs.name.toUpperCase());
                }
            });
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

You can use following code in above code that I have commented .It club the the single contact with its multiple number.To get all number associated with single contact use array in Object class.

 if (contactBook == null) {
            //irst  contact add to avoid duplication
            //load All contacts fro device
            contactBook = new ContactBook(contact_id, contact_name, getResources(), photo_uri, contact_acc_type, "");
            addressbook_array.put(contact_id, contactBook);
            listview_address.add(contactBook);
        }

        String Contact_mimeType = cursor.getString(mimeTypeIdx);
        //here am checking Contact_mimeType to get mobile number asociated with perticular contact and email adderess asociated
        if (Contact_mimeType.equals(ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE)) {

            contactBook.addEmail(contact_type, contact_data);

        } else {

            contactBook.addPhone(contact_type, contact_data);

        }

Object class

public class ContactBook {
public int id;
public Resources res;
public String name;
public String photo;
public String contact_acc_type;
public SparseArray<String> emails;
public SparseArray<String> phones;
/*  public LongSparseArray<String> emails;
public LongSparseArray<String> phones;*/
public String header = "";


public ContactBook(int id, String name, Resources res, String photo, String contact_acc_type, String header) {
    this.id = id;
    this.name = name;
    this.res = res;
    this.photo = photo;
    this.contact_acc_type = contact_acc_type;
    this.header = header;
}

@Override
public String toString() {
    return toString(false);
}

public String toString(boolean rich) {

    //testing method to check ddata
    SpannableStringBuilder builder = new SpannableStringBuilder();
    if (rich) {
        builder.append("id: ").append(Long.toString(id))
                .append(", name: ").append("\u001b[1m").append(name).append("\u001b[0m");
    } else {
        builder.append(name);
    }

    if (phones != null) {
        builder.append("\n\tphones: ");
        for (int i = 0; i < phones.size(); i++) {
            int type = (int) phones.keyAt(i);
            builder.append(ContactsContract.CommonDataKinds.Phone.getTypeLabel(res, type, ""))
                    .append(": ")
                    .append(phones.valueAt(i));
            if (i + 1 < phones.size()) {
                builder.append(", ");
            }
        }
    }

    if (emails != null) {
        builder.append("\n\temails: ");
        for (int i = 0; i < emails.size(); i++) {
            int type = (int) emails.keyAt(i);
            builder.append(ContactsContract.CommonDataKinds.Email.getTypeLabel(res, type, ""))
                    .append(": ")
                    .append(emails.valueAt(i));
            if (i + 1 < emails.size()) {
                builder.append(", ");
            }
        }
    }
    return builder.toString();
}

public void addEmail(int type, String address) {
    //this is the array in object class where i am storing contact all emails of perticular contact (single)
    if (emails == null) {
        //   if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
        emails = new SparseArray<String>();
        emails.put(type, address);
        /*} else {
            //add emails to array below Jelly bean //use single array list
        }*/
    }
}

public void addPhone(int type, String number) {
    //this is the array in object class where i am storing contact numbers of perticular contact
    if (phones == null) {
        //  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
        phones = new SparseArray<String>();
        phones.put(type, number);
       /* } else {
            //add emails to array below Jelly bean //use single array list
        }*/
    }
}}

###

For loading the contacts with mininum time the optimum solution is to use the concept of projection and selection argument while querying the cursor for contacts.

this can be done in following way

void getAllContacts() {
    long startnow;
    long endnow;

    startnow = android.os.SystemClock.uptimeMillis();
    ArrayList arrContacts = new ArrayList();

    Uri uri = ContactsContract.CommonDataKinds.Phone.CONTENT_URI;
    String selection = ContactsContract.Contacts.HAS_PHONE_NUMBER;
    Cursor cursor = ctx.getContentResolver().query(uri, new String[]{ContactsContract.CommonDataKinds.Phone.NUMBER,   ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME, ContactsContract.CommonDataKinds.Phone._ID, ContactsContract.Contacts._ID}, selection, null, ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME + " ASC");

    cursor.moveToFirst();
    while (cursor.isAfterLast() == false) {

        String contactNumber = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
        String contactName = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
        int phoneContactID = cursor.getInt(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone._ID));
        int contactID = cursor.getInt(cursor.getColumnIndex(ContactsContract.Contacts._ID));
        Log.d("con ", "name " + contactName + " " + " PhoeContactID " + phoneContactID + "  ContactID " + contactID)

        cursor.moveToNext();
    }
    cursor.close();
    cursor = null;

    endnow = android.os.SystemClock.uptimeMillis();
    Log.d("END", "TimeForContacts " + (endnow - startnow) + " ms");
}

With above method it took 400ms(less than second) to load contacts where as in normall way it was taking 10-12 sec.

For details imformation this post might help as i took help from it
http://www.blazin.in/2016/02/loading-contacts-fast-from-android.html

###

If your time increases with your data, then you are probably running a new query to fetch phones/emails for every contact. If you query for the phone/email field using ContactsContract.CommonDataKinds.Phone.NUMBER, then you will just retrieve 1 phone per contact.
The solution is to project the fields and join them by contact id.

Here is my solution in Kotlin (extracting id, name, all phones and emails):

val projection = arrayOf(
        ContactsContract.Data.MIMETYPE,
        ContactsContract.Data.CONTACT_ID,
        ContactsContract.Contacts.DISPLAY_NAME,
        ContactsContract.CommonDataKinds.Contactables.DATA

)
val selection = "${ContactsContract.Data.MIMETYPE} in (?, ?)"

val selectionArgs = arrayOf(
        ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE,
        ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE)

val contacts = applicationContext
        .contentResolver
        .query(ContactsContract.Data.CONTENT_URI, projection, selection, selectionArgs, null)
        .run {
            if (this == null) {
                throw IllegalStateException("Cursor null")
            }
            val contactsById = mutableMapOf<String, LocalContact>()
            val mimeTypeField = getColumnIndex(ContactsContract.Data.MIMETYPE)
            val idField = getColumnIndex(ContactsContract.Data.CONTACT_ID)
            val nameField = getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME)
            val dataField = getColumnIndex(ContactsContract.CommonDataKinds.Contactables.DATA)

            while (moveToNext()) {
                val mimeType = getString(mimeTypeField)
                val id = getString(idField)
                var contact = contactsById[id]
                if (contact == null) {
                    val name = getString(nameField)
                    contact = LocalContact(id = id, fullName = name, phoneNumbers = listOf(), emailAddresses = listOf())
                }
                val data = getString(dataField)
                when(getString(mimeTypeField)) {
                    ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE ->
                        contact = contact.copy(emailAddresses = contact.emailAddresses + data)
                    ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE ->
                        contact = contact.copy(phoneNumbers = contact.phoneNumbers + data)
                }
                contactsById[id] = contact
            }
            close()
            contactsById.values.toList()
        }

And for reference, my LocalContact model:

data class LocalContact(
        val id: String,
        val fullName: String?,
        val phoneNumbers: List<String>,
        val emailAddresses: List<String>
)

###

I think this is a better solution:

public ContentValues getAllContacts() {
    ContentValues contacts = new ContentValues();
    ContentResolver cr = getContentResolver();
    Cursor cur = cr.query(ContactsContract.Contacts.CONTENT_URI, null, null, null, null);

    if (cur != null && cur.getCount() > 0) {
        while (cur.moveToNext()) {
            String id = cur.getString(cur.getColumnIndex(ContactsContract.Contacts._ID));
            String name = cur.getString(cur.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME));

            if (cur.getInt(cur.getColumnIndex(ContactsContract.Contacts.HAS_PHONE_NUMBER)) > 0) {
                Cursor pCur = cr.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null,
                        ContactsContract.CommonDataKinds.Phone.CONTACT_ID + " = ?", new String[]{id}, null);
                if (pCur != null) {
                    while (pCur.moveToNext()) {
                        String phoneNo = pCur.getString(pCur.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
                        contacts.put(phoneNo, name);
                    }
                    pCur.close();
                }
            }
        }
        cur.close();
    }
    return contacts;
}

for use it you need to call this lines once:

ContentValues contacts = new ContentValues();
contacts = getAllContacts();

and when you want to get contact name by number, just use:

String number = "12345";
String name = (String) G.contacts.get(number);

this algorithm is a bit faster…

Leave a Reply

Your email address will not be published. Required fields are marked *