First, it's important to understand the most basic concept of the Android Contacts Provider / ContactsContract. Afterwards, everything in this library should just make sense.
There is only one thing you need to know outside of this library. The library handles the rest of the details so you don't have to!
There are 3 main database tables used in dealing with contacts. These tables are all connected.
- Contacts
- Rows representing different people.
- E.G. John Doe
- RawContacts
- Rows that link Contacts rows to specific Accounts.
- E.G. John Doe from [email protected], John Doe from [email protected]
- Data
- Rows containing data (e.g. name, email) for a RawContacts row.
- E.G. John Doe from Gmail's name and email, John Doe from Hotmail's phone and address
ℹ️ There are more tables but it won't be covered in this docs for brevity.
In the example given (E.G.) above,
- there is one row in the Contacts table for the person John Doe
- there are 2 rows in the RawContacts table that make up the Contact John Doe
- there are 4 rows in the Data table belonging to the Contact John Doe.
- 2 of these rows belong to John Doe from Gmail and the other 2 belong to John Doe from Hotmail
In the background, the Contacts Provider automatically performs the RawContacts linking/aggregation into a single Contact. To forcefully link or unlink sets of RawContacts, read Link unlink Contacts.
In the background, the Contacts Provider syncs all data from the local database to the remote database and vice versa (depending on system contact sync settings). Read more in Sync contact data across devices.
That's all you need to know! Hopefully it wasn't too much. I know it was difficult for me to grasp in the beginning =P.
Once you internalize this one to many relationship between Contacts -> RawContacts -> Data, you have unlocked the full potential of this library and the world is at the palm of your hands!
This library provides entities that model everything in the Contacts Provider database.
Contact
- Primarily contains a list of RawContacts that are associated with this contact.
RawContact
- Contains contact data that belong to an account.
- There may be more than one RawContact per Contact.
DataEntity
- A specific kind of data of a RawContact. These entities model the common data kinds that are provided by the Contacts Provider.
Address
Email
Event
GroupMembership
Im
Name
Nickname
Note
Organization
Phone
Photo
Relation
SipAddress
Website
You can find all of the above in the contacts.core.entities
package. Note that there are other
entities that are not mentioned in this docs for brevity.
All entities are Parcelable
to support state retention during app/activity/fragment/view recreation.
Each entity has an immutable version (typically returned by queries) and a mutable version
(typically used by insert, update, and delete functions). Most immutable entities have a
mutableCopy
function that returns a mutable copy (typically to be used for inserts and updates
and other mutating API functions).
ℹ️ Custom data kinds may also be integrated into the contacts database (though not synced across devices). For more info, read Integrate custom data.
ℹ️ Default native and custom data may be retrieved, set, or cleared. For more info, read Get set clear default Contact data.
The fields defined in contacts.core.Fields.kt
specify what properties of entities to include in
read and write operations. For example, to include only the contact display name, organization
company, and all phone number fields in a query/insert/update operation,
queryInsertUpdate.include(mutableSetOf<AbstractDataField>().apply {
add(Fields.Contact.DisplayNamePrimary)
add(Fields.Organization.Company)
addAll(Fields.Phone.all)
})
The following entity properties are used in the read/write operation,
Contact {
displayNamePrimary
RawContact {
organization {
company
}
phones {
number
normalizedNumber
type
label
}
}
}
ℹ️ For more info, read Include only certain fields for read and write operations.
A RawContact
may have at most one OR no limits of certain kinds of data.
A RawContact may have 0 or 1 of each of these data kinds;
Name
Nickname
Note
Organization
Photo
SipAddress
A RawContact may have 0, 1, or more of each of these data kinds;
Address
Email
Event
GroupMembership
Im
Phone
Relation
Website
The Contacts Provider may or may not enforce these count restrictions. However, the native Contacts app imposes these restrictions. Therefore, this library also imposes these restrictions and disables consumers from violating them.
The core library does not explicitly expose count restrictions to consumers. However, it is exposed
when integrating custom data via the CustomDataCountRestriction
.
Any data kind may be set as read-only during insertion of new RawContacts. This is not for general application usage and should really only be used by sync adapters. For example,
NewEmail(address = "[email protected]", isReadOnly = true)
ℹ️ For more info, read the in-code documentation of
NewDataEntity.isReadOnly
.
To set the read-only property of all data of a NewRawContact
in one function call,
newRawContact.setDataAsReadOnly(true)
Checking if an ExistingDataEntity
is read-only requires a separate query. You can use extension
functions defined in DataIsReadOnly.kt
for this purpose.
ℹ️ For more info, read Convenience functions | Get/set Data read-only.
Modifying read-only data locally as a sync adapter is possible by using an instance of
contacts.core.Contacts
with the callerIsSyncAdapter
flag set to true.
ℹ️ For more info, read Contacts API Setup | Sync adapter operations.
There is a section in the official Contacts Provider documentation about "Data Integrity"; https://developer.android.com/guide/topics/providers/contacts-provider#DataIntegrity
It enumerates four general rules to follow to retain the "integrity of data" :D Paraphrasing in terms of this library, the rules are as follows;
- Always add a
Name
for everyRawContact
. - Always link new Data to their parent
RawContact
. - Change data only for those raw contacts that you own.
- Always use the constants defined in
ContactsContract
and its subclasses for authorities, content URIs, URI paths, column names, MIME types, and TYPE values.
This library follows rules 2 and 4.
Rule 1 is ignored because the native Contacts app also ignores that rule. Enforcing this rule means
that a name has to be provided for every RawContact
, which is not practical at all. Users should
be able to create contacts with just an email or phone number, without a name. This library follows
the native Contacts app behavior, which also disregards this rule =P
Rule 3 is intentionally ignored. There are two kinds of data;
a. those that are defined in the Contacts Provider (e.g. name, email, phone number, etc) b. those that are defined by other apps (e.g. custom data from other apps)
This library allows modification of native data kinds and custom data kinds. Native data kinds should obviously be modifiable as it is the entire reason why the Contacts Provider exposes these data kinds to us in the first place. The question is, should this library provide functions for modifying (insert, update, delete) custom data defined by other apps/services (e.g. Google Contacts, WhatsApp, etc)? The answer to that will be determined when the time comes to support custom data from other apps in the future... Probably, yes!
For more info, read Integrate custom data from other apps.
When you have an instance of Contact
, you have complete access to data stored in it.
To access data of a Contact with only one RawContact,
val contact: Contact
val rawContact: RawContact = contact.rawContacts.first()
Log.d(
"Contact",
"""
ID: ${contact.id}
Lookup Key: ${contact.lookupKey}
Display name: ${contact.displayNamePrimary}
Display name alt: ${contact.displayNameAlt}
Photo Uri: ${contact.photoUri}
Thumbnail Uri: ${contact.photoThumbnailUri}
Last updated: ${contact.lastUpdatedTimestamp}
Starred?: ${contact.options?.starred}
Send to voicemail?: ${contact.options?.sendToVoicemail}
Ringtone: ${contact.options?.customRingtone}
Addresses: ${rawContact.addresses}
Emails: ${rawContact.emails}
Events: ${rawContact.events}
Group memberships: ${rawContact.groupMemberships}
IMs: ${rawContact.ims}
Name: ${rawContact.name}
Nickname: ${rawContact.nickname}
Note: ${rawContact.note}
Organization: ${rawContact.organization}
Phones: ${rawContact.phones}
Relations: ${rawContact.relations}
SipAddress: ${rawContact.sipAddress}
Websites: ${rawContact.websites}
""".trimIndent()
// Photo require separate blocking function calls.
)
To access data of a Contact with possibly more than one RawContact, we can use ContactData.kt
extensions to make our life easier,
val contact: Contact
Log.d(
"Contact",
"""
ID: ${contact.id}
Lookup Key: ${contact.lookupKey}
Display name: ${contact.displayNamePrimary}
Display name alt: ${contact.displayNameAlt}
Photo Uri: ${contact.photoUri}
Thumbnail Uri: ${contact.photoThumbnailUri}
Last updated: ${contact.lastUpdatedTimestamp}
Starred?: ${contact.options?.starred}
Send to voicemail?: ${contact.options?.sendToVoicemail}
Ringtone: ${contact.options?.customRingtone}
Aggregate data from all RawContacts of the contact
-----------------------------------
Addresses: ${contact.addressList()}
Emails: ${contact.emailList()}
Events: ${contact.eventList()}
Group memberships: ${contact.groupMembershipList()}
IMs: ${contact.imList()}
Names: ${contact.nameList()}
Nicknames: ${contact.nicknameList()}
Notes: ${contact.noteList()}
Organizations: ${contact.organizationList()}
Phones: ${contact.phoneList()}
Relations: ${contact.relationList()}
SipAddresses: ${contact.sipAddressList()}
Websites: ${contact.websiteList()}
-----------------------------------
""".trimIndent()
// There are also aggregate data functions that return a sequence instead of a list.
)
Each Contact may have more than one of the following data if the Contact is made up of 2 or more RawContacts; name, nickname, note, organization, sip address.
To get the list of all data kinds of a particular Contact or RawContact,
contact.dataList()
rawContact.dataList()
ℹ️ For more info on how to easily aggregate data from all RawContacts in a Contact, read Convenience functions.
ℹ️ To learn more about the Contact lookup key, read about Contact lookup key vs ID.
ℹ️ To look into the actual Contacts Provider tables, read Debug the Contacts Provider tables.
All Entity
in this library are Redactable
, which indicates that there could be sensitive private
user data that could be redacted, for legal purposes. If you are logging contact data in production
to remote data centers for analytics or crash reporting, then it is important to redact certain
parts of every contact's data.
For more info, read Redact entities and API input and output in production.
Syncing contact data, including groups, are done automatically by the Contacts Provider depending on the account sync settings.
For more info, read Sync contact data across devices.
An entry of each of the following data kinds are automatically created for all contacts, if not provided;
GroupMembership
, underlying value defaults to the account's default system groupName
, underlying value defaults to nullNickname
, underlying value defaults to nullNote
, underlying value defaults to null
This automatic creation occur automatically in the background (typically after creation) only for RawContacts that are associated with an Account. If a valid account is provided, membership to the (auto add) system group is automatically created immediately by the Contacts Provider at the time of creation. The name, nickname, and note are automatically created at a later time.
ℹ️ Query APIs in this library do not return blanks in results. In this case, the
Name
,Nickname
, andNote
will not be included in the RawContact because their primary values are all null. Blanks are also ignored on insert and deleted on update. For more info, read about Blank data.
If a valid account is not provided, no entries of the above are automatically created.