v2.0.2flutter_contacts
適用於 Android、iOS 與 macOS 的快速、完整聯絡人管理——所有欄位、群組、帳戶、vCard、原生對話方塊、監聽器、SIM 通訊錄與號碼封鎖。
最快、最完整的原生聯絡人 Flutter 外掛 🚀
{"sdk":"flutter"}{"sdk":"flutter"}^6.0.0以下為英文專案原文快照,最新內容請造訪 GitHub。
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 version pub likes pub dm license
💜 Sponsored by Quis Contacts — a modern, private contacts app from the makers of this plugin.
dependencies:
flutter_contacts: ^2.0.0
Android (android/app/src/main/AndroidManifest.xml):
<uses-permission android:name="android.permission.READ_CONTACTS"/>
<uses-permission android:name="android.permission.WRITE_CONTACTS"/>
iOS (ios/Runner/Info.plist):
<key>NSContactsUsageDescription</key>
<string>We need access to your contacts to...</string>
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 permissions
final status = await FlutterContacts.permissions.request(PermissionType.readWrite);
if (status == PermissionStatus.granted) {
// Get all contacts (fast - defaults to IDs and display names only)
List<Contact> contacts = await FlutterContacts.getAll();
// Get a specific contact with all properties
Contact? contact = await FlutterContacts.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 thumbnails
await FlutterContacts.getAll(
properties: {ContactProperty.photoThumbnail},
);
// Detail view: all properties
await FlutterContacts.get(contactId, properties: ContactProperties.all);
Only fetched properties can be updated. This prevents accidental data loss.
// Fetch name + phone only
var contact = await FlutterContacts.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'));
await FlutterContacts.update(contact);
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 (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 properties
Contact? contact = await FlutterContacts.get(id, properties: ContactProperties.all);
// All contacts with filtering
List<Contact> contacts = await FlutterContacts.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 = await FlutterContacts.create(contact, account: account);
List<String> ids = await FlutterContacts.createAll([contact1, contact2]);
// Must fetch first (see Data Integrity above)
await FlutterContacts.update(contact);
await FlutterContacts.updateAll([contact1, contact2]);
await FlutterContacts.delete(id);
await FlutterContacts.deleteAll([id1, id2, id3]);
bool has = await FlutterContacts.permissions.has(PermissionType.read);
PermissionStatus status = await FlutterContacts.permissions.check(PermissionType.read);
PermissionStatus result = await FlutterContacts.permissions.request(PermissionType.readWrite);
await FlutterContacts.permissions.openSettings();
Types: PermissionType.read, .write, .readWrite (on iOS, all are equivalent).
List<Account> accounts = await FlutterContacts.accounts.getAll();
Account? defaultAccount = await FlutterContacts.accounts.getDefault();
await FlutterContacts.accounts.showDefaultPicker(); // Android only
Group? group = await FlutterContacts.groups.get(groupId, withContactCount: true);
List<Group> groups = await FlutterContacts.groups.getAll(accounts: [account]);
Group created = await FlutterContacts.groups.create('Family', account: account);
await FlutterContacts.groups.update(group);
await FlutterContacts.groups.delete(groupId);
await FlutterContacts.groups.addContacts(groupId: groupId, contactIds: [id1, id2]);
await FlutterContacts.groups.removeContacts(groupId: groupId, contactIds: [id1]);
List<Group> contactGroups = await FlutterContacts.groups.getOf(contactId);
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.
System UI for picking, viewing, editing, and creating contacts.
Contact? picked = await FlutterContacts.native.showPicker(
properties: {ContactProperty.phone},
);
await FlutterContacts.native.showViewer(contactId);
String? editedId = await FlutterContacts.native.showEditor(contactId);
String? createdId = await FlutterContacts.native.showCreator(contact: prefill);
Permissions:
showPicker() and showCreator() need no contacts permission on either platform.showPicker(properties: …) is permissionless on iOS. On Android it requires READ_CONTACTS and throws PlatformException if missing.showViewer() and showEditor() are permissionless on Android. On iOS they require NSContactsUsageDescription in Info.plist and a granted read permission.FlutterContacts.onDatabaseChange.listen((_) => refreshUI());
FlutterContacts.onContactChange.listen((changes) {
for (final change in changes) {
print('${change.type}: ${change.contactId}');
}
});
List<Contact> simContacts = await FlutterContacts.sim.get();
SIM contacts are read-only and typically contain only name and phone.
Contact? me = await FlutterContacts.profile.get(properties: ContactProperties.all);
Returns the device owner's profile (Android) or "Me" card (macOS). Not available on iOS.
Requires app to be default phone app. See example manifest.
if (await FlutterContacts.blockedNumbers.isAvailable()) {
bool isBlocked = await FlutterContacts.blockedNumbers.isBlocked('+1234567890');
List<Phone> blocked = await FlutterContacts.blockedNumbers.getAll();
await FlutterContacts.blockedNumbers.block('+1234567890');
await FlutterContacts.blockedNumbers.blockAll([number1, number2]);
await FlutterContacts.blockedNumbers.unblock('+1234567890');
await FlutterContacts.blockedNumbers.unblockAll([number1, number2]);
await FlutterContacts.blockedNumbers.openDefaultAppSettings();
}
String? uri = await FlutterContacts.ringtones.pick(type: RingtoneType.ringtone);
Ringtone? ringtone = await FlutterContacts.ringtones.get(uri, withMetadata: true);
List<Ringtone> all = await FlutterContacts.ringtones.getAll(type: RingtoneType.ringtone);
String? defaultUri = await FlutterContacts.ringtones.getDefaultUri(RingtoneType.ringtone);
await FlutterContacts.ringtones.setDefaultUri(RingtoneType.ringtone, uri);
await FlutterContacts.ringtones.play(uri);
await FlutterContacts.ringtones.stop();
FlutterContacts.config.enableIosNotes = true; // Requires iOS entitlement
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.
class Contact {
final String? id; // Stable identifier
final String? displayName; // Read-only, auto-generated
final Photo? photo;
final Name? name;
final List<Phone> phones;
final List<Email> emails;
final List<Address> addresses;
final List<Organization> organizations;
final List<Website> websites;
final List<SocialMedia> socialMedias;
final List<Event> events; // Birthdays, anniversaries
final List<Relation> relations;
final List<Note> notes;
// Android-specific fields: isFavorite, customRingtone,
// sendToVoicemail, timestamp, identifiers
final AndroidData? android;
}
// Name
Name(
first: 'John',
last: 'Smith',
middle: 'Q',
prefix: 'Dr.',
suffix: 'Jr.',
)
// Phone, Email, Address
Phone('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),
)
// Organization
Organization(
organizationName: 'FlutterCorp',
jobTitle: 'Software Engineer',
departmentName: 'Engineering',
)
// Photo
Photo(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.
Most properties support labels (home, work, mobile, etc.) and custom labels:
// Standard label
Phone('555-1234', label: Label(PhoneLabel.mobile))
// Custom label
Phone('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.
Not Available:
ringtones, blockedNumbers, sim, profile (iOS), native (macOS)favorite, ringtone, sendToVoicemail, timestamp, identifiers, debugData (all nested in android field)Phone.isPrimary, Phone.normalizedNumber, Email.isPrimary, Address.poBox, Address.neighborhood, Organization.jobDescription, Organization.symbol, Organization.officeLocationLimits: 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.
Not Available:
Name.previousFamilyName, Address.isoCountryCode, Address.subAdministrativeArea, Address.subLocalitySpecial Features:
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?
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