The fastest, most complete Flutter plugin for native contacts 🚀
Get, create, update, and delete contacts with comprehensive property support. Includes groups, accounts, vCard import/export, native dialogs, real-time change listeners, permissions management, and platform-specific APIs across Android, iOS, and macOS.
💬 Have feedback on the new v2? Share it by opening an issue on GitHub!
- 🏆 Benchmarks: up to 7x faster reads, 100x faster bulk operations
- 📈 Scalable - handles 10,000+ contacts smoothly via batch operations
- 🛡️ Read/write with comprehensive property support
- 🔍 Efficient filtering by name, phone, email, group, and more
- 👥 Accounts & Groups for multi-account contact management
- 📇 vCard import/export supporting all properties and formats
- 🔔 Real-time change listeners with change tracking
- 🎨 Native dialogs for picking, viewing, editing contacts
- 📱 Android-specific APIs - Blocked Numbers, Ringtones, SIM contacts
- 🌍 Cross-platform - Android, iOS, and macOS
dependencies:
flutter_contacts: ^2.0.0Android (android/app/src/main/AndroidManifest.xml):
<uses-permissionandroid:name="android.permission.READ_CONTACTS"/>
<uses-permissionandroid:name="android.permission.WRITE_CONTACTS"/>iOS (ios/Runner/Info.plist):
<key>NSContactsUsageDescription</key>
<string>We need access to your contacts to...</string>iOS Notes Entitlement
To access contact notes on iOS, add the com.apple.developer.contacts.notes entitlement and set FlutterContacts.config.enableIosNotes = true. See Apple's documentation.
import'package:flutter_contacts/flutter_contacts.dart';
// Request permissionsfinal status =awaitFlutterContacts.permissions.request(PermissionType.readWrite);
if (status ==PermissionStatus.granted) {
// Get all contacts (fast - defaults to IDs and display names only)List<Contact> contacts =awaitFlutterContacts.getAll();
// Get a specific contact with all propertiesContact? contact =awaitFlutterContacts.get(
contacts.first.id!,
properties:ContactProperties.all,
);
}📂 See example/ for a minimal example, or flutter_contacts_example for a full-fledged contacts app.
| Category | API | Purpose |
|---|---|---|
| Core CRUD | FlutterContacts.get(), .getAll(), .create(), .createAll(), .update(), .updateAll(), .delete(), .deleteAll() | Contact operations |
| Feature APIs | FlutterContacts.accounts, .groups, .permissions, .vCard, .native, .config | Specialized features |
| Platform APIs | FlutterContacts.sim (Android), .profile (Android/macOS), .blockedNumbers (Android), .ringtones (Android) | Platform-specific |
| Streams | FlutterContacts.onDatabaseChange, .onContactChange | Real-time notifications |
By default, get() and getAll() fetch only ID and display name. Specify properties for additional fields.
// List view: display name is always fetched, add thumbnailsawaitFlutterContacts.getAll(
properties: {ContactProperty.photoThumbnail},
);
// Detail view: all propertiesawaitFlutterContacts.get(contactId, properties:ContactProperties.all);Only fetched properties can be updated. This prevents accidental data loss.
// Fetch name + phone onlyvar contact =awaitFlutterContacts.get(id, properties: {ContactProperty.name, ContactProperty.phone});
// Only name and phone are saved; email changes would be ignored
contact = contact.copyWith(name:Name(first:'Jane'));
awaitFlutterContacts.update(contact);Unified Contact Model
On Android, multiple "raw contacts" from different accounts are automatically merged into unified contacts. On iOS, the same merging happens behind the scenes. The plugin provides a single, consistent API across both platforms.
Accounts & Groups
Accounts (containers on iOS) organize contacts by source - Google, iCloud, local device. Groups (labels on Android) organize contacts within an account. When creating a contact, you can specify which account to use; otherwise, it uses the default.
Tested with ~2,000 contacts on real devices (iPhone 7, Pixel 6):
Fetch all contacts: ID + display name (ms)
━━━━━━━━━━━━ iOS ━━━━━━━━━━━━ ━━━━━━━━━━ Android ━━━━━━━━━━━
◀─── faster slower ───▶ ◀─── faster slower ───▶
flutter_contacts v2 █████░░░░░░░░░░░░░░░░░░░ 117 ██████████░░░░░░░░░░░░░ 424
fast_contacts █████░░░░░░░░░░░░░░░░░░░ 127 ███████░░░░░░░░░░░░░░░░ 304
contacts_service_plus █████████████░░░░░░░░░░░ 462 ███████████████░░░░░░░░ 668
flutter_contacts v1 ███████████████████████░ 803 ███████████████████████ 1075
| V2 vs V1 | iOS | Android |
|---|---|---|
| Read speed | 1.6–7x faster | 2–5x faster |
| Bulk create | 4x faster | 5x faster |
| Bulk delete | 34x faster | 140x faster |
See the benchmark repository for detailed methodology and feature comparison.
// Single contact with all propertiesContact? contact =awaitFlutterContacts.get(id, properties:ContactProperties.all);
// All contacts with filteringList<Contact> contacts =awaitFlutterContacts.getAll(
properties: {ContactProperty.name, ContactProperty.phone},
filter:ContactFilter.name('John'),
limit:100,
);Both get() and getAll() default to fetching only ID + display name. Specify properties for additional fields.
Properties:name, phone, email, address, organization, website, socialMedia, event, relation, note, photoThumbnail, photoFullRes, favorite (Android), ringtone (Android), sendToVoicemail (Android), timestamp (Android), identifiers (Android). Use ContactProperties.all for all properties, or ContactProperties.allProperties to exclude photos. Only fetch the properties you need to improve query performance.
Filters:ContactFilter.name(), .phone(), .email(), .group(), .ids(). Phone/email filters use partial match on Android, full match on iOS.
String id =awaitFlutterContacts.create(contact, account: account);
List<String> ids =awaitFlutterContacts.createAll([contact1, contact2]);// Must fetch first (see Data Integrity above)awaitFlutterContacts.update(contact);
awaitFlutterContacts.updateAll([contact1, contact2]);awaitFlutterContacts.delete(id);
awaitFlutterContacts.deleteAll([id1, id2, id3]);🔐 Permissions
bool has =awaitFlutterContacts.permissions.has(PermissionType.read);
PermissionStatus status =awaitFlutterContacts.permissions.check(PermissionType.read);
PermissionStatus result =awaitFlutterContacts.permissions.request(PermissionType.readWrite);
awaitFlutterContacts.permissions.openSettings();Types:PermissionType.read, .write, .readWrite (on iOS, all are equivalent).
👤 Accounts
List<Account> accounts =awaitFlutterContacts.accounts.getAll();
Account? defaultAccount =awaitFlutterContacts.accounts.getDefault();
awaitFlutterContacts.accounts.showDefaultPicker(); // Android only👥 Groups
Group? group =awaitFlutterContacts.groups.get(groupId, withContactCount:true);
List<Group> groups =awaitFlutterContacts.groups.getAll(accounts: [account]);
Group created =awaitFlutterContacts.groups.create('Family', account: account);
awaitFlutterContacts.groups.update(group);
awaitFlutterContacts.groups.delete(groupId);
awaitFlutterContacts.groups.addContacts(groupId: groupId, contactIds: [id1, id2]);
awaitFlutterContacts.groups.removeContacts(groupId: groupId, contactIds: [id1]);
List<Group> contactGroups =awaitFlutterContacts.groups.getOf(contactId);📇 vCard
String vCard =FlutterContacts.vCard.export(contact, version:VCardVersion.v3);
String vCards =FlutterContacts.vCard.exportAll([contact1, contact2]);
List<Contact> contacts =FlutterContacts.vCard.import(vCardString);Supports vCard 2.1, 3.0, and 4.0.
🎨 Native Dialogs (Android & iOS)
No permissions required - uses system UI.
String? pickedId =awaitFlutterContacts.native.showPicker();
awaitFlutterContacts.native.showViewer(contactId);
String? editedId =awaitFlutterContacts.native.showEditor(contactId);
String? createdId =awaitFlutterContacts.native.showCreator(contact: prefill);🔔 Change Listeners
FlutterContacts.onDatabaseChange.listen((_) =>refreshUI());
FlutterContacts.onContactChange.listen((changes) {
for (final change in changes) {
print('${change.type}: ${change.contactId}');
}
});📱 SIM Contacts (Android only)
List<Contact> simContacts =awaitFlutterContacts.sim.get();SIM contacts are read-only and typically contain only name and phone.
👤 Profile / "Me" Card (Android & macOS)
Contact? me =awaitFlutterContacts.profile.get(properties:ContactProperties.all);Returns the device owner's profile (Android) or "Me" card (macOS). Not available on iOS.
🚫 Blocked Numbers (Android only)
Requires app to be default phone app. See example manifest.
if (awaitFlutterContacts.blockedNumbers.isAvailable()) {
bool isBlocked =awaitFlutterContacts.blockedNumbers.isBlocked('+1234567890');
List<Phone> blocked =awaitFlutterContacts.blockedNumbers.getAll();
awaitFlutterContacts.blockedNumbers.block('+1234567890');
awaitFlutterContacts.blockedNumbers.blockAll([number1, number2]);
awaitFlutterContacts.blockedNumbers.unblock('+1234567890');
awaitFlutterContacts.blockedNumbers.unblockAll([number1, number2]);
awaitFlutterContacts.blockedNumbers.openDefaultAppSettings();
}🔔 Ringtones (Android only)
String? uri =awaitFlutterContacts.ringtones.pick(type:RingtoneType.ringtone);
Ringtone? ringtone =awaitFlutterContacts.ringtones.get(uri, withMetadata:true);
List<Ringtone> all =awaitFlutterContacts.ringtones.getAll(type:RingtoneType.ringtone);
String? defaultUri =awaitFlutterContacts.ringtones.getDefaultUri(RingtoneType.ringtone);
awaitFlutterContacts.ringtones.setDefaultUri(RingtoneType.ringtone, uri);
awaitFlutterContacts.ringtones.play(uri);
awaitFlutterContacts.ringtones.stop();⚙️ Configuration
FlutterContacts.config.enableIosNotes =true; // Requires iOS entitlementSee lib/models/ for complete data model documentation.
A Contact contains an id, an auto-generated displayName, and lists of properties (phones, emails, addresses, etc.). Many properties support native labels (home, work, mobile, etc.) and custom labels.
Contact Structure
classContact {
finalString? id; // Stable identifierfinalString? displayName; // Read-only, auto-generatedfinalPhoto? photo;
finalName? name;
finalList<Phone> phones;
finalList<Email> emails;
finalList<Address> addresses;
finalList<Organization> organizations;
finalList<Website> websites;
finalList<SocialMedia> socialMedias;
finalList<Event> events; // Birthdays, anniversariesfinalList<Relation> relations;
finalList<Note> notes;
// Android-specific fields: isFavorite, customRingtone,// sendToVoicemail, timestamp, identifiersfinalAndroidData? android;
}Property Types
// NameName(
first:'John',
last:'Smith',
middle:'Q',
prefix:'Dr.',
suffix:'Jr.',
)
// Phone, Email, AddressPhone('555-1234')
Phone('555-1234', label:Label(PhoneLabel.mobile))
Email('john@example.com', label:Label(EmailLabel.work))
Address(
street:'123 Main St',
city:'New York',
state:'NY',
postalCode:'10001',
label:Label(AddressLabel.home),
)
// OrganizationOrganization(
organizationName:'FlutterCorp',
jobTitle:'Software Engineer',
departmentName:'Engineering',
)
// PhotoPhoto(fullSize: imageBytes)Address Usage: Prefer component fields (
street,city,state, etc.) when creating/editing. Theformattedfield is available when reading and is guaranteed to be present.
Photo Usage: When creating/updating, set
Photo(fullSize: imageBytes)- the system automatically generates the thumbnail. When reading, fetchContactProperty.photoThumbnail(fast) for contact lists,ContactProperty.photoFullRes(slower) for detail views.
Note: The examples above show common fields. See lib/models/ for the complete list of all available fields and properties.
Labels
Most properties support labels (home, work, mobile, etc.) and custom labels:
// Standard labelPhone('555-1234', label:Label(PhoneLabel.mobile))
// Custom labelPhone('555-1234', label:Label(PhoneLabel.custom, customLabel:'Emergency'))Platform Support: Some labels are platform-specific (e.g., PhoneLabel.appleWatch is iOS-only, PhoneLabel.workMobile is Android-only). Unsupported labels are automatically converted to custom labels with the original name preserved.
iOS & macOS
Not Available:
- APIs:
ringtones,blockedNumbers,sim,profile(iOS),native(macOS) - Properties:
favorite,ringtone,sendToVoicemail,timestamp,identifiers,debugData(all nested inandroidfield) - Fields:
Phone.isPrimary,Phone.normalizedNumber,Email.isPrimary,Address.poBox,Address.neighborhood,Organization.jobDescription,Organization.symbol,Organization.officeLocation - Some Android-specific labels (auto-converted to custom)
Limits: One organization, one note, one birthday per contact.
Filtering: Phone and email filters only support full match (not partial).
iOS Notes: Requires the com.apple.developer.contacts.notes entitlement and FlutterContacts.config.enableIosNotes = true. See Apple's documentation.
Android
Not Available:
- Fields:
Name.previousFamilyName,Address.isoCountryCode,Address.subAdministrativeArea,Address.subLocality - Some iOS-specific labels (auto-converted to custom)
Special Features:
- SIM Contacts: Read-only, typically name + phone only
- Blocked Numbers: Requires app to be default phone app
If you're using flutter_contacts v1:
| v1 | v2 |
|---|---|
FlutterContacts.getContacts() | FlutterContacts.getAll() |
FlutterContacts.getContact(id) | FlutterContacts.get(id) |
contact.insert() | FlutterContacts.create(contact) |
FlutterContacts.requestPermission() | FlutterContacts.permissions.request(PermissionType.readWrite) |
FlutterContacts.addListener() | FlutterContacts.onDatabaseChange.listen() |
contact.toVCard() | FlutterContacts.vCard.export(contact) |
PhoneLabel.mobile | Label(PhoneLabel.mobile) |
FlutterContacts.openExternalView(id) | FlutterContacts.native.showViewer(id) |
FlutterContacts.openExternalPick() | FlutterContacts.native.showPicker() |
FlutterContacts.openExternalEdit(id) | FlutterContacts.native.showEditor(id) |
FlutterContacts.openExternalInsert() | FlutterContacts.native.showCreator() |
We'd love your help making this plugin even better. Found a bug? Have a feature idea? Want to improve the docs?
- 🐛 Report an issue
- 💡 Suggest a feature
- 🔧 Submit a pull request
- 📖 Improve the documentation
Every contribution, big or small, makes a difference. Thank you for helping make Flutter Contacts better for everyone.
MIT License - see the LICENSE file for details.
Flutter Contacts is maintained in my free time. If this plugin has been helpful for your project, consider supporting its development. Your support helps me continue maintaining and improving it.
Special thanks to all contributors and users who have helped shape this plugin.
Made with ❤️ for the Flutter community