I want to get for each contacts in the user phone the following data
StructuredName.GIVEN_NAME|Phone.NUMBER|Email.DATA|StructuredPostal.CITY
I'm pretty sure I query the ContactsContract.Data
table with a pure SQL query but the there is no clear documentation on how to do it.
It seems that you can inject SQL in the contentResolver.query but it does not seem to be sustainable.
My code hereafter works perfectly but is very slow.
Basically,
However, the many loops are obviously counterproductive in term of performance.
With a 1000 contacts, it makes around 3000 queries.
// CREATE Content resolver
val resolver: ContentResolver = contentResolver
val cursor = resolver.query(
ContactsContract.Contacts.CONTENT_URI,
arrayOf(
ContactsContract.Contacts._ID
),
null,
null,
null
)
if ( cursor != null && cursor.count > 0) {
// PROGRESSBAR Process
myProgressBar?.progress = 0
myProgressBarCircleText?.text = getString(R.string.processing_contacts)
myProgressBar?.visibility = View.VISIBLE
myProgressBarCircle?.visibility = View.VISIBLE
myProgressBarCircleText?.visibility = View.VISIBLE
// PUT BASIC REQUIRED INFO
val jsonAllContacts = JSONObject()
jsonAllContacts.put("source", "2")
// EXECUTE CODE on another thread to prevent blocking UI
Thread(Runnable {
var cursorPosition = 0
var currentProgress: Int
Log.e("JSON", "cursor.count: ${cursor.count}")
// CODE TO EXEC LOOP
while (cursor.moveToNext()) {
// Increment cursor for progressBar
cursorPosition += 1
currentProgress = ((cursorPosition.toFloat() / cursor.count.toFloat()) * 100).toInt()
// INIT of jsonObjects
val jsonEmail = JSONObject()
val jsonPhone = JSONObject()
val jsonAddress = JSONObject()
val jsonCurrentContact = JSONObject()
/**
* NAME DETAILS
*/
val contactID = cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts._ID))
val nameCur = contentResolver.query(
ContactsContract.Data.CONTENT_URI,
arrayOf(
ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME,
ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME,
ContactsContract.CommonDataKinds.StructuredName.MIDDLE_NAME
),
ContactsContract.Data.CONTACT_ID + " = ?" + " AND " + ContactsContract.Data.MIMETYPE + " = ?",
arrayOf(
contactID,
ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE
),
null
)
var givenName = ""
var familyName: String
var middleName: String
var fullName = ""
if ( nameCur != null ) {
while (nameCur.moveToNext()) {
givenName = nameCur.getString(nameCur.getColumnIndex(ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME)) ?: ""
middleName = nameCur.getString(nameCur.getColumnIndex(ContactsContract.CommonDataKinds.StructuredName.MIDDLE_NAME)) ?: ""
familyName = nameCur.getString(nameCur.getColumnIndex(ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME)) ?: ""
fullName = if ( middleName != "" && (middleName != familyName) ) {
"$middleName $familyName"
} else {
familyName
}
}
jsonCurrentContact.put("given", givenName)
jsonCurrentContact.put("family", fullName)
}
nameCur?.close()
/**
* PHONE NUMBER
*/
val phoneCur = contentResolver.query(
ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
arrayOf(
ContactsContract.CommonDataKinds.Phone.TYPE,
ContactsContract.CommonDataKinds.Phone.LABEL,
ContactsContract.CommonDataKinds.Phone.NUMBER
),
ContactsContract.CommonDataKinds.Phone.CONTACT_ID + "=?",
arrayOf( contactID ),
null
)
if ( phoneCur != null && phoneCur.count > 0 ) {
while (phoneCur.moveToNext()) {
val phoneNumType = phoneCur.getString( phoneCur.getColumnIndex(ContactsContract.CommonDataKinds.Phone.TYPE) ) ?: ""
val phoneNumLabel = phoneCur.getString( phoneCur.getColumnIndex(ContactsContract.CommonDataKinds.Phone.LABEL) ) ?: ""
var label: String
val phoneNumber = phoneCur.getString( phoneCur.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER) ).replace(" ", "") ?: ""
//Log.e("JSON", "JSON phoneNum: $phoneNumLabel $phoneNumber")
// TRY to get label info
label = if ( phoneNumType == "" ) {
phoneNumLabel
} else {
phoneNumType
}
jsonPhone.put("label", label)
jsonPhone.put("number", phoneNumber)
jsonCurrentContact.accumulate("phone", jsonPhone)
}
}
phoneCur?.close()
/**
* EMAIL
*/
val emailCur = contentResolver.query(
ContactsContract.CommonDataKinds.Email.CONTENT_URI,
arrayOf(
ContactsContract.CommonDataKinds.Email.LABEL,
ContactsContract.CommonDataKinds.Email.DATA
),
ContactsContract.CommonDataKinds.Email.CONTACT_ID + "=?",
arrayOf(contactID),
null
)
if ( emailCur != null ) {
while (emailCur.moveToNext()) {
val emailLabel = emailCur.getString(emailCur.getColumnIndex(ContactsContract.CommonDataKinds.Email.LABEL)) ?: ""
val email = emailCur.getString(emailCur.getColumnIndex(ContactsContract.CommonDataKinds.Email.DATA)) ?: ""
jsonEmail.put("label", emailLabel)
jsonEmail.put("email", email)
jsonCurrentContact.accumulate("email", jsonEmail)
}
}
emailCur?.close()
/**
* ADDRESS
*/
var street: String
var city: String
var postalCode: String
var state: String
var country: String
var label: String
val addressCur = contentResolver.query(
ContactsContract.CommonDataKinds.StructuredPostal.CONTENT_URI,
arrayOf(
ContactsContract.CommonDataKinds.StructuredPostal.TYPE,
ContactsContract.CommonDataKinds.StructuredPostal.STREET,
ContactsContract.CommonDataKinds.StructuredPostal.CITY,
ContactsContract.CommonDataKinds.StructuredPostal.POSTCODE,
ContactsContract.CommonDataKinds.StructuredPostal.REGION,
ContactsContract.CommonDataKinds.StructuredPostal.COUNTRY
),
ContactsContract.CommonDataKinds.StructuredPostal.CONTACT_ID + "=" + contactID,
null,
null
)
if ( addressCur != null ) {
while (addressCur.moveToNext()) {
label = addressCur.getString(addressCur.getColumnIndex(ContactsContract.CommonDataKinds.StructuredPostal.TYPE)) ?: ""
street = addressCur.getString(addressCur.getColumnIndex(ContactsContract.CommonDataKinds.StructuredPostal.STREET)) ?: ""
city = addressCur.getString(addressCur.getColumnIndex(ContactsContract.CommonDataKinds.StructuredPostal.CITY)) ?: ""
postalCode = addressCur.getString(addressCur.getColumnIndex(ContactsContract.CommonDataKinds.StructuredPostal.POSTCODE)) ?: ""
state = addressCur.getString(addressCur.getColumnIndex(ContactsContract.CommonDataKinds.StructuredPostal.REGION)) ?: ""
country = addressCur.getString(addressCur.getColumnIndex(ContactsContract.CommonDataKinds.StructuredPostal.COUNTRY)) ?: ""
jsonAddress.put("label", label)
jsonAddress.put("street", street)
jsonAddress.put("city", city)
jsonAddress.put("postalcode", postalCode)
jsonAddress.put("state", state)
jsonAddress.put("country", country)
jsonCurrentContact.accumulate("address", jsonAddress)
}
}
addressCur?.close()
Log.e("", "jsonCurrentContact: $jsonCurrentContact")
// PUT the current JSON object info into an array
jsonAllContacts.accumulate("contacts", jsonCurrentContact)
}
cursor.close()
}).start()
} else {
cursor?.close()
}
Those 3000 queries you mentioned can be reduced to just one, and it should finish rather quickly.
We'll take advantage of two things when improving the code:
ContactsContract.CommonDataKinds.XXX
tables is actually stored in a single big table called Data
.ContactsContract.Contacts
when querying over Data
To make the code simpler, I advise you to define a Contact
object to store in memory the info we find for a single contact, and use a HashMap to map a contact-ID to a Contact
object
Read more about this here: https://developer.android.com/reference/android/provider/ContactsContract.Data.html
Here's some code to get you started:
Map<Long, Contact> contacts = new HashMap<>();
// If you need item type / label, add Data.DATA2 & Data.DATA3 to the projection
String[] projection = {Data.CONTACT_ID, Data.DISPLAY_NAME, Data.MIMETYPE, Data.DATA1};
// Add more types to the selection if needed, e.g. StructuredName
String selection = Data.MIMETYPE + " IN ('" + Phone.CONTENT_ITEM_TYPE + "', '" + Email.CONTENT_ITEM_TYPE + "', '" + StructuredPostal.CONTENT_ITEM_TYPE + "')";
Cursor cur = cr.query(Data.CONTENT_URI, projection, selection, null, null);
// Loop through the data
while (cur.moveToNext()) {
long id = cur.getLong(0);
String name = cur.getString(1);
String mime = cur.getString(2); // email / phone / postal
String data = cur.getString(3); // the actual info, e.g. +1-212-555-1234
// get the Contact class from the HashMap, or create a new one and add it to the Hash
Contact contact;
if (contacts.containsKey(id)) {
contact = contacts.get(id);
} else {
contact = new Contact(id);
contact.setDisplayName(name);
// start with empty Sets for phones and emails
// instead of HashSets you can use some object to retain more info about the data item (e.g. label)
contact.setPhoneNumbers(new HashSet<>());
contact.setEmails(new HashSet<>());
contact.setAddresses(new HashSet<>());
contacts.put(id, contact);
}
switch (mime) {
case Phone.CONTENT_ITEM_TYPE:
contact.getPhoneNumbers().add(data);
break;
case Email.CONTENT_ITEM_TYPE:
contact.getEmails().add(data);
break;
case StructuredPostal.CONTENT_ITEM_TYPE:
contact.getAddresses().add(data);
break;
}
}
cur.close();
FOLLOW UP Great progress on performance! Now here are some small tweaks to take your new code even further:
getString
on all fields in projection, if some fields are only used by the StructuredPostal
read those only for StructuredPostal
rows and not for each iteration.report in the comments what were you able to achieve with the above tips...