基础入门 Flutter for OpenHarmony:flutter_contacts 通讯录管理详解

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
🎯 欢迎来到 Flutter for OpenHarmony 社区!本文将深入讲解 Flutter 中 flutter_contacts 通讯录管理插件的使用方法,带你全面掌握在应用中读取、写入、更新和删除联系人信息的完整流程。


一、flutter_contacts 组件概述

通讯录是移动设备中最基础也是最重要的数据之一,几乎每个应用都需要访问用户的联系人信息。在 Flutter for OpenHarmony 应用开发中,flutter_contacts 是一个功能强大的通讯录管理插件,提供了完整的联系人增删改查功能,支持联系人详细信息的管理,包括姓名、电话、邮箱、地址、组织等。

📋 flutter_contacts 组件特点

特点 说明
功能完整 支持联系人的增删改查全部操作
信息丰富 支持姓名、电话、邮箱、地址、组织等多种信息
权限管理 内置权限请求功能
批量操作 支持批量获取和删除联系人
分组管理 支持联系人分组功能
外部应用集成 支持打开系统通讯录应用
数据监听 支持监听通讯录数据变化
鸿蒙适配 专门为 OpenHarmony 平台进行了适配

联系人数据结构

flutter_contacts 使用面向对象的方式管理联系人数据,主要的数据模型包括:

模型 说明
Contact 联系人主模型
Name 结构化姓名
Phone 电话号码
Email 电子邮件
Address 邮政地址
Organization 组织/公司信息
Website 网站
SocialMedia 社交媒体账号
Event 事件/生日
Note 备注
Group 联系人分组
Account 原始账户信息

💡 使用场景:通讯录应用、社交应用、CRM 系统、电话拨号器、名片管理等。


二、OpenHarmony 平台适配说明

2.1 兼容性信息

本项目基于 flutter_contacts@1.1.7+1 开发,适配 Flutter 3.7.12-ohos-1.0.6。OpenHarmony 平台的通讯录功能基于系统联系人数据库实现。

2.2 权限等级说明

在 OpenHarmony 系统中,权限分为三个等级:

等级 说明
normal 普通权限,普通应用可直接申请
system_basic 系统基础权限,需要系统签名或特殊配置
system_core 系统核心权限,仅系统应用可使用

重要提示 :通讯录权限(READ_CONTACTS、WRITE_CONTACTS)属于 system_basic 级别权限,默认的应用权限等级是 normal,只能使用 normal 等级的权限。如果直接安装包含 system_basic 权限的应用,会报错 9568289

2.3 如何使用 system_basic 权限

要使用通讯录权限,需要修改应用的权限等级。请参考华为官方文档:安装 HAP 时提示 code 9568289

步骤 1:修改 Debug 签名模板

找到 SDK 目录下的签名模板文件:

复制代码
{SDK路径}/openharmony/toolchains/lib/UnsgnedDebugProfileTemplate.json

例如:d:\huawei\DevEco Studio\sdk\default\openharmony\toolchains\lib\UnsgnedDebugProfileTemplate.json

打开文件,修改以下内容:

1. 将 APL 等级从 normal 改为 system_basic:

json 复制代码
"bundle-info": {
    ...
    "apl": "system_basic",
    ...
}

2. 在 acls 中添加允许的权限:

json 复制代码
"acls": {
    "allowed-acls": [
        "ohos.permission.READ_CONTACTS",
        "ohos.permission.WRITE_CONTACTS"
    ]
}

3. 在 permissions 中添加受限权限:

json 复制代码
"permissions": {
    "restricted-permissions": [
        "ohos.permission.READ_CONTACTS",
        "ohos.permission.WRITE_CONTACTS"
    ]
}

完整的修改后的文件示例:

json 复制代码
{
    "version-name": "2.0.0",
    "version-code": 2,
    "uuid": "fe686e1b-3770-4824-a938-961b140a7c98",
    "validity": {
        "not-before": 1610519532,
        "not-after": 1705127532
    },
    "type": "debug",
    "bundle-info": {
        "developer-id": "OpenHarmony",
        "development-certificate": "...",
        "bundle-name": "com.OpenHarmony.app.test",
        "apl": "system_basic",
        "app-feature": "hos_normal_app"
    },
    "acls": {
        "allowed-acls": [
            "ohos.permission.READ_CONTACTS",
            "ohos.permission.WRITE_CONTACTS"
        ]
    },
    "permissions": {
        "restricted-permissions": [
            "ohos.permission.READ_CONTACTS",
            "ohos.permission.WRITE_CONTACTS"
        ]
    },
    "debug-info": {
        "device-ids": [...],
        "device-id-type": "udid"
    },
    "issuer": "pki_internal"
}
步骤 2:修改 Release 签名模板(可选)

如果需要发布应用,同样修改 UnsgnedReleasedProfileTemplate.json 文件。

步骤 3:在 DevEco Studio 中重新签名
  1. 打开 DevEco Studio
  2. 点击 File > Project Structure > Project > Signing Configs
  3. 取消勾选 "Automatically generate signature"
  4. 重新勾选 "Automatically generate signature"
  5. 等待自动签名完成
  6. 点击 OK
步骤 4:重新运行应用

签名完成后,重新运行应用即可正常安装。

⚠️ 注意:修改签名模板后必须重新签名才能生效。如果仍然报错,请尝试清理项目后重新构建。


三、项目配置与安装

3.1 添加依赖配置

首先,需要在你的 Flutter 项目的 pubspec.yaml 文件中添加 flutter_contacts 依赖。

打开项目根目录下的 pubspec.yaml 文件,找到 dependencies 部分,添加以下配置:

yaml 复制代码
dependencies:
  flutter:
    sdk: flutter

  # 添加 flutter_contacts 依赖(OpenHarmony 适配版本)
  flutter_contacts:
    git:
      url: https://atomgit.com/openharmony-sig/flutter_contacts

配置说明:

  • 使用 git 方式引用开源鸿蒙适配的 flutter_contacts 仓库
  • url:指定 AtomGit 托管的仓库地址
  • 本项目基于 flutter_contacts@1.1.7+1 开发

3.2 下载依赖

配置完成后,需要在项目根目录执行以下命令下载依赖:

bash 复制代码
flutter pub get

执行成功后,你会看到类似以下的输出:

复制代码
Running "flutter pub get" in my_cross_platform_app...
Resolving dependencies...
Got dependencies!

3.3 权限配置

通讯录访问需要读写权限,在 OpenHarmony 平台上需要配置:

ohos/entry/src/main/module.json5:

json 复制代码
{
  "module": {
    "requestPermissions": [
      {
        "name": "ohos.permission.INTERNET",
        "reason": "$string:network_reason",
        "usedScene": {
          "abilities": ["EntryAbility"],
          "when": "inuse"
        }
      },
      {
        "name": "ohos.permission.READ_CONTACTS",
        "reason": "$string:contacts_reason",
        "usedScene": {
          "abilities": ["EntryAbility"],
          "when": "inuse"
        }
      },
      {
        "name": "ohos.permission.WRITE_CONTACTS",
        "reason": "$string:contacts_reason",
        "usedScene": {
          "abilities": ["EntryAbility"],
          "when": "inuse"
        }
      }
    ]
  }
}

配置字段说明:

  • name :权限名称
    • ohos.permission.READ_CONTACTS:读取联系人权限
    • ohos.permission.WRITE_CONTACTS:写入联系人权限
  • reason:权限说明,引用字符串资源
  • usedScene:使用场景配置

ohos/entry/src/main/resources/base/element/string.json:

json 复制代码
{
  "string": [
    {
      "name": "network_reason",
      "value": "使用网络"
    },
    {
      "name": "contacts_reason",
      "value": "读取和写入联系人信息"
    }
  ]
}

⚠️ 重要提示 :由于通讯录权限是 system_basic 级别,你需要按照官方文档修改应用的权限等级,否则安装时会报错。


四、flutter_contacts 基础用法

4.1 导入库

在使用 flutter_contacts 之前,需要先导入库:

dart 复制代码
import 'package:flutter_contacts/flutter_contacts.dart';

导入后,你就可以使用 flutter_contacts 提供的所有类和方法了。

4.2 请求权限

在访问通讯录之前,必须先请求用户授权。flutter_contacts 提供了内置的权限请求方法:

dart 复制代码
Future<bool> requestContactsPermission() async {
  bool granted = await FlutterContacts.requestPermission(readonly: false);
  return granted;
}

参数说明:

  • readonly :是否只读模式
    • true:只请求读取权限
    • false:请求读写权限

4.3 获取联系人列表

获取所有联系人的基本信息:

dart 复制代码
Future<List<Contact>> getContacts() async {
  final contacts = await FlutterContacts.getContacts(
    withProperties: true,    // 包含电话、邮箱等属性
    withThumbnail: true,     // 包含缩略图
    withPhoto: false,        // 不包含高清照片
    sorted: true,            // 按名称排序
  );
  return contacts;
}

参数说明:

参数 默认值 说明
withProperties false 是否包含电话、邮箱等属性
withThumbnail false 是否包含缩略图
withPhoto false 是否包含高清照片
withGroups false 是否包含分组信息
withAccounts false 是否包含账户信息
sorted true 是否按名称排序
deduplicateProperties true 是否去重属性

4.4 获取单个联系人详情

根据 ID 获取联系人的完整信息:

dart 复制代码
Future<Contact?> getContactById(String id) async {
  final contact = await FlutterContacts.getContact(
    id,
    withProperties: true,
    withThumbnail: true,
    withPhoto: true,
  );
  return contact;
}

五、联系人 CRUD 操作

5.1 创建联系人

创建新联系人并保存到通讯录:

dart 复制代码
Future<Contact> createContact({
  required String firstName,
  required String lastName,
  String? phone,
  String? email,
}) async {
  final newContact = Contact(
    name: Name(
      first: firstName,
      last: lastName,
    ),
    phones: phone != null
        ? [Phone(phone, label: PhoneLabel.mobile)]
        : [],
    emails: email != null
        ? [Email(email, label: EmailLabel.work)]
        : [],
  );

  final savedContact = await FlutterContacts.insertContact(newContact);
  return savedContact;
}

5.2 更新联系人

更新现有联系人的信息:

dart 复制代码
Future<Contact> updateContactPhone(Contact contact, String newPhone) async {
  contact.phones.add(Phone(newPhone, label: PhoneLabel.mobile));
  final updatedContact = await FlutterContacts.updateContact(contact);
  return updatedContact;
}

5.3 删除联系人

删除单个联系人:

dart 复制代码
Future<void> deleteContactById(Contact contact) async {
  await FlutterContacts.deleteContact(contact);
}

批量删除联系人:

dart 复制代码
Future<void> deleteMultipleContacts(List<Contact> contacts) async {
  await FlutterContacts.deleteContacts(contacts);
}

六、实际应用场景

6.1 通讯录管理示例

下面是一个功能完整的通讯录管理示例,包含联系人列表、详情查看、添加联系人等功能:

dart 复制代码
import 'package:flutter/material.dart';
import 'package:flutter_contacts/flutter_contacts.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: '通讯录示例',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: const Color(0xFF10B981)),
        useMaterial3: true,
      ),
      home: const ContactsListPage(),
    );
  }
}

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

  @override
  State<ContactsListPage> createState() => _ContactsListPageState();
}

class _ContactsListPageState extends State<ContactsListPage> {
  List<Contact>? _contacts;
  bool _permissionDenied = false;
  bool _isLoading = true;

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

  Future<void> _fetchContacts() async {
    setState(() {
      _isLoading = true;
      _permissionDenied = false;
    });

    bool granted = await FlutterContacts.requestPermission(readonly: false);
    if (!granted) {
      setState(() {
        _permissionDenied = true;
        _isLoading = false;
      });
      return;
    }

    try {
      final contacts = await FlutterContacts.getContacts(
        withProperties: true,
        withThumbnail: true,
        sorted: true,
      );
      setState(() {
        _contacts = contacts;
        _isLoading = false;
      });
    } catch (e) {
      setState(() {
        _isLoading = false;
      });
      if (mounted) {
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(content: Text('获取联系人失败: $e')),
        );
      }
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('通讯录'),
        actions: [
          IconButton(
            icon: const Icon(Icons.refresh),
            onPressed: _fetchContacts,
          ),
        ],
      ),
      body: _buildBody(),
      floatingActionButton: FloatingActionButton(
        onPressed: () => _navigateToAddContact(),
        child: const Icon(Icons.add),
      ),
    );
  }

  Widget _buildBody() {
    if (_isLoading) {
      return const Center(child: CircularProgressIndicator());
    }

    if (_permissionDenied) {
      return Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Icon(Icons.perm_contact_cal, size: 64, color: Colors.grey),
            const SizedBox(height: 16),
            const Text('需要通讯录权限'),
            const SizedBox(height: 16),
            ElevatedButton(
              onPressed: _fetchContacts,
              child: const Text('重新请求权限'),
            ),
          ],
        ),
      );
    }

    if (_contacts == null || _contacts!.isEmpty) {
      return const Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Icon(Icons.contact_page_outlined, size: 64, color: Colors.grey),
            SizedBox(height: 16),
            Text('暂无联系人'),
          ],
        ),
      );
    }

    return ListView.builder(
      itemCount: _contacts!.length,
      itemBuilder: (context, index) {
        final contact = _contacts![index];
        return _buildContactTile(contact);
      },
    );
  }

  Widget _buildContactTile(Contact contact) {
    String subtitle = '';
    if (contact.phones.isNotEmpty) {
      subtitle = contact.phones.first.number;
    } else if (contact.emails.isNotEmpty) {
      subtitle = contact.emails.first.address;
    }

    return ListTile(
      leading: CircleAvatar(
        backgroundColor: const Color(0xFF10B981).withOpacity(0.1),
        child: Text(
          contact.displayName.isNotEmpty ? contact.displayName[0] : '?',
          style: const TextStyle(
            color: Color(0xFF10B981),
            fontWeight: FontWeight.bold,
          ),
        ),
      ),
      title: Text(
        contact.displayName.isNotEmpty ? contact.displayName : '未知联系人',
        style: const TextStyle(fontWeight: FontWeight.w500),
      ),
      subtitle: subtitle.isNotEmpty ? Text(subtitle) : null,
      trailing: const Icon(Icons.chevron_right),
      onTap: () => _navigateToContactDetail(contact),
    );
  }

  Future<void> _navigateToAddContact() async {
    final result = await Navigator.push<bool>(
      context,
      MaterialPageRoute(builder: (_) => const AddContactPage()),
    );
    if (result == true) {
      _fetchContacts();
    }
  }

  Future<void> _navigateToContactDetail(Contact contact) async {
    final fullContact = await FlutterContacts.getContact(
      contact.id,
      withProperties: true,
      withThumbnail: true,
    );
    if (fullContact != null && mounted) {
      Navigator.push(
        context,
        MaterialPageRoute(builder: (_) => ContactDetailPage(fullContact)),
      );
    }
  }
}

class ContactDetailPage extends StatelessWidget {
  final Contact contact;

  const ContactDetailPage(this.contact, {super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(contact.displayName),
        actions: [
          IconButton(
            icon: const Icon(Icons.edit),
            onPressed: () {},
          ),
          IconButton(
            icon: const Icon(Icons.delete),
            onPressed: () => _deleteContact(context),
          ),
        ],
      ),
      body: SingleChildScrollView(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            _buildHeader(),
            const SizedBox(height: 24),
            _buildSection('电话', _buildPhones()),
            const SizedBox(height: 16),
            _buildSection('邮箱', _buildEmails()),
            const SizedBox(height: 16),
            _buildSection('地址', _buildAddresses()),
            const SizedBox(height: 16),
            _buildSection('组织', _buildOrganization()),
          ],
        ),
      ),
    );
  }

  Widget _buildHeader() {
    return Center(
      child: Column(
        children: [
          CircleAvatar(
            radius: 48,
            backgroundColor: const Color(0xFF10B981).withOpacity(0.1),
            child: Text(
              contact.displayName.isNotEmpty ? contact.displayName[0] : '?',
              style: const TextStyle(
                fontSize: 32,
                color: Color(0xFF10B981),
                fontWeight: FontWeight.bold,
              ),
            ),
          ),
          const SizedBox(height: 16),
          Text(
            contact.displayName,
            style: const TextStyle(
              fontSize: 24,
              fontWeight: FontWeight.bold,
            ),
          ),
          if (contact.name.first.isNotEmpty || contact.name.last.isNotEmpty)
            Text(
              '${contact.name.first} ${contact.name.last}',
              style: TextStyle(
                fontSize: 14,
                color: Colors.grey[600],
              ),
            ),
        ],
      ),
    );
  }

  Widget _buildSection(String title, Widget content) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Text(
          title,
          style: const TextStyle(
            fontSize: 14,
            fontWeight: FontWeight.bold,
            color: Color(0xFF10B981),
          ),
        ),
        const SizedBox(height: 8),
        content,
      ],
    );
  }

  Widget _buildPhones() {
    if (contact.phones.isEmpty) {
      return const Text('暂无电话', style: TextStyle(color: Colors.grey));
    }
    return Column(
      children: contact.phones.map((phone) {
        return ListTile(
          contentPadding: EdgeInsets.zero,
          leading: const Icon(Icons.phone),
          title: Text(phone.number),
          subtitle: Text(_getPhoneLabel(phone.label)),
        );
      }).toList(),
    );
  }

  Widget _buildEmails() {
    if (contact.emails.isEmpty) {
      return const Text('暂无邮箱', style: TextStyle(color: Colors.grey));
    }
    return Column(
      children: contact.emails.map((email) {
        return ListTile(
          contentPadding: EdgeInsets.zero,
          leading: const Icon(Icons.email),
          title: Text(email.address),
          subtitle: Text(_getEmailLabel(email.label)),
        );
      }).toList(),
    );
  }

  Widget _buildAddresses() {
    if (contact.addresses.isEmpty) {
      return const Text('暂无地址', style: TextStyle(color: Colors.grey));
    }
    return Column(
      children: contact.addresses.map((address) {
        return ListTile(
          contentPadding: EdgeInsets.zero,
          leading: const Icon(Icons.location_on),
          title: Text(address.address),
          subtitle: Text(_getAddressLabel(address.label)),
        );
      }).toList(),
    );
  }

  Widget _buildOrganization() {
    if (contact.organizations.isEmpty) {
      return const Text('暂无组织信息', style: TextStyle(color: Colors.grey));
    }
    return Column(
      children: contact.organizations.map((org) {
        return ListTile(
          contentPadding: EdgeInsets.zero,
          leading: const Icon(Icons.business),
          title: Text(org.company),
          subtitle: Text(org.title),
        );
      }).toList(),
    );
  }

  String _getPhoneLabel(PhoneLabel label) {
    const labels = {
      PhoneLabel.mobile: '手机',
      PhoneLabel.home: '家庭',
      PhoneLabel.work: '工作',
      PhoneLabel.other: '其他',
    };
    return labels[label] ?? '其他';
  }

  String _getEmailLabel(EmailLabel label) {
    const labels = {
      EmailLabel.home: '家庭',
      EmailLabel.work: '工作',
      EmailLabel.mobile: '手机',
      EmailLabel.other: '其他',
    };
    return labels[label] ?? '其他';
  }

  String _getAddressLabel(AddressLabel label) {
    const labels = {
      AddressLabel.home: '家庭',
      AddressLabel.work: '工作',
      AddressLabel.other: '其他',
    };
    return labels[label] ?? '其他';
  }

  Future<void> _deleteContact(BuildContext context) async {
    final confirmed = await showDialog<bool>(
      context: context,
      builder: (context) => AlertDialog(
        title: const Text('确认删除'),
        content: Text('确定要删除联系人 ${contact.displayName} 吗?'),
        actions: [
          TextButton(
            onPressed: () => Navigator.pop(context, false),
            child: const Text('取消'),
          ),
          TextButton(
            onPressed: () => Navigator.pop(context, true),
            style: TextButton.styleFrom(foregroundColor: Colors.red),
            child: const Text('删除'),
          ),
        ],
      ),
    );

    if (confirmed == true) {
      await FlutterContacts.deleteContact(contact);
      if (context.mounted) {
        Navigator.pop(context);
        ScaffoldMessenger.of(context).showSnackBar(
          const SnackBar(content: Text('联系人已删除')),
        );
      }
    }
  }
}

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

  @override
  State<AddContactPage> createState() => _AddContactPageState();
}

class _AddContactPageState extends State<AddContactPage> {
  final _formKey = GlobalKey<FormState>();
  final _firstNameController = TextEditingController();
  final _lastNameController = TextEditingController();
  final _phoneController = TextEditingController();
  final _emailController = TextEditingController();
  bool _isSaving = false;

  @override
  void dispose() {
    _firstNameController.dispose();
    _lastNameController.dispose();
    _phoneController.dispose();
    _emailController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('添加联系人'),
        actions: [
          TextButton(
            onPressed: _isSaving ? null : _saveContact,
            child: const Text('保存'),
          ),
        ],
      ),
      body: Form(
        key: _formKey,
        child: ListView(
          padding: const EdgeInsets.all(16),
          children: [
            TextFormField(
              controller: _firstNameController,
              decoration: const InputDecoration(
                labelText: '名字',
                prefixIcon: Icon(Icons.person),
              ),
              validator: (value) {
                if (value == null || value.isEmpty) {
                  return '请输入名字';
                }
                return null;
              },
            ),
            const SizedBox(height: 16),
            TextFormField(
              controller: _lastNameController,
              decoration: const InputDecoration(
                labelText: '姓氏',
                prefixIcon: Icon(Icons.person),
              ),
            ),
            const SizedBox(height: 16),
            TextFormField(
              controller: _phoneController,
              decoration: const InputDecoration(
                labelText: '电话',
                prefixIcon: Icon(Icons.phone),
              ),
              keyboardType: TextInputType.phone,
            ),
            const SizedBox(height: 16),
            TextFormField(
              controller: _emailController,
              decoration: const InputDecoration(
                labelText: '邮箱',
                prefixIcon: Icon(Icons.email),
              ),
              keyboardType: TextInputType.emailAddress,
            ),
          ],
        ),
      ),
    );
  }

  Future<void> _saveContact() async {
    if (!_formKey.currentState!.validate()) return;

    setState(() => _isSaving = true);

    try {
      final newContact = Contact(
        name: Name(
          first: _firstNameController.text,
          last: _lastNameController.text,
        ),
        phones: _phoneController.text.isNotEmpty
            ? [Phone(_phoneController.text, label: PhoneLabel.mobile)]
            : [],
        emails: _emailController.text.isNotEmpty
            ? [Email(_emailController.text, label: EmailLabel.work)]
            : [],
      );

      await FlutterContacts.insertContact(newContact);

      if (mounted) {
        Navigator.pop(context, true);
        ScaffoldMessenger.of(context).showSnackBar(
          const SnackBar(content: Text('联系人已添加')),
        );
      }
    } catch (e) {
      if (mounted) {
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(content: Text('添加失败: $e')),
        );
      }
    } finally {
      setState(() => _isSaving = false);
    }
  }
}

七、API 参考

7.1 主要方法

方法 返回值 说明
requestPermission({bool readonly = false}) Future<bool> 请求通讯录权限
getContacts({...}) Future<List<Contact>> 获取所有联系人
getContact(String id, {...}) Future<Contact?> 获取单个联系人详情
insertContact(Contact contact) Future<Contact> 插入新联系人
updateContact(Contact contact) Future<Contact> 更新联系人
deleteContact(Contact contact) Future<void> 删除单个联系人
deleteContacts(List<Contact> contacts) Future<void> 批量删除联系人
getGroups() Future<List<Group>> 获取所有分组
openExternalPick() Future<Contact?> 打开系统联系人选择器
openExternalView(String id) Future<void> 打开系统联系人详情

7.2 Contact 模型属性

属性 类型 说明
id String 唯一标识符
displayName String 显示名称
thumbnail Uint8List? 缩略图
photo Uint8List? 高清照片
name Name 结构化姓名
phones List<Phone> 电话列表
emails List<Email> 邮箱列表
addresses List<Address> 地址列表
organizations List<Organization> 组织列表
websites List<Website> 网站列表
socialMedias List<SocialMedia> 社交媒体列表
events List<Event> 事件列表
notes List<Note> 备注列表
groups List<Group> 分组列表
accounts List<Account> 账户列表

八、常见问题与解决方案

8.1 安装报错 9568289

问题描述:安装应用时报错 "install failed due to grant request permissions failed"。

原因 :通讯录权限是 system_basic 级别,普通应用默认只有 normal 级别权限。

解决方案 :按照华为官方文档修改应用的权限等级为 system_basic

8.2 权限被拒绝

问题描述:请求权限时用户拒绝授权。

解决方案

dart 复制代码
final granted = await FlutterContacts.requestPermission();
if (!granted) {
  // 用户拒绝了权限,引导用户去设置页面开启
  await openAppSettings();
}

8.3 获取联系人为空

问题描述:调用 getContacts() 返回空列表。

可能原因

  1. 权限未授予
  2. 设备通讯录确实为空
  3. 权限等级不正确

解决方案

  1. 确保已获取权限
  2. 检查设备通讯录是否有数据
  3. 确认应用权限等级配置正确

九、最佳实践

9.1 权限管理

在访问通讯录前先检查权限状态:

dart 复制代码
Future<void> checkAndRequestPermission() async {
  final status = await Permission.contacts.status;
  if (status.isGranted) {
    // 已有权限,直接访问通讯录
  } else if (status.isPermanentlyDenied) {
    // 用户永久拒绝,引导去设置页面
    await openAppSettings();
  } else {
    // 请求权限
    final result = await Permission.contacts.request();
  }
}

9.2 性能优化

获取联系人时按需加载属性:

dart 复制代码
// 只获取基本信息,提高性能
final contacts = await FlutterContacts.getContacts(
  withProperties: false,
  withThumbnail: false,
);

// 需要详情时再获取
final fullContact = await FlutterContacts.getContact(
  contact.id,
  withProperties: true,
);

9.3 错误处理

始终添加错误处理:

dart 复制代码
try {
  final contacts = await FlutterContacts.getContacts();
  // 处理数据
} catch (e) {
  // 处理错误
  print('获取联系人失败: $e');
}

十、总结

本文详细介绍了 Flutter for OpenHarmony 中 flutter_contacts 通讯录管理插件的使用方法,包括:

  1. 基础概念:flutter_contacts 的特点、数据结构
  2. 平台适配:权限等级说明、system_basic 权限配置
  3. 项目配置:依赖添加、权限配置
  4. 核心 API:权限请求、联系人 CRUD 操作
  5. 实际应用:完整的通讯录管理示例
  6. 最佳实践:权限管理、性能优化、错误处理

flutter_contacts 是一个功能完整的通讯录管理插件,适合需要访问和管理联系人信息的应用。需要注意的是,由于通讯录权限属于 system_basic 级别,需要正确配置应用权限等级才能正常使用。


十一、参考资料

📌 提示:本文基于 Flutter 3.7.12-ohos-1.0.6 和 flutter_contacts@1.1.7+1 编写,不同版本可能略有差异。

相关推荐
键盘鼓手苏苏3 小时前
Flutter for OpenHarmony:cider 自动化版本管理与变更日志生成器(发布流程标准化的瑞士军刀) 深度解析与鸿蒙适配指南
运维·开发语言·flutter·华为·rust·自动化·harmonyos
松叶似针3 小时前
Flutter三方库适配OpenHarmony【secure_application】— onMethodCall 方法分发实现
flutter·harmonyos
阿林来了3 小时前
Flutter三方库适配OpenHarmony【flutter_speech】— 语音识别引擎创建
人工智能·flutter·语音识别·harmonyos
键盘鼓手苏苏3 小时前
Flutter for OpenHarmony:dart_ping 网络诊断的瑞士军刀(支持 ICMP Ping) 深度解析与鸿蒙适配指南
开发语言·网络·flutter·华为·rust·harmonyos
阿林来了4 小时前
Flutter三方库适配OpenHarmony【flutter_speech】— 语音识别启动与参数配置
人工智能·flutter·语音识别·harmonyos
松叶似针14 小时前
Flutter三方库适配OpenHarmony【secure_application】— OpenHarmony 插件工程搭建
flutter·harmonyos
lqj_本人16 小时前
Flutter三方库适配OpenHarmony【apple_product_name】华为nova系列设备映射表
flutter·华为
空白诗18 小时前
基础入门 Flutter for OpenHarmony:ClipRRect 圆角裁剪组件详解
flutter
键盘鼓手苏苏19 小时前
Flutter for OpenHarmony 实战:just_audio 音乐播放器深度适配与进阶
flutter