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.

pub package License: MIT

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 - Boolean
  • starred - Boolean (Android only, null on iOS)
  • lastModifiedMillis - Timestamp (Android only, null on 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, requires includeNotes: true and entitlement)

Permission Handling

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

  1. Use minimizeData: true for name + phone only (10x faster on large contact lists)
  2. Use onlyWithPhone: true to skip contacts without numbers
  3. 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:

  1. Open ios/Runner.xcworkspace in Xcode
  2. Select Runner target → Signing & Capabilities
  3. Click + Capability → Search "Contacts"
  4. 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


Roadmap

  • EventChannel progress UI helper widget
  • Contact write support (create/update/delete)
  • Contact groups support
  • SIM contacts filter (Android)
  • Contact usage frequency (where available)


Developer

Developed by Gunjan Sharma
Full Stack System Architect & Tech Lead

LinkedIn Email

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!

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.