Flutter Simple Contact
A lightweight (~2KB), transparent Flutter plugin for fetching contacts from Android and iOS with zero third-party dependencies. Built specifically for fintech and enterprise applications that require full control over contact data handling.
Why Flutter Simple Contact?
- ** Zero Third-Party Dependencies**: Direct native implementation using Android ContactsContract and iOS Contacts framework
- ** Lightweight**: Only ~5KB - no bloated dependencies
- ** Enterprise-Ready**: Built for fintech/banking apps requiring audit trails
- ** Transparent**: Clean, readable source code - audit every line
- ** Flexible**: Minimal mode (name + phone) or full metadata (emails, addresses, organizations)
- ** Privacy-First**: Built-in permission handling with configurable UI flows
- ** Production-Tested**: Works on Android (API 21+) and iOS (13.0+)
Features
✅ Fetch unified or raw contacts
✅ Built-in permission handling (optional)
✅ Filter by phone/photo availability
✅ Sort alphabetically or by last updated
✅ Minimize data mode (name + phones only)
✅ Rich metadata mode (emails, addresses, websites, organizations)
✅ Progress events via EventChannel (optional)
✅ Typed API or raw maps for flexibility
✅ iOS notes support (with entitlement flag)
Installation
Add to your pubspec.yaml:
dependencies:
flutter_simple_contact: ^0.0.1
Run:
flutter pub get
Platform Setup
Android
Add to android/app/src/main/AndroidManifest.xml:
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.READ_CONTACTS"/>
</manifest>
Minimum SDK: API 21 (Android 5.0)
iOS
Add to ios/Runner/Info.plist:
<key>NSContactsUsageDescription</key>
<string>We need access to your contacts to help you share referral links with friends and family.</string>
Minimum Version: iOS 13.0
Usage
1. Minimal Mode (Name + Phone Only)
Perfect for referral systems, contact pickers, or minimal data collection:
import 'package:flutter_simple_contact/flutter_simple_contact.dart';
Future<void> fetchBasicContacts() async {
final raw = await FlutterSimpleContact.fetchContactsRaw(
options: {
"handlePermission": true,
"mode": "unified",
"sort": "alphabetical",
"filters": {
"onlyWithPhone": true, // Skip contacts without phone numbers
"onlyStarred": false,
"onlyWithPhoto": false,
},
"minimizeData": true, // Only name + phones (fast & minimal)
"advanced": {
"enableProgressEvents": false,
"includeNotes": false,
},
},
);
final contacts = (raw["contacts"] as List? ?? []).map((c) {
return {
"name": c["displayName"] ?? "Unknown",
"phones": c["phones"] ?? [],
};
}).toList();
print("Fetched ${contacts.length} contacts");
}
Output:
[
{
"name": "John Doe",
"phones": [{ "number": "+1234567890", "label": "Mobile" }]
}
]
2. Full Metadata Mode
Get comprehensive contact data (emails, addresses, websites, organizations):
Future<void> fetchDetailedContacts() async {
final raw = await FlutterSimpleContact.fetchContactsRaw(
options: {
"handlePermission": true,
"mode": "unified",
"sort": "alphabetical",
"filters": {
"onlyWithPhone": false,
"onlyStarred": false,
"onlyWithPhoto": false,
},
"minimizeData": false, // Fetch all metadata
"advanced": {
"enableProgressEvents": false,
"includeNotes": false, // Requires iOS entitlement
},
},
);
final contacts = raw["contacts"] as List? ?? [];
for (var contact in contacts) {
print("Name: ${contact['displayName']}");
print("Phones: ${contact['phones']}");
print("Emails: ${contact['emails']}");
print("Addresses: ${contact['addresses']}");
print("Websites: ${contact['websites']}");
print("Organizations: ${contact['organizations']}");
}
}
Output:
{
"id": "ABC123",
"displayName": "Jane Smith",
"phones": [
{ "number": "+9876543210", "label": "Work", "normalizedNumber": null }
],
"emails": [{ "address": "[email protected]", "label": "Work" }],
"addresses": [
{
"street": "123 Main St",
"city": "San Francisco",
"state": "CA",
"postalCode": "94105",
"country": "USA",
"isoCountryCode": "US",
"label": "Home"
}
],
"websites": [{ "url": "https://example.com", "label": "Homepage" }],
"organizations": [
{
"company": "Acme Corp",
"department": "Engineering",
"jobTitle": "Engineer"
}
],
"hasPhoto": true,
"starred": false,
"lastModifiedMillis": 1704672000000
}
3. Typed API (Strongly Typed)
For apps preferring type safety:
import 'package:flutter_simple_contact/flutter_simple_contact.dart';
Future<void> fetchTypedContacts() async {
final result = await FlutterSimpleContact.fetchContacts(
options: SimpleFetchOptions(
handlePermission: true,
mode: SimpleContactMode.unified,
sort: SimpleSort.alphabetical,
filters: const SimpleFetchFilters(onlyWithPhone: true),
minimizeData: false,
advanced: const SimpleAdvancedOptions(
enableProgressEvents: false,
includeNotes: false,
),
),
);
if (result.ok) {
for (var contact in result.contacts) {
print("${contact.displayName}: ${contact.phones.length} phones");
}
} else {
print("Error: ${result.errorMessage}");
}
}
Configuration Options
Main Options
| Option | Type | Default | Description |
|---|---|---|---|
handlePermission |
bool |
true |
Automatically request contacts permission |
mode |
String |
"unified" |
"unified" or "raw" (Android only) |
sort |
String |
"none" |
"none", "alphabetical", "lastUpdatedDesc" |
minimizeData |
bool |
false |
If true, only fetch name + phones (faster) |
Filters
| Filter | Type | Default | Description |
|---|---|---|---|
onlyWithPhone |
bool |
false |
Skip contacts without phone numbers |
onlyStarred |
bool |
false |
Only starred/favorite contacts (Android only) |
onlyWithPhoto |
bool |
false |
Only contacts with photos |
Advanced Options
| Option | Type | Default | Description |
|---|---|---|---|
enableProgressEvents |
bool |
false |
Enable progress EventChannel (for large contact lists) |
includeNotes |
bool |
false |
Fetch contact notes (iOS: requires com.apple.developer.contacts.notes entitlement) |
Returned Fields
Always Available (minimizeData: true or false)
id- Contact identifier (String)displayName- Full name (String)phones- Array of{number, label, normalizedNumber}hasPhoto- Booleanstarred- Boolean (Android only,nullon iOS)lastModifiedMillis- Timestamp (Android only,nullon iOS)
Additional Fields (minimizeData: false)
emails- Array of{address, label}addresses- Array of{street, city, state, postalCode, country, isoCountryCode, label}websites- Array of{url, label}organizations- Array of{company, department, jobTitle}(iOS) or{company, title, department}(Android)note- String (iOS only, requiresincludeNotes: trueand entitlement)
Permission Handling
Automatic (Recommended)
final raw = await FlutterSimpleContact.fetchContactsRaw(
options: {"handlePermission": true, /* ... */},
);
// Plugin automatically requests permission if needed
Manual
// 1. Request permission yourself using permission_handler or similar
// 2. Then fetch with handlePermission: false
final raw = await FlutterSimpleContact.fetchContactsRaw(
options: {"handlePermission": false, /* ... */},
);
Error Handling
final raw = await FlutterSimpleContact.fetchContactsRaw(
options: {/* ... */},
);
if (raw["ok"] == false) {
final status = raw["status"]; // "permission_denied", "error", etc.
final errorCode = raw["errorCode"];
final errorMessage = raw["errorMessage"];
print("Failed: $errorMessage ($errorCode)");
if (status == "permission_denied") {
// Show dialog: "Enable contacts permission in Settings"
}
}
Error Status Codes
| Status | Description |
|---|---|
permission_denied |
User denied permission |
error |
Native exception (see errorMessage for details) |
success |
Contacts fetched successfully |
Performance Tips
- Use
minimizeData: truefor name + phone only (10x faster on large contact lists) - Use
onlyWithPhone: trueto skip contacts without numbers - Enable progress events for large datasets (1000+ contacts)
// For 5000+ contacts, enable progress to avoid ANR/UI freezing
final raw = await FlutterSimpleContact.fetchContactsRaw(
options: {
"advanced": {"enableProgressEvents": true},
// ...
},
);
Platform Differences
| Feature | Android | iOS |
|---|---|---|
| Unified contacts | ✅ | ✅ |
| Raw contacts | ✅ | ❌ (not exposed by Apple) |
| Starred/favorites | ✅ | ❌ |
| Last modified timestamp | ✅ | ❌ |
| Notes | ✅ (no entitlement) | ⚠️ (requires entitlement) |
| All other fields | ✅ | ✅ |
iOS Notes Entitlement (Optional)
To enable includeNotes: true on iOS, add this to your Xcode project:
- Open
ios/Runner.xcworkspacein Xcode - Select
Runnertarget → Signing & Capabilities - Click + Capability → Search "Contacts"
- Enable Contacts Notes
Or manually edit ios/Runner/Runner.entitlements:
<key>com.apple.developer.contacts.notes</key>
<true/>
Without this entitlement, includeNotes: true will cause an "Unauthorized Keys" error.
Examples
See the /example folder for a complete demo app with:
- Minimal mode UI
- Detailed mode UI
- Permission flow examples
- Error handling
Run:
cd example
flutter run
Contributing
Contributions are welcome! This plugin intentionally has zero dependencies to maintain auditability for enterprise use. Please ensure:
- No third-party packages added for core contact fetching
- Code remains transparent and readable
- Tests pass on both Android and iOS
License
MIT License - see LICENSE file.
Support
- Issues: GitHub Issues
- Discussions: GitHub Discussions
Roadmap
EventChannel progress UI helper widgetContact write support (create/update/delete)Contact groups supportSIM contacts filter (Android)Contact usage frequency (where available)
Developer
Developed by Gunjan Sharma
Full Stack System Architect & Tech Lead
Note on This Package
This is a minimal, production-ready version focused on core contact fetching with zero dependencies. It's built to be lightweight, transparent, and auditable for enterprise/fintech applications.
Need Additional Features?
If your use case isn't covered or you need extended functionality (e.g., contact write operations, advanced filters, SIM-specific contacts, contact groups), I'm happy to extend this package!
- Request a Feature: Open an Issue with detailed requirements
- Found a Bug?: Report it here
- Like this package?: Please ⭐ star the repo to show your support!
Your feedback and contributions help make this package better for everyone in the Flutter community.
Built with ❤️ for fintech and enterprise Flutter apps that need transparent, auditable contact access.