flutter_contacts_pro 3.0.0 copy "flutter_contacts_pro: ^3.0.0" to clipboard
flutter_contacts_pro: ^3.0.0 copied to clipboard

Fast, complete contact management for Android, iOS & macOS — all fields, groups, accounts, vCards, native dialogs, listeners, SIM contacts, and number blocking.

example/lib/main.dart

// Comprehensive example demonstrating flutter_contacts plugin usage.
// Shows CRUD operations, native dialogs, permissions, and contact management.

import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_contacts_pro/flutter_contacts.dart';

void main() => runApp(
  MaterialApp(
    theme: ThemeData(useMaterial3: true, colorSchemeSeed: Colors.blue),
    home: const ContactListPage(),
  ),
);

class ContactListPage extends StatefulWidget {
  const ContactListPage({super.key});

  @override
  State<ContactListPage> createState() => _ContactListPageState();
}

class _ContactListPageState extends State<ContactListPage> {
  List<Contact>? _contacts;
  StreamSubscription? _sub;
  bool _denied = false;
  bool _loading = true;

  @override
  void initState() {
    super.initState();
    _init();
  }

  @override
  void dispose() {
    _sub?.cancel();
    super.dispose();
  }

  Future<void> _init() async {
    final status = await FlutterContacts.permissions.request(
      PermissionType.readWrite,
    );

    if (status != PermissionStatus.granted &&
        status != PermissionStatus.limited) {
      return setState(() {
        _denied = true;
        _loading = false;
      });
    }

    // Listen for contact changes
    _sub = FlutterContacts.onContactChange.listen((changes) {
      for (final change in changes) {
        debugPrint('Contact ${change.type.name}: ${change.contactId}');
      }
      _load();
    });

    _load();
  }

  Future<void> _load() async {
    setState(() => _loading = true);
    try {
      final contacts = await FlutterContacts.getAll(
        properties: {
          ContactProperty.name,
          ContactProperty.phone,
          ContactProperty.email,
          ContactProperty.photoThumbnail,
        },
      );
      setState(() {
        _contacts = contacts;
        _loading = false;
      });
    } catch (e) {
      debugPrint('Error loading contacts: $e');
      setState(() => _loading = false);
    }
  }

  Future<void> _createContactWithNativeDialog() async {
    // Create a contact with pre-filled data
    final newContact = Contact(
      name: Name(first: 'John', last: 'Doe'),
      phones: [Phone(number: '+1234567890')],
      emails: [Email(address: 'john.doe@example.com')],
    );

    // Show native contact creator dialog
    final contactId = await FlutterContacts.native.showCreator(
      contact: newContact,
    );

    if (contactId != null && mounted) {
      // Show success message
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(
          content: Text('Contact created successfully!'),
          backgroundColor: Colors.green,
        ),
      );

      // Optionally open the contact viewer
      await FlutterContacts.native.showViewer(contactId);

      // Reload contacts
      _load();
    } else if (mounted) {
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(
          content: Text('Contact creation cancelled'),
          backgroundColor: Colors.orange,
        ),
      );
    }
  }

  Future<void> _pickContact() async {
    final contactId = await FlutterContacts.native.showPicker();
    if (contactId != null && mounted) {
      Navigator.push(
        context,
        MaterialPageRoute(
          builder: (_) => ContactDetailPage(contactId: contactId),
        ),
      );
    }
  }

  void _openContactDetail(String contactId) {
    Navigator.push(
      context,
      MaterialPageRoute(
        builder: (_) => ContactDetailPage(contactId: contactId),
      ),
    );
  }

  @override
  Widget build(BuildContext context) => Scaffold(
    appBar: AppBar(
      title: const Text('Contacts'),
      actions: [
        IconButton(
          icon: const Icon(Icons.search),
          tooltip: 'Pick Contact',
          onPressed: _pickContact,
        ),
      ],
    ),
    body: _denied
        ? Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                const Icon(Icons.block, size: 64, color: Colors.red),
                const SizedBox(height: 16),
                const Text(
                  'Contact permission not granted',
                  style: TextStyle(fontSize: 18),
                ),
                const SizedBox(height: 8),
                ElevatedButton(
                  onPressed: () async {
                    await FlutterContacts.permissions.openSettings();
                  },
                  child: const Text('Open Settings'),
                ),
              ],
            ),
          )
        : _loading
        ? const Center(child: CircularProgressIndicator())
        : _contacts == null || _contacts!.isEmpty
        ? Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                const Icon(Icons.contacts, size: 64, color: Colors.grey),
                const SizedBox(height: 16),
                const Text(
                  'No contacts found',
                  style: TextStyle(fontSize: 18, color: Colors.grey),
                ),
              ],
            ),
          )
        : RefreshIndicator(
            onRefresh: _load,
            child: ListView.builder(
              itemCount: _contacts!.length,
              itemBuilder: (_, i) {
                final contact = _contacts![i];
                return ListTile(
                  leading: CircleAvatar(
                    backgroundImage: contact.photo?.thumbnail != null
                        ? MemoryImage(contact.photo!.thumbnail!)
                        : null,
                    child: contact.photo?.thumbnail == null
                        ? const Icon(Icons.person)
                        : null,
                  ),
                  title: Text(contact.displayName ?? '(No name)'),
                  subtitle: contact.phones.isNotEmpty
                      ? Text(contact.phones.first.number)
                      : contact.emails.isNotEmpty
                      ? Text(contact.emails.first.address)
                      : null,
                  // The 'favorite' getter does not exist on Contact, so we remove it.
                  trailing: null,
                  onTap: () => _openContactDetail(contact.id!),
                );
              },
            ),
          ),
    floatingActionButton: FloatingActionButton.extended(
      onPressed: _createContactWithNativeDialog,
      icon: const Icon(Icons.add),
      label: const Text('Create Contact'),
    ),
  );
}

class ContactDetailPage extends StatelessWidget {
  final String contactId;
  const ContactDetailPage({super.key, required this.contactId});

  Future<Contact?> _load() =>
      FlutterContacts.get(contactId, properties: ContactProperties.all);

  Future<void> _editContact(BuildContext context, Contact contact) async {
    final updatedId = await FlutterContacts.native.showEditor(contactId);
    if (updatedId != null && context.mounted) {
      Navigator.pop(context);
      Navigator.pushReplacement(
        context,
        MaterialPageRoute(
          builder: (_) => ContactDetailPage(contactId: updatedId),
        ),
      );
    }
  }

  Future<void> _viewContact(BuildContext context) async {
    await FlutterContacts.native.showViewer(contactId);
  }

  Future<void> _deleteContact(BuildContext context) async {
    final confirmed = await showDialog<bool>(
      context: context,
      builder: (context) => AlertDialog(
        title: const Text('Delete Contact'),
        content: const Text('Are you sure you want to delete this contact?'),
        actions: [
          TextButton(
            onPressed: () => Navigator.pop(context, false),
            child: const Text('Cancel'),
          ),
          TextButton(
            onPressed: () => Navigator.pop(context, true),
            style: TextButton.styleFrom(foregroundColor: Colors.red),
            child: const Text('Delete'),
          ),
        ],
      ),
    );

    if (confirmed == true && context.mounted) {
      try {
        await FlutterContacts.delete(contactId);
        if (context.mounted) {
          Navigator.pop(context);
          ScaffoldMessenger.of(context).showSnackBar(
            const SnackBar(
              content: Text('Contact deleted'),
              backgroundColor: Colors.green,
            ),
          );
        }
      } catch (e) {
        if (context.mounted) {
          ScaffoldMessenger.of(context).showSnackBar(
            SnackBar(
              content: Text('Error deleting contact: $e'),
              backgroundColor: Colors.red,
            ),
          );
        }
      }
    }
  }

  @override
  Widget build(BuildContext context) => FutureBuilder<Contact?>(
    future: _load(),
    builder: (context, snapshot) {
      if (snapshot.connectionState == ConnectionState.waiting) {
        return const Scaffold(body: Center(child: CircularProgressIndicator()));
      }

      final contact = snapshot.data;
      if (contact == null) {
        return Scaffold(
          appBar: AppBar(title: const Text('Contact')),
          body: const Center(child: Text('Contact not found')),
        );
      }

      final photo = contact.photo?.fullSize ?? contact.photo?.thumbnail;

      return Scaffold(
        appBar: AppBar(
          title: Text(contact.displayName ?? 'Contact'),
          actions: [
            IconButton(
              icon: const Icon(Icons.visibility),
              tooltip: 'View',
              onPressed: () => _viewContact(context),
            ),
            IconButton(
              icon: const Icon(Icons.edit),
              tooltip: 'Edit',
              onPressed: () => _editContact(context, contact),
            ),
            IconButton(
              icon: const Icon(Icons.delete),
              tooltip: 'Delete',
              onPressed: () => _deleteContact(context),
            ),
          ],
        ),
        body: ListView(
          children: [
            // Photo section
            if (photo != null)
              Container(
                height: 200,
                width: double.infinity,
                decoration: BoxDecoration(
                  image: DecorationImage(
                    image: MemoryImage(photo),
                    fit: BoxFit.cover,
                  ),
                ),
              )
            else
              Container(
                height: 200,
                width: double.infinity,
                color: Colors.grey[300],
                child: const Icon(Icons.person, size: 100, color: Colors.grey),
              ),

            // Name section
            if (contact.name != null) ...[
              _buildSection('Name', [
                if ((contact.name!.first ?? '').isNotEmpty)
                  _buildInfoRow('First', contact.name!.first ?? ''),
                if ((contact.name!.last ?? '').isNotEmpty)
                  _buildInfoRow('Last', contact.name!.last ?? ''),
                if ((contact.name!.middle ?? '').isNotEmpty)
                  _buildInfoRow('Middle', contact.name!.middle ?? ''),
                if ((contact.name!.prefix ?? '').isNotEmpty)
                  _buildInfoRow('Prefix', contact.name!.prefix ?? ''),
                if ((contact.name!.suffix ?? '').isNotEmpty)
                  _buildInfoRow('Suffix', contact.name!.suffix ?? ''),
              ]),
            ],

            // Phones section
            if (contact.phones.isNotEmpty)
              _buildSection(
                'Phones',
                contact.phones
                    .map(
                      (p) => _buildInfoRow(
                        p.label.label.name,
                        p.number,
                        icon: Icons.phone,
                      ),
                    )
                    .toList(),
              ),

            // Emails section
            if (contact.emails.isNotEmpty)
              _buildSection(
                'Emails',
                contact.emails
                    .map(
                      (e) => _buildInfoRow(
                        e.label.label.name,
                        e.address,
                        icon: Icons.email,
                      ),
                    )
                    .toList(),
              ),

            // Addresses section
            if (contact.addresses.isNotEmpty)
              _buildSection(
                'Addresses',
                contact.addresses
                    .map(
                      (a) => _buildInfoRow(
                        a.label.label.name,
                        _formatAddress(a),
                        icon: Icons.location_on,
                      ),
                    )
                    .toList(),
              ),

            // Organization section
            if (contact.organizations.isNotEmpty)
              _buildSection(
                'Organization',
                contact.organizations
                    .map(
                      (o) => [
                        if ((o.name ?? '').isNotEmpty)
                          _buildInfoRow('Company', o.name ?? ''),
                        if ((o.jobTitle ?? '').isNotEmpty)
                          _buildInfoRow('Title', o.jobTitle ?? ''),
                      ],
                    )
                    .expand((x) => x)
                    .toList(),
              ),

            // Websites section
            if (contact.websites.isNotEmpty)
              _buildSection(
                'Websites',
                contact.websites
                    .map(
                      (w) => _buildInfoRow(
                        w.label.label.name,
                        w.url,
                        icon: Icons.link,
                      ),
                    )
                    .toList(),
              ),

            // Events section
            if (contact.events.isNotEmpty)
              _buildSection(
                'Events',
                contact.events
                    .map(
                      (e) => _buildInfoRow(
                        e.label.label.name,
                        _formatEvent(e),
                        icon: Icons.calendar_today,
                      ),
                    )
                    .toList(),
              ),

            // Notes section
            if (contact.notes.isNotEmpty)
              _buildSection(
                'Notes',
                contact.notes
                    .map((n) => _buildInfoRow('Note', n.note, icon: Icons.note))
                    .toList(),
              ),

            // Metadata section
            if (contact.id != null)
              _buildSection('Metadata', [
                _buildInfoRow('ID', contact.id!),
                if (contact.displayName != null)
                  _buildInfoRow('Display Name', contact.displayName!),
              ]),
          ],
        ),
      );
    },
  );

  Widget _buildSection(String title, List<Widget> children) => Column(
    crossAxisAlignment: CrossAxisAlignment.start,
    children: [
      Padding(
        padding: const EdgeInsets.fromLTRB(16, 16, 16, 8),
        child: Text(
          title,
          style: const TextStyle(
            fontSize: 16,
            fontWeight: FontWeight.bold,
            color: Colors.blue,
          ),
        ),
      ),
      ...children,
      const Divider(),
    ],
  );

  Widget _buildInfoRow(String label, String value, {IconData? icon}) =>
      ListTile(
        leading: icon != null ? Icon(icon, size: 20) : null,
        title: Text(value),
        subtitle: Text(label),
        dense: true,
      );

  String _formatAddress(Address address) {
    final parts = <String>[];
    if (address.street?.isNotEmpty ?? false) parts.add(address.street!);
    if (address.city?.isNotEmpty ?? false) parts.add(address.city!);
    if (address.postalCode?.isNotEmpty ?? false) parts.add(address.postalCode!);
    if (address.country?.isNotEmpty ?? false) parts.add(address.country!);
    return parts.join(', ');
  }

  String _formatEvent(Event event) {
    final parts = <String>[];
    parts.add('${event.month}/${event.day}');
    if (event.year != null) {
      parts.add(event.year.toString());
    }
    return parts.join(' ');
  }
}
0
likes
140
points
104
downloads

Publisher

unverified uploader

Weekly Downloads

Fast, complete contact management for Android, iOS & macOS — all fields, groups, accounts, vCards, native dialogs, listeners, SIM contacts, and number blocking.

Documentation

API reference

License

MIT (license)

Dependencies

flutter

More

Packages that depend on flutter_contacts_pro

Packages that implement flutter_contacts_pro