# Convenience functions

This library provides some nice-to-have extensions in the `contacts.core.utils` package. I will be 
going over some of them in this page.

> ℹ️ Functions in the util package that are used directly by other APIs such as result APIs are not 
> discussed here.

## Contact data getter and setters

Contacts can be made up of one or more RawContacts. In the case that a Contact has two or more
RawContacts, getting/setting RawContact data may be a bit of a hassle, requiring loops or iterators,

```kotlin
// get all emails from all RawContacts belonging to the Contact
val contactEmails = contact.rawContacts.flatMap { it.emails }
// add an email to the first RawContact belonging to the Contact
contact.mutableCopy().rawContacts.first().emails.add(NewEmail())
```

> ℹ️ For more info, read about [API Entities](./../entities/about-api-entities.md).

To simplify things, getter/setter extensions are provided in the **`ContactData.kt`** file,

```kotlin
// get all emails from all RawContacts belonging to the Contact
val contactEmailSequence = contact.emails()
val contactEmailList = contact.emailList()
// add an email to the first RawContact belonging to the Contact
contact.mutableCopy().addEmail {
    address = "test@email.com"
}
```

> ℹ️ Newer versions of the Android Open Source Project Contacts app and the Google Contacts app 
> shows data coming from all RawContacts in a Contact details screen. However, they only allow 
> editing a single RawContact and not the aggregate Contact in a single screen to avoid confusion. 
> With this in mind, feel free to use the getter extensions but be very careful with using the
> setters!

## Mutable and New RawContact data setters

Getting data from RawContacts is straightforward. You have direct access to their properties. The
same goes for setting data. 

```kotlin
val rawContactEmails = rawContact.emails
rawContact.mutableCopy().addEmail(
    NewEmail().apply{
        address = "abc@alphabet.com"
        type = EmailEntity.Type.WORK
    }
)
```

Still, there are some setter extensions provided in `MutableRawContactData.kt` and 
`NewRawContactData.kt` that can add some sugar to your syntax.

```kotlin
rawContact.mutableCopy().addEmail {
    address = "abc@alphabet.com"
    type = EmailEntity.Type.WORK
}
```

The setter functions in this section and in the "Contact data getter and setters" section also 
uphold the redacted state of the mutable Contact/RawContact. We setting or adding a property using
these extensions, the property being passed will be redacted if the Contact/RawContact it is being
added to is redacted.

> ℹ️ For more info, read [Redact entities and API input and output in production](./../entities/redact-apis-and-entities.md).

## Getting all data of a Contact or RawContact

There may be instances where you want to get all available data of a Contact or RawContact instance.
In such cases, you can use extensions in `ExistingContactData.kt`, `ExistingRawContactData.kt`, and
`NewRawContactData.kt`.

To get a sequence of all data kinds of a Contact or RawContact,

```kotlin
contact.data()
rawContact.data()
```

To get it as a list,

```kotlin
contact.dataList()
rawContact.dataList()
```

## Getting the parent Contact of a RawContact or Data

Using the `Query` API, it is easy to get the parent Contact of a RawContact or Data,

```kotlin
val contactOfRawContact = contactsApi.query().where { Contact.Id equalTo rawContact.contactId }.find().firstOrNull()
val contactOfData = contactsApi.query().where { Contact.Id equalTo data.contactId }.find().firstOrNull()
```

> ℹ️ For more info, read [Query contacts (advanced)](./../basics/query-contacts-advanced.md).

To shorten things, you can use the extensions in `RawContactContact.kt` and `DataContact.kt`,

```kotlin
val contactOfRawContact = rawContact.contact(contactsApi)
val contactOfData = data.contact(contactsApi)
```

On a similar note, to get the parent RawContact of a Data using the extensions in `DataRawContact.kt`,

```kotlin
val rawContactOfData = data.rawContact(contactsApi)
```

These are blocking calls so you might want to do them outside the UI thread.

> ℹ️ For more info, read [Execute work outside of the UI thread using coroutines](./../async/async-execution-coroutines.md).

## Refresh Contact, RawContact, and Data references

In-memory references to these entities could become inaccurate due to changes in the database that
could occur in your app, other apps, or by the Contacts Provider. If you need to get the most
up-to-date reference of an entity from the database, you could do it using the `Query` and 
`DataQuery` APIs,

```kotlin
val contactFromDb = contactsApi.query().where { Contact.Id equalTo contactInMemory.id }.find().firstOrNull()
val rawContactFromDb = contactsApi.query().where { RawContact.Id equalTo rawContactInMemory.id }.find()
    .firstOrNull()
    ?.rawContacts
    ?.find { it.id == rawContactInMemory.id }
val dataFromDb = contactsApi.data().query().where { DataId equalTo dataInMemory.id }.find().firstOrNull()
```

To shorten things, you can use extensions in `ContactRefresh.kt`, `RawContactRefresh.kt`, and
`DataRefresh.kt`,

```kotlin
val contactFromDb = contactInMemory.refresh(contactsApi)
val rawContactFromDb = rawContactInMemory.refresh(contactsApi)
val dataFromDb = dataInMemory.refresh(contactsApi)
```

These are blocking calls so you might want to do them outside the UI thread.

> ℹ️ For more info, read [Execute work outside of the UI thread using coroutines](./../async/async-execution-coroutines.md).

## Get/set Data read-only

To set the read-only property of all data of a `NewRawContact` in one function call, you can use 
extensions in `NewRawContactData.kt`,

```kotlin
newRawContact.setDataAsReadOnly(true)
```

To check if any `ExistingDataEntity` is read-only you can use extensions in `DataIsReadOnly.kt`,

```kotlin
val isReadOnly = existingDataEntity.isReadOnly(contactsApi)
```

To check if multiple `ExistingDataEntity`s are read-only,

```kotlin
val isReadOnlyMap = existingDataEntities.isReadOnlyMap(contactsApi)
```

These are blocking calls so you might want to do them outside the UI thread.

> ℹ️ For more info, read [Execute work outside of the UI thread using coroutines](./../async/async-execution-coroutines.md).

> The value of `android.provider.ContactsContract.DataColumns.IS_READ_ONLY` is not a property of
> `ExistingDataEntity` because including that particular column in the query projection array causes 
> an exception. However, it is still possible to use the column in selection/WHERE clauses. Thus, 
> allowing us to create this extension function =)

> ℹ️ For more info, read [API Entities | Read-only data](./../entities/about-api-entities.md#read-only-data).

## Sort Contacts by data fields

The `Query` and `BroadQuery` APIs allows you to sort Contacts based on fields in the Contacts table
such as `Id` and `DisplayNamePrimary`, 

```kotlin
val sortedContacts = query.orderBy(ContactsFields.DisplayNamePrimary.desc(ignoreCase = true))
```

If you want to sort Contacts based on Data fields (e.g. email), you are unable to use the query 
APIs provided in this library to do so. However, if you have a list of Contacts in memory, you can
use the extensions in `ContactsComparator.kt` to build a `Comparator` to use for sorting,

```kotlin
val sortedContacts = unsortedContacts.sortedWith(
    Fields.Email.Address.desc(ignoreCase = true).contactsComparator()
)
```

You can also specify multiple fields for sorting,

```kotlin
val sortedContacts = unsortedContacts.sortedWith(
    setOf(
        Fields.Contact.Options.Starred.desc(),
        Fields.Contact.DisplayNamePrimary.asc(ignoreCase = false),
        Fields.Email.Address.asc()
    ).contactsComparator()
)
```

## Get the Group of a GroupMembership

The `GroupsQuery` allows you to get groups from a set of group Ids,

```kotlin
val group = contactsApi.groups().query().where { Id equalTo groupMembership.groupId }.find().firstOrNull()
val groups = contactsApi.groups().query().where { Id `in` groupMemberships.map { it.groupId } }.find()
```

To shorten things, you can use the extensions in `GroupMembershipGroup.kt`,

```kotlin
val group = groupMembership.group(contactsApi)
val groups = groupMemberships.groups(contactsApi)
```

These are blocking calls so you might want to do them outside the UI thread.

> ℹ️ For more info, read [Execute work outside of the UI thread using coroutines](./../async/async-execution-coroutines.md).