Flutter 插件鸿蒙化,flutter

本项目作者:王阳科/坚果

适配仓库地址[1]

作者仓库:

在数字化浪潮的推动下,跨平台开发框架如 Flutter 凭借其高效、便捷的特性,成为了开发者们的宠儿。而鸿蒙系统的崛起,更是为跨平台开发注入了新的活力。为了助力开发者在鸿蒙生态中快速实现 flutter_native_contact_picker 联系人选择功能,本文将深入浅出地为大家解析如何适配 flutter_native_contact_picker 三方库至鸿蒙平台。

  • 功能
  • 使用此插件,Flutter 应用可以要求用户从其通讯录中选择一个联系人。与该联系人相关的信息将返回给应用。
  • 鸿蒙支持:
    • 选择单个联系人
    • 选择多个联系人
    • 从联系人中选择特定的电话号码
    • 返回选定联系人的所有电话号码

一、flutter_native_contact_picker 联系人选择器的跨平台适配实践

(一)版本选择与仓库简介

我们先去 pub 上查看最新版本,我们选择以 0.0.10 版本为基础进行适配。flutter_native_contact_picker 是一个用于在 Flutter 应用中选择联系人的插件,其 GitHub 仓库为 ,我们的目标是将这个插件适配到鸿蒙平台。

(二)引入背景与使用场景

在 OpenHarmony 北向生态的发展过程中,许多已经适配了 Flutter 的厂商在接入 OpenHarmony 时,都希望能够继续使用 FlutterToast 来实现通知功能。因此,我们提供了这个适配方案,采用插件化的适配器模式,帮助生态伙伴快速实现产品化。

本方案适用于已经支持 Flutter 框架的设备在移植到 OpenHarmony 系统过程中,作为一个备选方案。

(三)使用文档与插件库使用

适配 OpenHarmony 平台的详细使用指导可以参考:Flutter 使用指导文档[2]

在项目中使用该插件库时,只需在 pubspec.yaml 文件的 dependencies 中新增如下配置:

代码语言:javascript代码运行次数:0运行复制
dependencies:
  flutter_native_contact_picker:
    git:
      url: ".git"
      path: ""

然后在项目根目录运行 flutter pub get,即可完成依赖添加

接下来是具体的适配过程。

二、适配过程详解

(一)准备工作

确保已经配置好了 Flutter 开发环境,具体可参考 Flutter 配置指南[3]。同时,从 官方插件库[4] 下载待适配的三方插件。本指导书, 以适配 flutter_native_contact_picker[5] 为例

image-20250417200546042

image-20250417200546042

(二)插件目录结构

下载并解压插件后,我们会看到以下目录结构:

  • lib :对接 Dart 端代码的入口,由此文件接收到参数后,通过 channel 将数据发送到原生端。
  • android :安卓端代码实现目录。
  • ios :iOS 原生端实现目录。
  • example :一个依赖于该插件的 Flutter 应用程序,用于说明如何使用它。
  • README.md :介绍包的文件。
  • CHANGELOG.md :记录每个版本中的更改。
  • LICENSE :包含软件包许可条款的文件。

(三)创建插件的鸿蒙模块

在插件目录下,打开 Terminal,执行以下命令来创建一个鸿蒙平台的 Flutter 模块:

代码语言:javascript代码运行次数:0运行复制
flutter create . --template=plugin --platforms=ohos

步骤:

  1. 用 vscode/trae 打开刚刚下载好的插件。
  2. 打开 Terminal,cd 到插件目录下。
  3. 执行命令flutter create . --template=plugin --platforms=ohos 创建一个 ohos 平台的 flutter 模块。

第一个问题,修改 sdk 的版本,适配旧版本。

我们做好修改就好。

(四)在根目录下添加鸿蒙平台配置

在项目根目录的 pubspec.yaml 文件中,添加鸿蒙平台的相关配置:

代码语言:javascript代码运行次数:0运行复制
name: flutter_native_contact_picker
description:"A Flutter plugin for picking a contact from the address book."
version:0.0.10
#author: Jayesh Pansheriya <pansheriyajayesh@gmail>
homepage:

environment:
sdk:^3.4.0
flutter:'>=3.3.0'

dependencies:
flutter:
    sdk:flutter
plugin_platform_interface:^2.0.2

dev_dependencies:
flutter_test:
    sdk:flutter
flutter_lints:^4.0.0

# For information on the generic Dart part of this file, see the
# following page: /tools/pub/pubspec

# The following section is specific to Flutter packages.
flutter:
# This section identifies this Flutter project as a plugin project.
# The 'pluginClass' specifies the class (in Java, Kotlin, Swift, Objective-C, etc.)
# which should be registered in the plugin registry. This is required for
# using method channels.
# The Android 'package' specifies package in which the registered class is.
# This is required for using method channels on Android.
# The 'ffiPlugin' specifies that native code should be built and bundled.
# This is required for using `dart:ffi`.
# All these are used by the tooling to maintain consistency when
# adding or updating assets for this project.
plugin:
    platforms:
      android:
        package:com.jayesh.flutter_native_contact_picker
        pluginClass:FlutterNativeContactPickerPlugin
      ios:
        pluginClass:FlutterNativeContactPickerPlugin
      ohos:
        pluginClass:FlutterNativeContactPickerPlugin

# To add assets to your plugin package, add an assets section, like this:
# assets:
#   - images/a_dot_burr.jpeg
#   - images/a_dot_ham.jpeg
#
# For details regarding assets in packages, see
# /to/asset-from-package
#
# An image asset can refer to one or more resolution-specific "variants", see
# /to/resolution-aware-images

# To add custom fonts to your plugin package, add a fonts section here,
# in this "flutter" section. Each entry in this list should have a
# "family" key with the font family name, and a "fonts" key with a
# list giving the asset and other descriptors for the font. For
# example:
# fonts:
#   - family: Schyler
#     fonts:
#       - asset: fonts/Schyler-Regular.ttf
#       - asset: fonts/Schyler-Italic.ttf
#         style: italic
#   - family: Trajan Pro
#     fonts:
#       - asset: fonts/TrajanPro.ttf
#       - asset: fonts/TrajanPro_Bold.ttf
#         weight: 700
#
# For details regarding fonts in packages, see
# /to/font-from-package

(五)编写鸿蒙插件的原生 ArkTS 模块

1. 创建鸿蒙插件模块

使用 DevEco Studio 打开鸿蒙项目。

2. 修改相关配置文件

ohos 目录内的 oh-package.json5 文件中添加 libs/flutter.har 依赖,并创建 .gitignore 文件,添加以下内容以忽略 libs 目录:

代码语言:javascript代码运行次数:0运行复制
/node_modules
/oh_modules
/local.properties
/.preview
/.idea
/build
/libs
*.har
/.cxx
/.test
/BuildProfile.ets
/oh-package-lock.json5

oh-package.json5 文件内容如下:

代码语言:javascript代码运行次数:0运行复制
{
  "name": "flutter_native_contact_picker",
"version": "1.0.0",
"description": "A Flutter plugin for picking a contact from the address book.",
"main": "index.ets",
"author": "nutpi",
"license": "Apache-2.0",
"dependencies": {
    "@ohos/flutter_ohos": "file:har/flutter.har"
  }
}

ohos 目录下创建 index.ets 文件,导出配置:

代码语言:javascript代码运行次数:0运行复制

import FlutterNativeContactPickerPlugin from './src/main/ets/components/plugin/FlutterNativeContactPickerPlugin';
export default FlutterNativeContactPickerPlugin;

3. 编写 ETS 代码

文件结构和代码逻辑可以参考安卓或 iOS 的实现,

ohos 的 api 可以参考:

以下是 FlutterNativeContactPickerPlugin.ets 文件的代码示例:

代码语言:javascript代码运行次数:0运行复制
import {
  FlutterPlugin,
  FlutterPluginBinding,
  MethodCall,
  MethodCallHandler,
  MethodChannel,
  MethodResult,
} from'@ohos/flutter_ohos';
import { contact } from'@kit.ContactsKit';
import { BusinessError } from'@kit.BasicServicesKit';

/** FlutterNativeContactPickerPlugin **/
exportdefaultclass FlutterNativeContactPickerPlugin implements FlutterPlugin, MethodCallHandler {
  private channel: MethodChannel | null = null;

constructor() {
  }

  getUniqueClassName(): string {
    return"FlutterNativeContactPickerPlugin"
  }

  onAttachedToEngine(binding: FlutterPluginBinding): void {
    console.info(" onAttachedToEngine 被调用,开始初始化插件");
    this.channel = new MethodChannel(binding.getBinaryMessenger(), "flutter_native_contact_picker");
    console.info(" MethodChannel 已创建,通道名称: flutter_native_contact_picker");
    this.channel.setMethodCallHandler(this);
    console.info(" MethodCallHandler 已设置,插件初始化完成");
  }

  onDetachedFromEngine(binding: FlutterPluginBinding): void {
    if (this.channel != null) {
      this.channel.setMethodCallHandler(null)
    }
  }

  onMethodCall(call: MethodCall, result: MethodResult): void {
    console.info(` onMethodCall 被调用,方法名: ${call.method}`);
    // 获取参数,使用正确的属性名 argument 而不是 arguments
    const argument = call.argument || {};

    if (call.method == "getPlatformVersion") {
      console.info(" getPlatformVersion 被调用");
      result.success("OpenHarmony ^ ^ ")
    } elseif (call.method == "selectContact") {
      console.info(" selectContact 被调用");
      contact.selectContacts({
        isMultiSelect: false
      }, (err: BusinessError, data) => {
        if (err) {
          console.error(`Failed to select Contacts. Code: ${err.code}, message: ${err.message}`);
          return;
        }
        const contactInfo = newMap<string, string | string[]>();
        if (data && data.length > 0) {
          const contact: contact.Contact = data[0];

          contactInfo.set("fullName", contact.name?.fullName || "");
          const phoneNumbers: string[] = contact.phoneNumbers?.map(item => item.phoneNumber) || [];
          contactInfo.set("phoneNumbers", phoneNumbers);

          if (contact.emails && contact.emails.length > 0) {
            const emails: string[] = contact.emails.map(item => item.email);
            contactInfo.set("emails", emails);
          }
        }
        let jsonObject: Record<string, Object> = {};
        contactInfo.forEach((value, key) => {
          if (key !== undefined && value !== undefined) {
            jsonObject[key] = value;
          }
        })
        console.info(`Succeeded in selecting Contacts. data->${JSON.stringify(jsonObject)}`);
        result.success(jsonObject)
      });
    } elseif (call.method == "selectContacts") {
      console.info(" selectContacts 被调用");
      contact.selectContacts({
        isMultiSelect: true
      }, (err: BusinessError, data) => {
        if (err) {
          console.error(`Failed to select Contacts. Code: ${err.code}, message: ${err.message}`);
          result.error(err.code.toString(), err.message, null);
          return;
        }
        console.info(`Succeeded in selecting Contacts. data->${JSON.stringify(data)}`);
        const selectedContacts: contact.Contact[] = [];
        if (data && data.length > 0) {
          for (const contact of data) {
            const contactInfo = newMap<string, string | string[]>();
            contactInfo.set("fullName", contact.name?.fullName || "");
            const phoneNumbers: string[] = contact.phoneNumbers?.map(item => item.phoneNumber) || [];
            contactInfo.set("phoneNumbers", phoneNumbers);

            if (contact.emails && contact.emails.length > 0) {
              const emails: string[] = contact.emails.map(item => item.email);
              contactInfo.set("emails", emails);
            }
            let jsonObject: Record<string, Object> = {};
            contactInfo.forEach((value, key) => {
              if (key !== undefined && value !== undefined) {
                jsonObject[key] = value;
              }
            });
            selectedContacts.push(jsonObject);
          }
        }
        console.info(`Succeeded in selecting Contacts. data->${JSON.stringify(selectedContacts)}`);
        result.success(selectedContacts);
      });


    } else {
      result.notImplemented();
    }
  }
}

这里我主要参考的是

三、Contacts Kit

Contacts Kit 可以帮助开发者轻松实现联系人的增删改查等功能。该 Kit 提供了一系列 API,可以让开发者在应用中快速集成联系人管理功能。

详情请参考@ohos.contact API[6]

使用示例。

代码语言:javascript代码运行次数:0运行复制
contact.selectContacts({
  isMultiSelect:false
},(err: BusinessError, data) => {
    if (err) {
      console.error(`selectContact callback: err->${JSON.stringify(err)}`);
        return;
    }
    console.log(`selectContact callback: success data->${JSON.stringify(data)}`);
});

参数:

参数名

类型

必填

说明

options

ContactSelectionOptions[7]

选择联系人时的筛选条件。

callback

AsyncCallback<Array<Contact[8]>>

回调函数。成功返回选择的联系人对象数组;失败返回失败的错误码。

代码语言:javascript代码运行次数:0运行复制
let myContact: contact.Contact = {
    phoneNumbers: [{
        phoneNumber: "138xxxxxxxx"
    }],
    name: {
        fullName: "fullName",
        namePrefix: "namePrefix"
    },
    nickName: {
        nickName: "nickName"
    }
};

1.鸿蒙侧选择联系人

代码语言:javascript代码运行次数:0运行复制
import { BusinessError } from '@kit.BasicServicesKit';
contact.selectContacts({
  isMultiSelect:false
}, (err: BusinessError, data) => {
    if (err) {
        console.error(`Failed to select Contacts. Code: ${err.code}, message: ${err.message}`);
        return;
    }
    console.info(`Succeeded in selecting Contacts. data->${JSON.stringify(data)}`);
});

可以使用 ai 直接将鸿蒙的 interface 转换成 dart 的类,并且增加 toMapfromMap,和注释。

2,明确 contact 返回的数据类似

其中 data 返回的格式是

代码语言:javascript代码运行次数:0运行复制
[
  {
    "id": 1,
    "key": "1",
    "emails": [],
    "events": [],
    "groups": [],
    "imAddresses": [],
    "phoneNumbers": [
      {
        "phoneNumber": "17752170152",
        "labelName": "手机",
        "labelId": 1
      }
    ],
    "portrait": {
      "uri": ""
    },
    "postalAddresses": [],
    "relations": [],
    "websites": [],
    "name": {
      "fullName": "坚果"
    },
    "note": {
      "noteContent": ""
    },
    "organization": {
      "name": ""
    }
  }
]

3.dart 端是这样定义的

代码语言:javascript代码运行次数:0运行复制
class Contact {
  final String? fullName;           // Contact's full name
  final List<String>? phoneNumbers; // All phone numbers (iOS: all numbers, Android: selected number only)
  final String? selectedPhoneNumber; // The specifically selected phone number when using selectPhoneNumber()
}

4.ios 侧处理

类似 ios 侧处理这边的 Contact,并返回

代码语言:javascript代码运行次数:0运行复制
 var data = Dictionary<String, Any>()
        data["fullName"] = CNContactFormatter.string(from: contact, style: CNContactFormatterStyle.fullName)
        let numbers: Array<String> = contact.phoneNumberspactMap { $0.value.stringValue as String }
        data["phoneNumbers"] = numbers
        result(data)

5.ArkTS 处理单选联系人

代码语言:javascript代码运行次数:0运行复制
  console.info(" selectContact 被调用");
      contact.selectContacts({
        isMultiSelect: false
      }, (err: BusinessError, data) => {
        if (err) {
          console.error(`Failed to select Contacts. Code: ${err.code}, message: ${err.message}`);
          return;
        }
        const contactInfo = new Map<string, string | string[]>();
        if (data && data.length > 0) {
          const contact: contact.Contact = data[0];

          contactInfo.set("fullName", contact.name?.fullName || "");
          const phoneNumbers: string[] = contact.phoneNumbers?.map(item => item.phoneNumber) || [];
          contactInfo.set("phoneNumbers", phoneNumbers);

          if (contact.emails && contact.emails.length > 0) {
            const emails: string[] = contact.emails.map(item => item.email);
            contactInfo.set("emails", emails);
          }
        }
        let jsonObject: Record<string, Object> = {};
        contactInfo.forEach((value, key) => {
          if (key !== undefined && value !== undefined) {
            jsonObject[key] = value;
          }
        })
        console.info(`Succeededin selecting Contacts. data->${JSON.stringify(jsonObject)}`);
        result.success(jsonObject)
      });

6.ArkTS 处理多选联系人

多选的处理方式

代码语言:javascript代码运行次数:0运行复制
  console.info(" selectContacts 被调用");
      contact.selectContacts({
        isMultiSelect: true
      }, (err: BusinessError, data) => {
        if (err) {
          console.error(`Failed to select Contacts. Code: ${err.code}, message: ${err.message}`);
          result.error(err.code.toString(), err.message, null);
          return;
        }
        console.info(`Succeeded in selecting Contacts. data->${JSON.stringify(data)}`);
        const selectedContacts: contact.Contact[] = [];
        if (data && data.length > 0) {
          for (const contact of data) {
            const contactInfo = newMap<string, string | string[]>();
            contactInfo.set("fullName", contact.name?.fullName || "");
            const phoneNumbers: string[] = contact.phoneNumbers?.map(item => item.phoneNumber) || [];
            contactInfo.set("phoneNumbers", phoneNumbers);

            if (contact.emails && contact.emails.length > 0) {
              const emails: string[] = contact.emails.map(item => item.email);
              contactInfo.set("emails", emails);
            }
            let jsonObject: Record<string, Object> = {};
            contactInfo.forEach((value, key) => {
              if (key !== undefined && value !== undefined) {
                jsonObject[key] = value;
              }
            });
            selectedContacts.push(jsonObject);
          }
        }
        console.info(`Succeeded in selecting Contacts. data->${JSON.stringify(selectedContacts)}`);
        result.success(selectedContacts);
      });

7.完整的代码

代码语言:javascript代码运行次数:0运行复制
import {
  FlutterPlugin,
  FlutterPluginBinding,
  MethodCall,
  MethodCallHandler,
  MethodChannel,
  MethodResult,
} from'@ohos/flutter_ohos';
import { contact } from'@kit.ContactsKit';
import { BusinessError } from'@kit.BasicServicesKit';

/** FlutterNativeContactPickerPlugin **/
exportdefaultclass FlutterNativeContactPickerPlugin implements FlutterPlugin, MethodCallHandler {
  private channel: MethodChannel | null = null;

constructor() {
  }

  getUniqueClassName(): string {
    return"FlutterNativeContactPickerPlugin"
  }

  onAttachedToEngine(binding: FlutterPluginBinding): void {
    console.info(" onAttachedToEngine 被调用,开始初始化插件");
    this.channel = new MethodChannel(binding.getBinaryMessenger(), "flutter_native_contact_picker");
    console.info(" MethodChannel 已创建,通道名称: flutter_native_contact_picker");
    this.channel.setMethodCallHandler(this);
    console.info(" MethodCallHandler 已设置,插件初始化完成");
  }

  onDetachedFromEngine(binding: FlutterPluginBinding): void {
    if (this.channel != null) {
      this.channel.setMethodCallHandler(null)
    }
  }

  onMethodCall(call: MethodCall, result: MethodResult): void {
    console.info(` onMethodCall 被调用,方法名: ${call.method}`);
    // 获取参数,使用正确的属性名 argument 而不是 arguments
    const argument = call.argument || {};

    if (call.method == "getPlatformVersion") {
      console.info(" getPlatformVersion 被调用");
      result.success("OpenHarmony ^ ^ ")
    } elseif (call.method == "selectContact") {
      console.info(" selectContact 被调用");
      contact.selectContacts({
        isMultiSelect: false
      }, (err: BusinessError, data) => {
        if (err) {
          console.error(`Failed to select Contacts. Code: ${err.code}, message: ${err.message}`);
          return;
        }
        const contactInfo = newMap<string, string | string[]>();
        if (data && data.length > 0) {
          const contact: contact.Contact = data[0];

          contactInfo.set("fullName", contact.name?.fullName || "");
          const phoneNumbers: string[] = contact.phoneNumbers?.map(item => item.phoneNumber) || [];
          contactInfo.set("phoneNumbers", phoneNumbers);

          if (contact.emails && contact.emails.length > 0) {
            const emails: string[] = contact.emails.map(item => item.email);
            contactInfo.set("emails", emails);
          }
        }
        let jsonObject: Record<string, Object> = {};
        contactInfo.forEach((value, key) => {
          if (key !== undefined && value !== undefined) {
            jsonObject[key] = value;
          }
        })
        console.info(`Succeeded in selecting Contacts. data->${JSON.stringify(jsonObject)}`);
        result.success(jsonObject)
      });
    } elseif (call.method == "selectContacts") {
      console.info(" selectContacts 被调用");
      contact.selectContacts({
        isMultiSelect: true
      }, (err: BusinessError, data) => {
        if (err) {
          console.error(`Failed to select Contacts. Code: ${err.code}, message: ${err.message}`);
          result.error(err.code.toString(), err.message, null);
          return;
        }
        console.info(`Succeeded in selecting Contacts. data->${JSON.stringify(data)}`);
        const selectedContacts: contact.Contact[] = [];
        if (data && data.length > 0) {
          for (const contact of data) {
            const contactInfo = newMap<string, string | string[]>();
            contactInfo.set("fullName", contact.name?.fullName || "");
            const phoneNumbers: string[] = contact.phoneNumbers?.map(item => item.phoneNumber) || [];
            contactInfo.set("phoneNumbers", phoneNumbers);

            if (contact.emails && contact.emails.length > 0) {
              const emails: string[] = contact.emails.map(item => item.email);
              contactInfo.set("emails", emails);
            }
            let jsonObject: Record<string, Object> = {};
            contactInfo.forEach((value, key) => {
              if (key !== undefined && value !== undefined) {
                jsonObject[key] = value;
              }
            });
            selectedContacts.push(jsonObject);
          }
        }
        console.info(`Succeeded in selecting Contacts. data->${JSON.stringify(selectedContacts)}`);
        result.success(selectedContacts);
      });


    } else {
      result.notImplemented();
    }
  }
}

参考:://gitcode/openharmony-sig/flutter_contacts/tree/master/ohos/src/main/ets/components

四、编写 Example

1. 创建 Example 应用

在插件根目录下创建一个名为 example 的文件夹,用于存放示例应用。在 example 文件夹中,创建一个鸿蒙平台的 Flutter 应用,用于验证插件功能。

2. 签名与运行

使用 Deveco Studio 打开 example > ohos 目录,单击 File > Project Structure > Project > Signing Configs,勾选 Automatically generate signature,等待自动签名完成。然后运行以下命令:

代码语言:javascript代码运行次数:0运行复制
flutter pub get

 flutter build hap --debug

如果应用正常启动,说明插件适配成功。如果没有,欢迎大家联系坚果派一起支持。

五、总结

通过以上步骤,我们成功地将 flutter_native_contact_picker 三方库适配到了鸿蒙平台。这个过程涉及到了解插件的基本信息、配置开发环境、创建鸿蒙模块、编写原生代码以及测试验证等多个环节。希望这篇博客能够帮助到需要进行 flutter_native_contact_picker 鸿蒙适配的开发者们,让大家在鸿蒙生态的开发中更加得心应手。

六、参考

  • [如何使用 Flutter 与 OpenHarmony 通信 FlutterChannel]( FlutterChannel.md "如何使用 Flutter 与 OpenHarmony 通信 FlutterChannel")
  • [开发 module]( module.md "开发 module")
  • 开发 package[9]
  • 开发 plugin[10]
  • [开发 FFI plugin]( plugin.md "开发 FFI plugin")
  • developing-packages[11]
  • 适配仓库地址[12]

七、FAQ

如何将 Map 转换为 JSON 字符串

可以将 Map 转成 Record 后,再通过 JSON.stringify()转为 JSON 字符串。示例如下

代码语言:javascript代码运行次数:0运行复制
  const contactInfo = newMap<string, string | string[]>();
        if (data && data.length > 0) {
          const contact: contact.Contact = data[0];

          contactInfo.set("fullName", contact.name?.fullName || "");
          const phoneNumbers: string[] = contact.phoneNumbers?.map(item => item.phoneNumber) || [];
          contactInfo.set("phoneNumbers", phoneNumbers);

          if (contact.emails && contact.emails.length > 0) {
            const emails: string[] = contact.emails.map(item => item.email);
            contactInfo.set("emails", emails);
          }
        }
        let jsonObject: Record<string, Object> = {};
        contactInfo.forEach((value, key) => {
          if (key !== undefined && value !== undefined) {
            jsonObject[key] = value;
          }
        })
        console.info(`Succeeded in selecting Contacts. data->${JSON.stringify(jsonObject)}`);

如何判断是鸿蒙平台

代码语言:javascript代码运行次数:0运行复制
import 'dart:io';

 Platform.operatingSystem == 'ohos';

八、使用示例

代码语言:javascript代码运行次数:0运行复制
void main() {
  runApp(const MyApp());
}

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

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  final FlutterNativeContactPicker _contactPicker = FlutterNativeContactPicker();
  List<Contact>? _contacts;
String? _selectedPhoneNumber;

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Contact Picker Example App'),
        ),
        body: Center(
          child: Column(
            mainAxisSize: MainAxisSize.min,
            children: <Widget>[
              MaterialButton(
                color: Colors.blue,
                textColor: Colors.white,
                child: const Text("Select Contact"),
                onPressed: () async {
                  Contact? contact = await _contactPicker.selectContact();
                  setState(() {
                    _contacts = contact == null ? null : [contact];
                    _selectedPhoneNumber = null;
                  });
                },
              ),
              MaterialButton(
                color: Colors.green,
                textColor: Colors.white,
                child: const Text("Select Phone Number"),
                onPressed: () async {
                  Contact? contact = await _contactPicker.selectPhoneNumber();
                  setState(() {
                    _contacts = contact == null ? null : [contact];
                    _selectedPhoneNumber = contact?.selectedPhoneNumber;
                  });
                },
              ),
              if (_contacts != null) ...[
                ..._contacts!.map(
                  (contact) => Column(
                    children: [
                      Text(contact.fullName ?? 'No name'),
                      if (_selectedPhoneNumber != null)
                        Text('Selected: $_selectedPhoneNumber'),
                      ...?contact.phoneNumbers?.map((number) => Text(number)),
                    ],
                  ),
                ),
              ],
            ],
          ),
        ),
      ),
    );
  }
}

参考资料

[1]

适配仓库地址:

[2]

Flutter 使用指导文档: .md

[3]

Flutter 配置指南: .md

[4]

官方插件库: /

[5]

flutter_native_contact_picker: .0.10

[6]

@ohos.contact API:

[7]

ContactSelectionOptions:

[8]

Contact:

[9]

开发 package: .md

[10]

开发 plugin: .md

[11]

developing-packages:

[12]

适配仓库地址:

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。原始发表:2025-04-30,如有侵权请联系 cloudcommunity@tencent 删除native插件跨平台实践flutter