In an attempt to learn how androids ContacsContract works, I tried to add a custom entry to a contact. A whole day of reading docs, tutorials and watching youtube videos later, I am still not really any further in achieving this task.
The closest I got is this document https://developer.android.com/reference/kotlin/android/provider/ContactsContract.Data Stating
For example, if you add a data row for "favorite song" to a raw contact owned by a Google account, it will not get synced to the server, because the Google sync adapter does not know how to handle this data kind. Thus new data kinds are typically introduced along with new account types, i.e. new sync adapters.
They wrote what I am trying, but unfortunately did not provide a solution for how to achieve this task. It would be really kind, if someone would provide a simple example for adding a data row for a favorite song to contacts and retrieving it via code.
__
What I got by myself:
A Method for fetching basic contact information:
import android.content.ContentResolver
import android.database.Cursor
import android.provider.ContactsContract
fun fetchContacts(resolver: ContentResolver) : MutableList<ItemContact> {
var cols = listOf<String>(
ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME,
ContactsContract.CommonDataKinds.Phone.NUMBER,
ContactsContract.CommonDataKinds.Phone.CONTACT_ID,
ContactsContract.CommonDataKinds.Phone._ID,
).toTypedArray()
var cursor : Cursor? = resolver.query(
ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
cols, null, null,
ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME
)
var contactsList : MutableList<ItemContact>
= emptyList<ItemContact>().toMutableList()
if (cursor != null && cursor.count > 0) {
while(cursor.moveToNext()){
contactsList.add(ItemContact(
name = cursor.getString(0),
number = cursor.getString(1),
contact_id = cursor.getString(2),
))
}
}
return contactsList
}
And its corresponding data class
data class ItemContact (
val name: String,
val number: String,
val contact_id: String,
)
From my current understanding I need to add a new RawContact, representing my App, to a given Contact and add the favorite song as single data entry, while creating it. This RawContact should be able to connect to the Contact with the retrieved contact_id. And then I need to check, if a RawContact representing my App exists for a Contact, if so, I will be able to retrieve the stored song, else I leave a placeholder text in the UI, that the Song still needs to be choosen. Somehow this involves a custom Mimetype, but I am still not sure, what this is and how to create one.
First, let's recap the way ContactsContract DB is organized:
There are also pseudo tables, such as ContactsContract.CommonDataKinds.Phone
which you are querying in your code, which basically queries over the Data table but with a specific MIMETYPE value, for example Phone.CONTENT_ITEM_TYPE
.
If you want to implement your own SyncProvider you usually create your own RawContact row, add it to an existing Contact_ID and add Data rows with your new RAW_CONTACT_ID.
Then the People/Contacts apps on the device, when they want to present data about a certain contact, will get a list of all the RawContacts for it (including yours), and then get all the Data for all those RawContacts.
If you only want to add a little piece of custom data to an existing contact like "favorite song" to a contact, you don't have to create a new RawContact for that, instead you can create a new Data row and attach it to the existing RawContact with a custom MIMETYPE, but keep in mind your item will not get synced to Google's servers, however it can still be used on the device locally.
I assume you're looking after the second option, so here's a sample code (not tested):
// you need to get a RawContact ID of the contact you want to add info to
fun addFavoriteSong(context: Context, rawContactId: Long) {
val resolver = context.contentResolver
val ops = ArrayList<ContentProviderOperation>()
ops.add(ContentProviderOperation.newInsert(Data.CONTENT_URI, true)
.withValue(Data.RAW_CONTACT_ID, rawContactId)
.withValue(Data.MIMETYPE, "vnd.android.cursor.item/vnd.com.example.favorite_song")
.withValue(Data.DATA1, "Paranoid Android")
.withValue(Data.DATA2, "Radiohead")
.withValue(Data.DATA3, "OK Computer")
.build())
try {
val results = resolver.applyBatch(ContactsContract.AUTHORITY, ops)
if (results.isEmpty())
return
} catch (e: Exception) {
e.printStackTrace()
}
Log.i("Songs Added", "success!");
}
Then to query for that information, along with other info such as name and phone:
fun fetchContacts(resolver: ContentResolver) : MutableCollection<ItemContact> {
var cols = arrayOf(
Data.CONTACT_ID,
Data.MIMETYPE,
Data.DISPLAY_NAME,
Phone.NUMBER,
Data.DATA1,
Data.DATA2,
Data.DATA3,
)
// get only rows of MIMETYPE phone and your new custom MIMETYPE
var selection = Data.MIMETYPE + " IN (" + Phone.CONTENT_ITEM_TYPE + ", " + "vnd.android.cursor.item/vnd.com.example.favorite_song" + ")"
var cursor : Cursor? = resolver.query(Data.CONTENT_URI, cols, null, null, Data.CONTACT_ID)
val map = hashMapOf<Long, ItemContact>()
while(cursor != null && cursor.moveToNext()) {
val contactId = cursor.getLong(0)
val mimetype = cursor.getString(1)
// gets the existing ItemContact from the map or if not found, puts an empty one in the map
val contact = map.getOrPut(contactId) { ItemContact() }
with(contact) {
if (mimetype == Phone.CONTENT_ITEM_TYPE) {
contact_id = contactId
name = cursor.getString(2)
number = cursor.getString(3)
} else {
contact_id = contactId
song = cursor.getString(4)
band = cursor.getString(5)
album = cursor.getString(6)
}
}
}
return map.values
}