admin管理员组文章数量:1437050
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
中新增如下配置:
dependencies:
flutter_native_contact_picker:
git:
url: ".git"
path: ""
然后在项目根目录运行 flutter pub get
,即可完成依赖添加
接下来是具体的适配过程。
二、适配过程详解
(一)准备工作
确保已经配置好了 Flutter 开发环境,具体可参考 Flutter 配置指南[3]。同时,从 官方插件库[4] 下载待适配的三方插件。本指导书, 以适配 flutter_native_contact_picker[5] 为例
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
步骤:
- 用 vscode/trae 打开刚刚下载好的插件。
- 打开 Terminal,cd 到插件目录下。
- 执行命令
flutter create . --template=plugin --platforms=ohos
创建一个 ohos 平台的 flutter 模块。
第一个问题,修改 sdk 的版本,适配旧版本。
我们做好修改就好。
(四)在根目录下添加鸿蒙平台配置
在项目根目录的 pubspec.yaml
文件中,添加鸿蒙平台的相关配置:
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
目录:
/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 可以参考:
以下是 FlutterNativeContactPickerPlugin.ets
文件的代码示例:
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]>> | 是 | 回调函数。成功返回选择的联系人对象数组;失败返回失败的错误码。 |
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
的类,并且增加 toMap
,fromMap
,和注释。
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
,等待自动签名完成。然后运行以下命令:
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本文标签: Flutter 插件鸿蒙化,flutter
版权声明:本文标题:Flutter 插件鸿蒙化,flutter 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/biancheng/1747443252a2697571.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论