UNPKG

expo-contacts

Version:

Provides access to the phone's system contacts.

1,271 lines (1,194 loc) 32.9 kB
import { PermissionResponse, PermissionStatus, PermissionExpiration, UnavailabilityError, uuid, EventSubscription, } from 'expo-modules-core'; import { Platform, Share, type ShareOptions } from 'react-native'; import ExpoContacts from './ExpoContacts'; export const onContactsChangeEventName = 'onContactsChange'; export type ContactsPermissionResponse = PermissionResponse & { /** * Indicates if your app has access to the whole or only part of the contact library. Possible values are: * - `'all'` if the user granted your app access to the whole contact library * - `'limited'` if the user granted your app access only to selected contacts (only available on iOS 18+) * - `'none'` */ accessPrivileges?: 'all' | 'limited' | 'none'; }; export type CalendarFormatType = CalendarFormats | `${CalendarFormats}`; export type ContainerType = ContainerTypes | `${ContainerTypes}`; export type ContactType = ContactTypes | `${ContactTypes}`; export type FieldType = Fields | `${Fields}`; export type Date = { /** * Day. */ day: number; /** * Month - adjusted for JavaScript `Date` which starts at `0`. */ month: number; /** * Year. */ year?: number; /** * Unique ID. This value will be generated by the OS. */ id?: string; /** * Localized display name. */ label?: string; /** * Format for the date. This is provided by the OS, do not set this manually. */ format?: CalendarFormatType; }; export type Relationship = { /** * Localized display name. */ label: string; /** * Name of related contact. */ name?: string; /** * Unique ID. This value will be generated by the OS. */ id?: string; }; export type Email = { /** * Email address. */ email?: string; /** * Flag signifying if it is a primary email address. */ isPrimary?: boolean; /** * Localized display name. */ label: string; /** * Unique ID. This value will be generated by the OS. */ id?: string; }; export type PhoneNumber = { /** * Phone number. */ number?: string; /** * Flag signifying if it is a primary phone number. */ isPrimary?: boolean; /** * Phone number without format. * @example * `8674305` */ digits?: string; /** * Country code. * @example * `us` */ countryCode?: string; /** * Localized display name. */ label: string; /** * Unique ID. This value will be generated by the OS. */ id?: string; }; export type Address = { /** * Street name. */ street?: string; /** * City name. */ city?: string; /** * Country name */ country?: string; /** * Region or state name. */ region?: string; /** * Neighborhood name. */ neighborhood?: string; /** * Local post code. */ postalCode?: string; /** * P.O. Box. */ poBox?: string; /** * [Standard country code](https://www.iso.org/iso-3166-country-codes.html). */ isoCountryCode?: string; /** * Localized display name. */ label: string; /** * Unique ID. This value will be generated by the OS. */ id?: string; }; /** * @platform ios */ export type SocialProfile = { /** * Name of social app. */ service?: string; /** * Localized profile name. */ localizedProfile?: string; /** * Web URL. */ url?: string; /** * Username in social app. */ username?: string; /** * Username ID in social app. */ userId?: string; /** * Localized display name. */ label: string; /** * Unique ID. This value will be generated by the OS. */ id?: string; }; export type InstantMessageAddress = { /** * Name of instant messaging app. */ service?: string; /** * Username in IM app. */ username?: string; /** * Localized name of app. */ localizedService?: string; /** * Localized display name. */ label: string; /** * Unique ID. This value will be generated by the OS. */ id?: string; }; export type UrlAddress = { /** * Localized display name. */ label: string; /** * Web URL. */ url?: string; /** * Unique ID. This value will be generated by the OS. */ id?: string; }; // @needs-audit /** * Information regarding thumbnail images. * > On Android you can get dimensions using [`Image.getSize`](https://reactnative.dev/docs/image#getsize) method. */ export type Image = { /** * A local image URI. * > **Note**: If you have a remote URI, download it first using [`FileSystem.downloadAsync`](/versions/latest/sdk/filesystem/#filesystemdownloadasyncuri-fileuri-options). */ uri?: string; /** * Image width. * @platform ios */ width?: number; /** * Image height * @platform ios */ height?: number; /** * Image as Base64 string. */ base64?: string; }; /** * Base contact type without ID. For better type safety, consider using: * - `Contact` when creating new contacts (no ID needed) * - `ExistingContact` when working with contacts returned from the system (ID guaranteed) */ export type Contact = { /** * Denoting a person or company. */ contactType: ContactType; /** * Full name with proper format. */ name: string; /** * Given name. */ firstName?: string; /** * Middle name */ middleName?: string; /** * Last name. */ lastName?: string; /** * Maiden name. */ maidenName?: string; /** * Dr., Mr., Mrs., and so on. */ namePrefix?: string; /** * Jr., Sr., and so on. */ nameSuffix?: string; /** * An alias to the proper name. */ nickname?: string; /** * Pronunciation of the first name. */ phoneticFirstName?: string; /** * Pronunciation of the middle name. */ phoneticMiddleName?: string; /** * Pronunciation of the last name. */ phoneticLastName?: string; /** * Organization the entity belongs to. */ company?: string; /** * Job description. */ jobTitle?: string; /** * Job department. */ department?: string; /** * Additional information. * > The `note` field [requires your app to request additional entitlements](https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_developer_contacts_notes). * > The Expo Go app does not contain those entitlements, so in order to test this feature you will need to [request the entitlement from Apple](https://developer.apple.com/contact/request/contact-note-field), * > set the [`ios.accessesContactNotes`](./../config/app/#accessescontactnotes) field in **app config** to `true`, and [create your development build](/develop/development-builds/create-a-build/). */ note?: string; /** * Used for efficient retrieval of images. */ imageAvailable?: boolean; /** * Thumbnail image. On iOS it size is set to 320×320px, on Android it may vary. */ image?: Image; /** * Raw image without cropping, usually large. */ rawImage?: Image; /** * Birthday information in Gregorian format. */ birthday?: Date; /** * A labeled list of other relevant user dates in Gregorian format. */ dates?: Date[]; /** * Names of other relevant user connections. */ relationships?: Relationship[]; /** * Email addresses. */ emails?: Email[]; /** * Phone numbers. */ phoneNumbers?: PhoneNumber[]; /** * Locations. */ addresses?: Address[]; /** * Instant messaging connections. */ instantMessageAddresses?: InstantMessageAddress[]; /** * Associated web URLs. */ urlAddresses?: UrlAddress[]; /** * Birthday that doesn't conform to the Gregorian calendar format, interpreted based on the [calendar `format`](#date) setting. * @platform ios */ nonGregorianBirthday?: Date; /** * Social networks. * @platform ios */ socialProfiles?: SocialProfile[]; /** * Whether the contact is starred. * @platform android */ isFavorite?: boolean; }; /** * Type for existing contacts returned from the system - guarantees the id field is present. */ export type ExistingContact = Contact & { /** * Immutable identifier used for querying and indexing. This value will be generated by the OS when the contact is created. */ id: string; }; /** * The return value for queried contact operations like `getContactsAsync`. */ export type ContactResponse = { /** * An array of contacts that match a particular query. */ data: ExistingContact[]; /** * This will be `true` if there are more contacts to retrieve beyond what is returned. */ hasNextPage: boolean; /** * This will be `true` if there are previous contacts that weren't retrieved due to `pageOffset` limit. */ hasPreviousPage: boolean; }; export type ContactSort = `${SortTypes}`; /** * Used to query contacts from the user's device. */ export type ContactQuery = { /** * The max number of contacts to return. If skipped or set to `0` all contacts will be returned. */ pageSize?: number; /** * The number of contacts to skip before gathering contacts. */ pageOffset?: number; /** * If specified, the defined fields will be returned. If skipped, all fields will be returned. */ fields?: FieldType[]; /** * Sort method used when gathering contacts. */ sort?: ContactSort; /** * Get all contacts whose name contains the provided string (not case-sensitive). */ name?: string; /** * Get contacts with a matching ID or array of IDs. */ id?: string | string[]; /** * Get all contacts that belong to the group matching this ID. * @platform ios */ groupId?: string; /** * Get all contacts that belong to the container matching this ID. * @platform ios */ containerId?: string; /** * Prevent unification of contacts when gathering. * @default false * @platform ios */ rawContacts?: boolean; }; /** * Denotes the functionality of a native contact form. * @platform ios */ export type FormOptions = { /** * The properties that will be displayed when viewing a contact. */ displayedPropertyKeys?: FieldType[]; /** * The message displayed under the name of the contact. Only applies when editing an existing contact. */ message?: string; /** * Used if contact doesn't have a name defined. */ alternateName?: string; /** * Allows for contact mutation. */ allowsEditing?: boolean; /** * Actions like share, add, create. */ allowsActions?: boolean; /** * Show or hide the similar contacts. */ shouldShowLinkedContacts?: boolean; /** * Present the new contact controller. If set to `false` the unknown controller will be shown. */ isNew?: boolean; /** * The name of the left bar button. Only applies when editing an existing contact. */ cancelButtonTitle?: string; /** * Prevents the controller from animating in. */ preventAnimation?: boolean; /** * The parent group for a new contact. */ groupId?: string; }; /** * Used to query native contact groups. * @platform ios */ export type GroupQuery = { /** * Query the group with a matching ID. */ groupId?: string; /** * Query all groups matching a name. */ groupName?: string; /** * Query all groups that belong to a certain container. */ containerId?: string; }; /** * A parent to contacts. A contact can belong to multiple groups. Here are some query operations you can perform: * - Child Contacts: `getContactsAsync({ groupId })` * - Groups From Container: `getGroupsAsync({ containerId })` * - Groups Named: `getContainersAsync({ groupName })` * @platform ios */ export type Group = { /** * Immutable id representing the group. */ name?: string; /** * The editable name of a group. */ id?: string; }; /** * Used to query native contact containers. * @platform ios */ export type ContainerQuery = { /** * Query all the containers that parent a contact. */ contactId?: string; /** * Query all the containers that parent a group. */ groupId?: string; /** * Query all the containers that matches ID or an array od IDs. */ containerId?: string | string[]; }; export type Container = { name: string; id: string; type: ContainerType; }; export { PermissionStatus, PermissionResponse, PermissionExpiration }; /** * Returns whether the Contacts API is enabled on the current device. This method does not check the app permissions. * @returns A promise that fulfills with a `boolean`, indicating whether the Contacts API is available on the current device. It always resolves to `false` on web. */ export async function isAvailableAsync(): Promise<boolean> { return !!ExpoContacts.getContactsAsync; } // @docsMissing export async function shareContactAsync( contactId: string, message: string, shareOptions: ShareOptions = {} ): Promise<any> { if (Platform.OS === 'ios') { const url = await writeContactToFileAsync({ id: contactId, }); return await Share.share( { url, message, }, shareOptions ); } else if (!ExpoContacts.shareContactAsync) { throw new UnavailabilityError('Contacts', 'shareContactAsync'); } return await ExpoContacts.shareContactAsync(contactId, message); } /** * Return a list of contacts that fit a given criteria. You can get all of the contacts by passing no criteria. * @param contactQuery Object used to query contacts. * @return A promise that fulfills with `ContactResponse` object returned from the query. * @example * ```js * const { data } = await Contacts.getContactsAsync({ * fields: [Contacts.Fields.Emails], * }); * * if (data.length > 0) { * const contact = data[0]; * console.log(contact); * } * ``` */ export async function getContactsAsync(contactQuery: ContactQuery = {}): Promise<ContactResponse> { if (!ExpoContacts.getContactsAsync) { throw new UnavailabilityError('Contacts', 'getContactsAsync'); } return await ExpoContacts.getContactsAsync(contactQuery); } export async function getPagedContactsAsync( contactQuery: ContactQuery = {} ): Promise<ContactResponse> { const { pageSize, ...nOptions } = contactQuery; if (pageSize && pageSize <= 0) { throw new Error('Error: Contacts.getPagedContactsAsync: `pageSize` must be greater than 0'); } return await getContactsAsync({ ...nOptions, pageSize, }); } /** * Used for gathering precise data about a contact. Returns a contact matching the given `id`. * @param id The ID of a system contact. * @param fields If specified, the fields defined will be returned. When skipped, all fields will be returned. * @return A promise that fulfills with `Contact` object with ID matching the input ID, or `undefined` if there is no match. * @example * ```js * const contact = await Contacts.getContactByIdAsync('161A368D-D614-4A15-8DC6-665FDBCFAE55'); * if (contact) { * console.log(contact); * } * ``` */ export async function getContactByIdAsync( id: string, fields?: FieldType[] ): Promise<ExistingContact | undefined> { if (!ExpoContacts.getContactsAsync) { throw new UnavailabilityError('Contacts', 'getContactsAsync'); } if (id == null) { throw new Error('Error: Contacts.getContactByIdAsync: id is required'); } else { const results = await ExpoContacts.getContactsAsync({ pageSize: 1, pageOffset: 0, fields, id, }); if (results && results.data && results.data.length > 0) { return results.data[0]; } } return undefined; } /** * Creates a new contact and adds it to the system. * > **Note**: For Android users, the Expo Go app does not have the required `WRITE_CONTACTS` permission to write to Contacts. * > You will need to create a [development build](/develop/development-builds/create-a-build/) and add permission in there manually to use this method. * @param contact A contact with the changes you wish to persist. The `id` parameter will not be used. * @param containerId @tag-ios The container that will parent the contact. * @return A promise that fulfills with ID of the new system contact. * @example * ```js * const contact = { * [Contacts.Fields.FirstName]: 'Bird', * [Contacts.Fields.LastName]: 'Man', * [Contacts.Fields.Company]: 'Young Money', * }; * const contactId = await Contacts.addContactAsync(contact); * ``` */ export async function addContactAsync(contact: Contact, containerId?: string): Promise<string> { if (!ExpoContacts.addContactAsync) { throw new UnavailabilityError('Contacts', 'addContactAsync'); } return await ExpoContacts.addContactAsync(contact, containerId); } /** * Mutate the information of an existing contact. Due to an iOS bug, `nonGregorianBirthday` field cannot be modified. * @param contact A contact object including the wanted changes. Contact `id` is required. * @return A promise that fulfills with ID of the updated system contact if mutation was successful. * @example * ```js * const contact = { * id: '161A368D-D614-4A15-8DC6-665FDBCFAE55', * [Contacts.Fields.FirstName]: 'Drake', * [Contacts.Fields.Company]: 'Young Money', * }; * await Contacts.updateContactAsync(contact); * ``` */ export async function updateContactAsync( contact: { id: string } & Partial<ExistingContact> ): Promise<string> { if (!ExpoContacts.updateContactAsync) { throw new UnavailabilityError('Contacts', 'updateContactAsync'); } return await ExpoContacts.updateContactAsync(contact); } // @needs-audit /** * Delete a contact from the system. * @param contactId ID of the contact you want to delete. * @example * ```js * await Contacts.removeContactAsync('161A368D-D614-4A15-8DC6-665FDBCFAE55'); * ``` * @platform ios */ export async function removeContactAsync(contactId: string): Promise<any> { if (!ExpoContacts.removeContactAsync) { throw new UnavailabilityError('Contacts', 'removeContactAsync'); } return await ExpoContacts.removeContactAsync(contactId); } /** * Query a set of contacts and write them to a local URI that can be used for sharing. * @param contactQuery Used to query contact you want to write. * @return A promise that fulfills with shareable local URI, or `undefined` if there was no match. * @example * ```js * const localUri = await Contacts.writeContactToFileAsync({ * id: '161A368D-D614-4A15-8DC6-665FDBCFAE55', * }); * Share.share({ url: localUri, message: 'Call me!' }); * ``` */ export async function writeContactToFileAsync( contactQuery: ContactQuery = {} ): Promise<string | undefined> { if (!ExpoContacts.writeContactToFileAsync) { throw new UnavailabilityError('Contacts', 'writeContactToFileAsync'); } return await ExpoContacts.writeContactToFileAsync(contactQuery); } // @needs-audit /** * Present a native form for manipulating contacts. * @param contactId The ID of a system contact. * @param contact A contact with the changes you want to persist. * @param formOptions Options for the native editor. * @example * ```js * await Contacts.presentFormAsync('161A368D-D614-4A15-8DC6-665FDBCFAE55'); * ``` */ export async function presentFormAsync( contactId?: string | null, contact?: Contact | null, formOptions: FormOptions = {} ): Promise<any> { if (!ExpoContacts.presentFormAsync) { throw new UnavailabilityError('Contacts', 'presentFormAsync'); } if (Platform.OS === 'ios') { const adjustedOptions = formOptions; if (contactId) { if (contact) { contact = undefined; console.log( 'Expo.Contacts.presentFormAsync: You should define either a `contact` or a `contactId` but not both.' ); } if (adjustedOptions.isNew !== undefined) { console.log( 'Expo.Contacts.presentFormAsync: `formOptions.isNew` is not supported with `contactId`' ); } } return await ExpoContacts.presentFormAsync(contactId, contact, adjustedOptions); } else { return await ExpoContacts.presentFormAsync(contactId, contact, formOptions); } } // iOS Only /** * Add a group to a container. * @param groupId The group you want to target. * @param containerId The container you want to add membership to. * @example * ```js * await Contacts.addExistingGroupToContainerAsync( * '161A368D-D614-4A15-8DC6-665FDBCFAE55', * '665FDBCFAE55-D614-4A15-8DC6-161A368D' * ); * ``` * @platform ios */ export async function addExistingGroupToContainerAsync( groupId: string, containerId: string ): Promise<any> { if (!ExpoContacts.addExistingGroupToContainerAsync) { throw new UnavailabilityError('Contacts', 'addExistingGroupToContainerAsync'); } return await ExpoContacts.addExistingGroupToContainerAsync(groupId, containerId); } /** * Create a group with a name, and add it to a container. If the container is `undefined`, the default container will be targeted. * @param name Name of the new group. * @param containerId The container you to add membership to. * @return A promise that fulfills with ID of the new group. * @example * ```js * const groupId = await Contacts.createGroupAsync('Sailor Moon'); * ``` * @platform ios */ export async function createGroupAsync(name?: string, containerId?: string): Promise<string> { if (!ExpoContacts.createGroupAsync) { throw new UnavailabilityError('Contacts', 'createGroupAsync'); } name = name || uuid.v4(); if (!containerId) { containerId = await getDefaultContainerIdAsync(); } return await ExpoContacts.createGroupAsync(name, containerId); } /** * Change the name of an existing group. * @param groupName New name for an existing group. * @param groupId ID of the group you want to edit. * @example * ```js * await Contacts.updateGroupName('Expo Friends', '161A368D-D614-4A15-8DC6-665FDBCFAE55'); * ``` * @platform ios */ export async function updateGroupNameAsync(groupName: string, groupId: string): Promise<any> { if (!ExpoContacts.updateGroupNameAsync) { throw new UnavailabilityError('Contacts', 'updateGroupNameAsync'); } return await ExpoContacts.updateGroupNameAsync(groupName, groupId); } // @needs-audit /** * Delete a group from the device. * @param groupId ID of the group you want to remove. * @example * ```js * await Contacts.removeGroupAsync('161A368D-D614-4A15-8DC6-665FDBCFAE55'); * ``` * @platform ios */ export async function removeGroupAsync(groupId: string): Promise<any> { if (!ExpoContacts.removeGroupAsync) { throw new UnavailabilityError('Contacts', 'removeGroupAsync'); } return await ExpoContacts.removeGroupAsync(groupId); } // @needs-audit /** * Add a contact as a member to a group. A contact can be a member of multiple groups. * @param contactId ID of the contact you want to edit. * @param groupId ID for the group you want to add membership to. * @example * ```js * await Contacts.addExistingContactToGroupAsync( * '665FDBCFAE55-D614-4A15-8DC6-161A368D', * '161A368D-D614-4A15-8DC6-665FDBCFAE55' * ); * ``` * @platform ios */ export async function addExistingContactToGroupAsync( contactId: string, groupId: string ): Promise<any> { if (!ExpoContacts.addExistingContactToGroupAsync) { throw new UnavailabilityError('Contacts', 'addExistingContactToGroupAsync'); } return await ExpoContacts.addExistingContactToGroupAsync(contactId, groupId); } // @needs-audit /** * Remove a contact's membership from a given group. This will not delete the contact. * @param contactId ID of the contact you want to remove. * @param groupId ID for the group you want to remove membership of. * @example * ```js * await Contacts.removeContactFromGroupAsync( * '665FDBCFAE55-D614-4A15-8DC6-161A368D', * '161A368D-D614-4A15-8DC6-665FDBCFAE55' * ); * ``` * @platform ios */ export async function removeContactFromGroupAsync( contactId: string, groupId: string ): Promise<any> { if (!ExpoContacts.removeContactFromGroupAsync) { throw new UnavailabilityError('Contacts', 'removeContactFromGroupAsync'); } return await ExpoContacts.removeContactFromGroupAsync(contactId, groupId); } // @needs-audit /** * Query and return a list of system groups. * @param groupQuery Information regarding which groups you want to get. * @example * ```js * const groups = await Contacts.getGroupsAsync({ groupName: 'sailor moon' }); * const allGroups = await Contacts.getGroupsAsync({}); * ``` * @return A promise that fulfills with array of groups that fit the query. * @platform ios */ export async function getGroupsAsync(groupQuery: GroupQuery): Promise<Group[]> { if (!ExpoContacts.getGroupsAsync) { throw new UnavailabilityError('Contacts', 'getGroupsAsync'); } return await ExpoContacts.getGroupsAsync(groupQuery); } /** * Presents a native contact picker to select a single contact from the system. On Android, the `READ_CONTACTS` permission is required. You can * obtain this permission by calling the [`Contacts.requestPermissionsAsync()`](#contactsrequestpermissionsasync) method. On iOS, no permissions are * required to use this method. * @return A promise that fulfills with a single `Contact` object if a contact is selected or `null` if no contact is selected (when selection is canceled). */ export async function presentContactPickerAsync(): Promise<ExistingContact | null> { if (!ExpoContacts.presentContactPickerAsync) { throw new UnavailabilityError('Contacts', 'presentContactPickerAsync'); } return await ExpoContacts.presentContactPickerAsync(); } /** * Get the default container's ID. * @return A promise that fulfills with default container ID. * @example * ```js * const containerId = await Contacts.getDefaultContainerIdAsync(); * ``` * @platform ios */ export async function getDefaultContainerIdAsync(): Promise<string> { if (!ExpoContacts.getDefaultContainerIdentifierAsync) { throw new UnavailabilityError('Contacts', 'getDefaultContainerIdentifierAsync'); } return await ExpoContacts.getDefaultContainerIdentifierAsync(); } /** * Query a list of system containers. * @param containerQuery Information used to gather containers. * @return A promise that fulfills with array of containers that fit the query. * @example * ```js * const allContainers = await Contacts.getContainersAsync({ * contactId: '665FDBCFAE55-D614-4A15-8DC6-161A368D', * }); * ``` * @platform ios */ export async function getContainersAsync(containerQuery: ContainerQuery): Promise<Container[]> { if (!ExpoContacts.getContainersAsync) { throw new UnavailabilityError('Contacts', 'getContainersAsync'); } return await ExpoContacts.getContainersAsync(containerQuery); } /** * Checks user's permissions for accessing contacts data. * @return A promise that resolves to a [ContactsPermissionResponse](#contactspermissionresponse) object. */ export async function getPermissionsAsync(): Promise<ContactsPermissionResponse> { if (!ExpoContacts.getPermissionsAsync) { throw new UnavailabilityError('Contacts', 'getPermissionsAsync'); } return await ExpoContacts.getPermissionsAsync(); } /** * Asks the user to grant permissions for accessing contacts data. * @return A promise that resolves to a [ContactsPermissionResponse](#contactspermissionresponse) object. */ export async function requestPermissionsAsync(): Promise<ContactsPermissionResponse> { if (!ExpoContacts.requestPermissionsAsync) { throw new UnavailabilityError('Contacts', 'requestPermissionsAsync'); } return await ExpoContacts.requestPermissionsAsync(); } /** * Presents a modal which allows the user to select which contacts the app has access to. * Using this function is reasonable only when the app has "limited" permissions. * @return A promise that resolves with an array of contact identifiers that were newly granted to the app. * Contacts which the app lost access to are not listed. On platforms other than iOS and below 18.0, the promise rejects immediately. * @platform ios 18.0+ */ export async function presentAccessPickerAsync(): Promise<string[]> { return await ExpoContacts.presentAccessPickerAsync(); } /** * Adds a listener for contact changes. The listener will be called whenever contacts are added, updated, or deleted. * * **Platform differences:** * - **Android**: 5-7 second delay - uses `ContentObserver` with inherent system delays * - **iOS**: Immediate response - uses `CNContactStoreDidChangeNotification` * * The Android delay is a system limitation that affects all apps using `ContentObserver` for contacts. * This delay is by design to batch notifications for better performance and battery life. * For more immediate updates, you can also listen to app state changes and refresh * contacts when the app comes to the foreground. This ensures users see the latest contacts when * returning from the native Contacts app. * * @param listener The function that will be executed when contacts change. * This function accepts no arguments. * * @returns A subscription object with a `remove` method to stop listening. * @example * ```jsx * const subscription = Contacts.addContactChangeListener(() => { * console.log('Contacts changed - refreshing contact list'); * // Refresh your contact list when changes are detected * loadContacts(); * }); * * // Later, remove the listener * subscription.remove(); * ``` */ export function addContactsChangeListener(listener: () => void): EventSubscription { if (!ExpoContacts.addListener) { throw new UnavailabilityError('Contacts', 'addContactChangeListener'); } const eventSubscription = ExpoContacts.addListener(onContactsChangeEventName, listener); return { remove: () => { eventSubscription?.remove(); }, }; } /** * Possible fields to retrieve for a contact. */ export enum Fields { ID = 'id', ContactType = 'contactType', Name = 'name', FirstName = 'firstName', MiddleName = 'middleName', LastName = 'lastName', MaidenName = 'maidenName', NamePrefix = 'namePrefix', NameSuffix = 'nameSuffix', Nickname = 'nickname', PhoneticFirstName = 'phoneticFirstName', PhoneticMiddleName = 'phoneticMiddleName', PhoneticLastName = 'phoneticLastName', Birthday = 'birthday', /** * @platform ios */ NonGregorianBirthday = 'nonGregorianBirthday', Emails = 'emails', PhoneNumbers = 'phoneNumbers', Addresses = 'addresses', /** * @platform ios */ SocialProfiles = 'socialProfiles', InstantMessageAddresses = 'instantMessageAddresses', UrlAddresses = 'urlAddresses', Company = 'company', JobTitle = 'jobTitle', Department = 'department', ImageAvailable = 'imageAvailable', Image = 'image', RawImage = 'rawImage', ExtraNames = 'extraNames', Note = 'note', Dates = 'dates', Relationships = 'relationships', /** * @platform android */ IsFavorite = 'isFavorite', } /** * This format denotes the common calendar format used to specify how a date is calculated in `nonGregorianBirthday` fields. */ export enum CalendarFormats { Gregorian = 'gregorian', /** * @platform ios */ Buddhist = 'buddhist', /** * @platform ios */ Chinese = 'chinese', /** * @platform ios */ Coptic = 'coptic', /** * @platform ios */ EthiopicAmeteMihret = 'ethiopicAmeteMihret', /** * @platform ios */ EthiopicAmeteAlem = 'ethiopicAmeteAlem', /** * @platform ios */ Hebrew = 'hebrew', /** * @platform ios */ ISO8601 = 'iso8601', /** * @platform ios */ Indian = 'indian', /** * @platform ios */ Islamic = 'islamic', /** * @platform ios */ IslamicCivil = 'islamicCivil', /** * @platform ios */ Japanese = 'japanese', /** * @platform ios */ Persian = 'persian', /** * @platform ios */ RepublicOfChina = 'republicOfChina', /** * @platform ios */ IslamicTabular = 'islamicTabular', /** * @platform ios */ IslamicUmmAlQura = 'islamicUmmAlQura', } /** * @platform ios */ export enum ContainerTypes { /** * A local non-iCloud container. */ Local = 'local', /** * In association with email server. */ Exchange = 'exchange', /** * With cardDAV protocol used for sharing. */ CardDAV = 'cardDAV', /** * Unknown container. */ Unassigned = 'unassigned', } export enum SortTypes { /** * The user default method of sorting. * @platform android */ UserDefault = 'userDefault', /** * Sort by first name in ascending order. */ FirstName = 'firstName', /** * Sort by last name in ascending order. */ LastName = 'lastName', /** * No sorting should be applied. */ None = 'none', } export enum ContactTypes { /** * Contact is a human. */ Person = 'person', /** * Contact is group or company. */ Company = 'company', }