Skip to content

Repository files navigation

📞 Flutter Contacts

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.

pub versionpub likespub dmlicense

💬 Have feedback on the new v2? Share it by opening an issue on GitHub!


⚡ Why Flutter Contacts?

  • 🏆 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

🚀 Quick Start

1. Add Dependency

dependencies:
flutter_contacts: ^2.0.0

2. Configure Permissions

Android (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.

3. Basic Usage

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.


🧠 Core Concepts

📐 API Structure

CategoryAPIPurpose
Core CRUDFlutterContacts.get(), .getAll(), .create(), .createAll(), .update(), .updateAll(), .delete(), .deleteAll()Contact operations
Feature APIsFlutterContacts.accounts, .groups, .permissions, .vCard, .native, .configSpecialized features
Platform APIsFlutterContacts.sim (Android), .profile (Android/macOS), .blockedNumbers (Android), .ringtones (Android)Platform-specific
StreamsFlutterContacts.onDatabaseChange, .onContactChangeReal-time notifications

⚡ Selective Fetching

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);

🔒 Data Integrity

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.


📊 Performance Benchmarks

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 V1iOSAndroid
Read speed1.6–7x faster2–5x faster
Bulk create4x faster5x faster
Bulk delete34x faster140x faster

See the benchmark repository for detailed methodology and feature comparison.


📚 Core API

Read

// 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.

Create

String id =awaitFlutterContacts.create(contact, account: account);
List<String> ids =awaitFlutterContacts.createAll([contact1, contact2]);

Update

// Must fetch first (see Data Integrity above)awaitFlutterContacts.update(contact);
awaitFlutterContacts.updateAll([contact1, contact2]);

Delete

awaitFlutterContacts.delete(id);
awaitFlutterContacts.deleteAll([id1, id2, id3]);

🔧 Feature APIs

🔐 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 entitlement

📋 Data Model

See 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. The formatted field 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, fetch ContactProperty.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.


⚠️ Platform Limitations

iOS & macOS

Not Available:

  • APIs: ringtones, blockedNumbers, sim, profile (iOS), native (macOS)
  • Properties: favorite, ringtone, sendToVoicemail, timestamp, identifiers, debugData (all nested in android field)
  • 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

🔄 Migrating from v1

If you're using flutter_contacts v1:

v1v2
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.mobileLabel(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()

🤝 Contributing

We'd love your help making this plugin even better. Found a bug? Have a feature idea? Want to improve the docs?

Every contribution, big or small, makes a difference. Thank you for helping make Flutter Contacts better for everyone.


📄 License

MIT License - see the LICENSE file for details.


💝 Support

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.

ko-fi


🙏 Acknowledgments

Special thanks to all contributors and users who have helped shape this plugin.


Made with ❤️ for the Flutter community

⭐ Star us on GitHub | 📖 Documentation | 🐛 Report Issues

About

No description, website, or topics provided.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages