> 技术文档 > Flutter插件鸿蒙化,flutter_native_contact_picker_plus联系人选择器的跨平台适配实践【上篇】

Flutter插件鸿蒙化,flutter_native_contact_picker_plus联系人选择器的跨平台适配实践【上篇】


Flutter插件鸿蒙化,flutter_native_contact_picker_plus联系人选择器的跨平台适配实践

本项目作者:坚果

适配仓库地址

作者仓库:https://pub.dev/packages/flutter_native_contact_picker_plus

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

功能

使用此插件,Flutter 应用可以要求用户从其通讯录中选择一个联系人。与该联系人相关的信息将返回给应用。

  • iOS 支持
    • 选择单个联系人
    • 选择多个联系人
  • Android 支持
    • 选择单个联系人

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

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

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

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

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

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

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

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

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

dependencies: flutter_native_contact_picker: git: url: \"https://gitcode.com/nutpi/flutter_native_contact_picker_plus.git\" path: \"\"

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

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

二、适配过程详解

(一)准备工作

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

Flutter插件鸿蒙化,flutter_native_contact_picker_plus联系人选择器的跨平台适配实践【上篇】

(二)插件目录结构

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

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

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

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

flutter create . --template=plugin --platforms=ohos

步骤:

  1. 用vscode/trae打开刚刚下载好的插件。

  2. 打开Terminal,cd到插件目录下。

  3. 执行命令flutter create . --template=plugin --platforms=ohos 创建一个ohos平台的flutter模块。

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

我们做好修改就好。

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

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

name: flutter_native_contact_picker_plusdescription: An enhanced version of flutter_native_contact_picker for selecting contacts from the address book.version: 0.0.3#author: Jayesh Pansheriya  and mohamed salem bousalem homepage: https://github.com/bousalem98/flutter_native_contact_picker_plusenvironment: sdk: ^3.4.0 flutter: \">=3.3.0\"dependencies: flutter: sdk: flutter plugin_platform_interface: ^2.0.2dev_dependencies: flutter_test: sdk: flutter flutter_lints: ^4.0.0# For information on the generic Dart part of this file, see the# following page: https://dart.dev/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.mohamedbousalem.flutter_native_contact_picker_plus pluginClass: FlutterNativeContactPickerPlusPlugin ios: pluginClass: FlutterNativeContactPickerPlusPlugin ohos: package: com.mohamedbousalem.flutter_native_contact_picker_plus pluginClass: FlutterNativeContactPickerPlusPlugin # 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 # https://flutter.dev/to/asset-from-package # # An image asset can refer to one or more resolution-specific \"variants\", see # https://flutter.dev/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 # https://flutter.dev/to/font-from-package

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

1. 创建鸿蒙插件模块

使用 DevEco Studio 打开鸿蒙项目。

2. 修改相关配置文件

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

/node_modules/oh_modules/local.properties/.preview/.idea/build/libs*.har/.cxx/.test/BuildProfile.ets/oh-package-lock.json5

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

{ \"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 文件,导出配置:

import FlutterNativeContactPickerPlugin from \'./src/main/ets/components/plugin/FlutterNativeContactPickerPlugin\';export default FlutterNativeContactPickerPlugin;
3. 编写 ETS 代码

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

ohos的api可以参考:https://gitcode.com/openharmony/docs

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

import { FlutterPlugin, FlutterPluginBinding, MethodCall, MethodCallHandler, MethodChannel, MethodResult,} from \'@ohos/flutter_ohos\';import { contact } from \'@kit.ContactsKit\';import { BusinessError } from \'@kit.BasicServicesKit\';/** FlutterNativeContactPickerPlusPlugin **/export default class FlutterNativeContactPickerPlusPlugin implements FlutterPlugin, MethodCallHandler { private channel: MethodChannel | null = null; constructor() { } getUniqueClassName(): string { return \"FlutterNativeContactPickerPlusPlugin\" } onAttachedToEngine(binding: FlutterPluginBinding): void { this.channel = new MethodChannel(binding.getBinaryMessenger(), \"flutter_native_contact_picker_plus\"); this.channel.setMethodCallHandler(this) } onDetachedFromEngine(binding: FlutterPluginBinding): void { if (this.channel != null) { this.channel.setMethodCallHandler(null) } } onMethodCall(call: MethodCall, result: MethodResult): void { console.info(\" call.method 被调用\" + JSON.stringify(call.method)); if (call.method == \"getPlatformVersion\") { result.success(\"OpenHarmony ^ ^ \") } else if (call.method == \"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(`Succeeded in selecting Contacts. data->${JSON.stringify(jsonObject)}`); result.success(jsonObject) }); } else if (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 = new Map<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。

使用示例。

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 是 选择联系人时的筛选条件。 callback AsyncCallback<Array> 是 回调函数。成功返回选择的联系人对象数组;失败返回失败的错误码。
let myContact: contact.Contact = { phoneNumbers: [{ phoneNumber: \"138xxxxxxxx\" }], name: { fullName: \"fullName\", namePrefix: \"namePrefix\" }, nickName: { nickName: \"nickName\" }};

1.鸿蒙侧选择联系人

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返回的格式是

[ { \"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端是这样定义的

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()}