Flutter plugin to read, create, update, delete and observe native contacts on Android and iOS, with group support, vCard support, and contact permission handling.
For an example app using the added features of this fork see flutter_contacts_and_accounts_demo
We have not changed the existing examples ( example/
and example_full/
) much, so we could recommend to use or demo app instead for reference.
Our integration tests lie in flutter_contacts_and_accounts_demo/integration_test. We recommend taking a look at them, since this shows how you can expect different functions to behave.
We created and fixed some platform-specific functionality for Android specifically when working on raw contacts.
The original library contained confusing Documentation when it came to the concept of raw contacts and ids. Specifically it referred to "raw account ids, even though accounts don't have ids in the Android Contact tables, and there is no such thing as a raw account.
We fixed this documentation to more accurately reflect the contents of https://developer.android.com/guide/topics/providers/contacts-provider#RawContactBasics
In general we took care to add accurate Documentation to all functions so we encourage to read those for the functions you plan to use.
updateRawContact
: Updates a raw contact by deleting that raw contacts properties and re-adding them- Intended to replace
updateContact
as that would delete all all raw contacts associated with that unified contact (same contact_id, but arbitrary raw_contact_id) but only re-insert the first raw contact (with the new properties) - This could potentially also lead to the deletion of that contact on a remote service.
- So we strongly advice to always use
updateRawContact
and never useupdateContact
on Android.
- Intended to replace
deleteRawContacts
deletes raw contacts based on a list ofraw_cotnact_ids
insertContact
actually already inserted raw contacts (and returned the corresponding unified contact). Updated checks and description
- General Properties every contact has:
- Read/ write for
source_id
(String that uniquely identifies this row to its source account.) - Read
lastUpdatedTimestamp
(due to Android specific restrictions this can likely not be retrieved for deleted contacts)
- Read/ write for
In Android, for a given contact in the contacts
table there will be one or multiple raw contacts in the raw_contacts
table.
And for every raw contact in the raw contacts table there will be several properties in the data
table.
Every row is one property of a raw contact. The mimetype row determines what kind of property it is (Phone Number, Email Address) and 15 data rows hold the actual data.
We have added queryCustomDataRows
, insertCustomDataRow
, updateCustomDataRows
and deleteCustomDataRows
functions to manually interact with such data rows.
This can be useful for custom mime-types. Data rows with custom mime-types is how messenger apps such as WhatsApp, Telegram or Signal add the "Message +12345", "Call +123345", etc buttons to your contacts.
- getRawContacts
- Convenience method that returns raw contacts (ignores value of
config.returnUnifiedContacts
) - Can set account property to restrict results to just raw contacts of that account
- Convenience method that returns raw contacts (ignores value of
getRawContactByRawId
gets a single raw contactgetDeleted
gets "soft deleted" contactsgetDirty
gets raw contacts marked as dirty
Added config.behaveAsSyncAdapter
option: On Android, whenever you modify a raw contact (note that a unified contact is just a "merged" representation of one or multiple raw contacts, a unified contact does not have properties itself) a dirty
flag is set, and when you delete a raw contact, the properties in the data table are deleted, but the raw contact in the raw_contacts table remains, and receives the deleted
flag.
This is so that a sync adapter can then upload these changes to whatever online sync service it provides. If you write a sync adapter want to avoid setting those flags (how else could you differentiate between changes you made at sync at changes the user made)
If config.behaveAsSyncAdapter
is set to true, and you use the rawContact-CRUD methods laid out in "New Functions", these flags will not be set.
Also we provide
clearDirtyContacts
which clears thedirty
flags for all given contacts
These are things we haven't fixed (yet)
- Do not use
updateContact
(see "New Functions" above) contact.id
will sometimes correspond tocontact_id
and sometimes toraw_contact_id
depending on the value ofconfig.returnUnifiedContacts
.insertContact
will always return the corresponding unified contact, ignoring the value ofconfig.returnUnifiedContact
- The Object model should does not reflect how the different contact tables relate on android. However we wont change this until we verified the inner workings of the iOS side of the plugin.
- A unified contact should have a list of contributing raw contacts
- A raw contact should have both a
contact_id
and araw_contact_id
, furthermore it should have the mime-types list and exactly one account - An account has only a name and a type (no mime-types and no ids!)
- Add the following key/value pair to your app's
Info.plist
(for iOS):<plist version="1.0"> <dict> ... <key>NSContactsUsageDescription</key> <string>Reason we need access to the contact list</string> </dict> </plist>
- Add the following
<uses-permissions>
tags to your app'sAndroidManifest.xml
(for Android):<manifest xmlns:android="http://schemas.android.com/apk/res/android" ...> <uses-permission android:name="android.permission.READ_CONTACTS"/> <uses-permission android:name="android.permission.WRITE_CONTACTS"/> <application ...> ...
- On iOS13+ you can only access notes if your app is
entitled by Apple
so notes are disabled by default. If you get entitlement, enable them via
FlutterContacts.config.includeNotesOnIos13AndAbove = true;
- On both iOS and Android there is a concept of raw and unified contacts. A
single person might have two raw contacts (for example from Gmail and from iCloud) but
will be merged into a single view called a unified contact. In a contact app you
typically want unified contacts, so this is what's returned by default. You can get
raw contacts instead via
FlutterContacts.config.returnUnifiedContacts = false;
- CRUD for Raw Contacts
- Change
Contact
class to more accurately reflect the unified to raw contact relationship (*)- Would need to first look into iOS side of things to not make things more complicated down the line
- Get contacts of a specified account
- Get Dirty/ Get Deleted Contacts
- Fix wrong documentation
- Custom data rows
- Insert
- Query
- Delete
- Update [Convenience]
- Create contact with custom data in one call [Convenience/ Performance]
- Create "advanced insert" function for this
- read source_id and lastUpdatedTime
- write source_id
- behaveAsSyncAdapter
- Fix "contact-not-showing-up" bug
- Look into what they do with groups and photos and if that needs any fixing
- Automated Testing especially for the newly added functions
- Support for partial updates (either by some sort of diff input or by checking against what is stored instead of delete+insert) [Convenience?/ Performance?]
- Also publish contacts_and_accounts_demo app as an example app of the new features
Code todos :
- Get deleted timestamp: work in progress
- Make old dangerous update method unavailable/ depreacated on android
- Groups are only deleted when withGroups flag is true. However groups are always inserted, irregardless of that flag.
- Photo is deleted and reinserted the photo on every update (even if photo is not changed) Is there a better way for this?
- Refactor so that raw contacts will have both contact_id and raw_contact_id and create a new update starred method that updates the starred status and doesn't touch anything else. (See (*))
- In
buildOpsForContact
(used for insert and update): If I am seeing this correctly contact_id is not set. Could this lead to the updated contact becoming "decoupled" from the unified contact? I could set this to contact.id, but first I have to make sure that contact.id is always a contact_id, and not a raw_contact_id.