3
\$\begingroup\$

My goal is to read complete contact information on an Android application. This is very slow and my understanding is that since the contacts are stored locally in a phone database, it shouldn't take long to fetch complete contact details.

public void readContacts() {
    Log.d("TAG", " -> Into readContacts");
    // Get the ContentResolver
    ContentResolver cr = getContentResolver();
    // Get the Cursor of all the contacts
    Cursor cursor = cr.query(ContactsContract.Contacts.CONTENT_URI, null, null, null, null);

    // Move the cursor to first. Also check whether the cursor is empty or not.
    if (cursor.moveToFirst()) {
        // Iterate through the cursor
        do {
            // Get the contacts name
            Contact contactToSave = new Contact();
            contactToSave.setIsUploaded(false);
            String id = cursor.getString(cursor.getColumnIndex(
                    ContactsContract.Contacts._ID));
            Log.d(TAG, " ID " + id);


            String name = cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME));
            contactToSave.setFirstName(name);
            Log.d(TAG, " Complete Name " + name);

            String orgWhere = ContactsContract.Data.CONTACT_ID + " = ? AND " + ContactsContract.Data.MIMETYPE + " = ?";
            String[] orgWhereParams = new String[]{id,
                    ContactsContract.CommonDataKinds.Organization.CONTENT_ITEM_TYPE};
            Cursor orgCur = cr.query(ContactsContract.Data.CONTENT_URI, null, orgWhere, orgWhereParams, null);

            if (orgCur.moveToFirst()) {
                String orgName = orgCur.getString(orgCur.getColumnIndex(ContactsContract.CommonDataKinds.Organization.DATA));
                String title = orgCur.getString(orgCur.getColumnIndex(ContactsContract.CommonDataKinds.Organization.TITLE));
                contactToSave.setCompany(orgName);
            }
            orgCur.close();

            Cursor emailCur = cr.query(
                    ContactsContract.CommonDataKinds.Email.CONTENT_URI,
                    null,
                    ContactsContract.CommonDataKinds.Email.CONTACT_ID + " = ?",
                    new String[]{id}, null);
            while (emailCur.moveToNext()) {
                // This would allow you get several email addresses
                // if the email addresses were stored in an array
                String email = emailCur.getString(
                        emailCur.getColumnIndex(ContactsContract.CommonDataKinds.Email.DATA));
                contactToSave.setEmail(email);
            }
            emailCur.close();


            if (Integer.parseInt(cursor.getString(cursor.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);
                while (pCur.moveToNext()) {
                    String phoneNumber = pCur.getString(pCur.getColumnIndex(
                            ContactsContract.CommonDataKinds.Phone.NUMBER));
                    contactToSave.setPhone(phoneNumber);
                }
                pCur.close();
            }
            mAdapter.add(contactToSave);
        } while (cursor.moveToNext());
        mAdapter.sort(new Comparator<Contact>() {
            @Override
            public int compare(Contact o1, Contact o2) {
                return o1.getFirstName().compareTo(o2.getFirstName());
            }
        });
    }
    // Close the cursor
    cursor.close();
}
  • I think creating multiple cursors for different data may be a performance overhead. Is there any way those can be combined?
  • Is there any other way to fetch the cursor than running multiple queries using multiple cursors?
  • If the answer for the above two questions are negative, then is this the best way possible for such a simple requirement?
\$\endgroup\$
5
\$\begingroup\$

Use AsyncQueryHandler, a helper class to help make handling asynchronous ContentResolver queries easier.

It basically wraps the ContentResolver object and handles background execution of its operations (CRUD) as well as passing messages (result) from the between threads (background and main/UI).

Just use to call the queryHandler.startQuery method and you will get callback in onQueryComplete method.

Example

class QueryHandler extends AsyncQueryHandler {

        public QueryHandler(ContentResolver cr) {
            super(cr);
        }

        @Override
        protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
            switch (token) {

                case CONTACT_QUERY:
//Do your stuff here
                    if (cursor.moveToFirst()) {
                        // Iterate through the cursor
                        do {

                            String id = cursor.getString(cursor.getColumnIndex(
                                    ContactsContract.Contacts._ID));
                            Log.e(TAG, " ID " + id);

                        } while (cursor.moveToNext());
                    }
                    break;
}
}

Use below method to fetch contact and add projection, selection according to your requirment.

public void fetchContact(){
        queryHandler.startQuery(CONTACT_QUERY, null, ContactsContract.Contacts.CONTENT_URI, null, null, null, null);
    }
\$\endgroup\$
  • \$\begingroup\$ Thanks for the answer can you please provide any example or pointer to what you suggested? \$\endgroup\$ – AppsWise Nov 30 '16 at 11:09
  • \$\begingroup\$ Its simple.. you can follow any tutorial and by the way example has been added ... have a look and enjoy \$\endgroup\$ – Amandeep Rohila Nov 30 '16 at 12:44
  • \$\begingroup\$ Thank you, it helped and indeed improved the performance. Can you also help me with this - stackoverflow.com/questions/40884458/… thread which is part of the problem. \$\endgroup\$ – AppsWise Dec 1 '16 at 5:41
3
\$\begingroup\$

Put this code exactly on your onCreate method, I have taken reference from your other thread on So to complete it and I have commented out the section and placed it on proper places -

queryHandler = new AsyncQueryHandler(getContentResolver()) {

        @Override
        protected Handler createHandler(Looper looper) {
            return super.createHandler(looper);
        }

        private ContentResolver cr = getContentResolver();

        @Override
        protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
            contactList = new ArrayList<>();
            contactListTosave = new ArrayList<>();

            // query() completed
            // Move the cursor to first. Also check whether the cursor is empty or not.

            if (cursor.moveToFirst()) {
                // Iterate through the cursor
                do {
                    // Get the contacts name
                    Contact contactToSave = new Contact();
                    contactToSave.setIsUploaded(false);
                    String id = cursor.getString(cursor.getColumnIndex(
                            ContactsContract.Contacts._ID));
//                Log.d(TAG, " ID " + id);


                    String name = cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME));
                    contactToSave.setFirstName(name);
//                Log.d(TAG, " Complete Name " + name);

                    String orgWhere = ContactsContract.Data.CONTACT_ID + " = ? AND " + ContactsContract.Data.MIMETYPE + " = ?";
                    String[] orgWhereParams = new String[]{id,
                            ContactsContract.CommonDataKinds.Organization.CONTENT_ITEM_TYPE};
                    Cursor orgCur = cr.query(ContactsContract.Data.CONTENT_URI, null, orgWhere, orgWhereParams, null);

                    if (orgCur.moveToFirst()) {
                        String orgName = orgCur.getString(orgCur.getColumnIndex(ContactsContract.CommonDataKinds.Organization.DATA));
                        String title = orgCur.getString(orgCur.getColumnIndex(ContactsContract.CommonDataKinds.Organization.TITLE));
                        contactToSave.setCompany(orgName);
                    }
                    orgCur.close();

                    Cursor emailCur = cr.query(
                            ContactsContract.CommonDataKinds.Email.CONTENT_URI,
                            null,
                            ContactsContract.CommonDataKinds.Email.CONTACT_ID + " = ?",
                            new String[]{id}, null);
                    while (emailCur.moveToNext()) {
                        // This would allow you get several email addresses
                        // if the email addresses were stored in an array
                        String email = emailCur.getString(
                                emailCur.getColumnIndex(ContactsContract.CommonDataKinds.Email.DATA));
                        contactToSave.setEmail(email);
                    }
                    emailCur.close();


                    if (Integer.parseInt(cursor.getString(cursor.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);
                        while (pCur.moveToNext()) {
                            String phoneNumber = pCur.getString(pCur.getColumnIndex(
                                    ContactsContract.CommonDataKinds.Phone.NUMBER));
                            contactToSave.setPhone(phoneNumber);
                        }
                        pCur.close();
                    }
                    contactList.add(contactToSave);
                } while (cursor.moveToNext());



            }
            // Close the cursor
            cursor.close();
            Log.d(TAG, "onCreate: Making List Adapter visible and pd invisible in mAdapter onQueryComplete");
            mContactListView = (ListView) findViewById(R.id.readContactLv);
            mContactListView.setOnItemClickListener(onClickListener);

            if (mAdapter == null) {
                mAdapter = new ContactArrayAdapter(ReadContacts.this, 0, contactList);
                mContactListView.setAdapter(mAdapter);
            }

            mAdapter.sort(new Comparator<Contact>() {
                @Override
                public int compare(Contact o1, Contact o2) {
                    return o1.getFirstName().compareTo(o2.getFirstName());
                }
            });
            pb.setVisibility(View.INVISIBLE);
            mContactListView.setVisibility(View.VISIBLE);
        }
    };
\$\endgroup\$
  • \$\begingroup\$ Welcome to Code Review! You have presented an alternative solution, but haven't reviewed the code. Please explain your reasoning (how your solution works and how it improves upon the original) so that the author can learn from your thought process. \$\endgroup\$ – Heslacher Dec 1 '16 at 9:27

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.