Skip to content

Latest commit

 

History

History
364 lines (273 loc) · 13.9 KB

about-api-entities.md

File metadata and controls

364 lines (273 loc) · 13.9 KB

API Entities

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!

Contacts Provider / ContactsContract Basic Concept

There are 3 main database tables used in dealing with contacts. These tables are all connected.

  1. Contacts
    • Rows representing different people.
    • E.G. John Doe
  2. RawContacts
  3. 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!

Contacts API Entities

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.

Contacts API Fields

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.

Data kinds count restrictions

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.

Read-only data

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.

Data integrity

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;

  1. Always add a Name for every RawContact.
  2. Always link new Data to their parent RawContact.
  3. Change data only for those raw contacts that you own.
  4. 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.

Accessing contact data

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.

Redacting entities

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

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.


Developer notes (or for advanced users)

Automatic data kinds creation

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 group
  • Name, underlying value defaults to null
  • Nickname, underlying value defaults to null
  • Note, 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, and Note 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.