📋 项目概述

本文档详细介绍如何在 HarmonyOS 平台上使用 Flutter 框架集成 file_selector 插件,实现文件选择、多文件选择和目录选择等功能。通过实际开发案例,展示从插件配置到功能实现的完整流程,并记录开发过程中遇到的问题及解决方案。

img

运行截图说明:本文档中的代码已在 HarmonyOS 设备上实际运行测试,功能正常运行。建议读者在阅读时结合实际操作,以获得更好的学习效果。

🎯 项目目标

  • ✅ 在 HarmonyOS 平台上集成 file_selector 插件
  • ✅ 实现文件选择功能(单个/多个文件)
  • ✅ 实现目录选择功能
  • ✅ 构建美观的 Material Design 3 风格 UI
  • ✅ 处理平台兼容性和权限配置

🛠️ 技术栈

  • 开发框架: Flutter 3.7.12-ohos-1.0.6+
  • 三方库: file_selector (OpenHarmony TPC 适配版本)
  • UI 框架: Material Design 3
  • 目标平台: HarmonyOS (OpenHarmony)
  • 开发工具: DevEco Studio / VS Code

📦 一、项目初始化

1.1 创建 Flutter 项目

flutter create --platforms=ohos file_selector_demo
cd file_selector_demo

1.2 配置依赖

pubspec.yaml 中添加 file_selector 依赖:

dependencies:
  flutter:
    sdk: flutter
  cupertino_icons: ^1.0.8
  file_selector:
    git:
      url: https://gitcode.com/openharmony-tpc/flutter_packages.git
      path: packages/file_selector/file_selector

重要说明:必须使用 OpenHarmony TPC 提供的适配版本,pub.dev 上的官方版本不支持 HarmonyOS 平台。

1.3 安装依赖

flutter pub get

🔐 二、权限配置

2.1 添加网络权限

ohos/entry/src/main/module.json5 中添加权限配置:

{
  "module": {
    "requestPermissions": [
      {
        "name": "ohos.permission.INTERNET",
        "reason": "$string:network_reason",
        "usedScene": {
          "abilities": ["EntryAbility"],
          "when": "inuse"
        }
      },
      {
        "name": "ohos.permission.READ_MEDIA",
        "reason": "$string:file_access_reason",
        "usedScene": {
          "abilities": ["EntryAbility"],
          "when": "inuse"
        }
      }
    ]
  }
}

2.2 添加权限说明

ohos/entry/src/main/resources/base/element/string.json 中添加权限说明:

{
  "string": [
    {
      "name": "network_reason",
      "value": "使用网络"
    },
    {
      "name": "file_access_reason",
      "value": "访问文件系统以选择文件和目录"
    }
  ]
}

💻 三、核心功能实现

3.1 打开单个文件

Future<void> _openSingleFile() async {
  const XTypeGroup typeGroup = XTypeGroup(
    label: 'images',
    extensions: <String>['jpg', 'png', 'gif', 'webp'],
    uniformTypeIdentifiers: <String>['public.jpeg', 'public.png'],
  );

  final XFile? file = await openFile(
    acceptedTypeGroups: <XTypeGroup>[typeGroup],
  );

  if (file != null) {
    setState(() {
      _selectedSingleFile = file.path;
    });
    _showSnackBar('已选择文件: ${file.name}');
  } else {
    _showSnackBar('未选择文件');
  }
}

3.2 打开多个文件

Future<void> _openMultipleFiles() async {
  const XTypeGroup jpgsTypeGroup = XTypeGroup(
    label: 'JPEGs',
    extensions: <String>['jpg', 'jpeg'],
    uniformTypeIdentifiers: <String>['public.jpeg'],
  );
  const XTypeGroup pngTypeGroup = XTypeGroup(
    label: 'PNGs',
    extensions: <String>['png'],
    uniformTypeIdentifiers: <String>['public.png'],
  );

  final List<XFile> files = await openFiles(
    acceptedTypeGroups: <XTypeGroup>[jpgsTypeGroup, pngTypeGroup],
  );

  if (files.isNotEmpty) {
    setState(() {
      _selectedMultipleFiles = files.map((f) => f.path).toList();
    });
    _showSnackBar('已选择 ${files.length} 个文件');
  } else {
    setState(() {
      _selectedMultipleFiles = [];
    });
    _showSnackBar('未选择文件');
  }
}

3.3 选择目录(当前版本未实现)

⚠️ 重要提示:虽然 OpenHarmony TPC 文档标注 getDirectoryPath API 在 HarmonyOS 平台上支持,但当前版本的 file_selector_ohos 插件实现中,该方法尚未完成实现,代码中只有:

getDirectoryPath(initialDirectory: string, result: Result<string>): void {
  throw new Error('Method not implemented.')
}

因此,调用此方法会抛出 PlatformException(channel-error, Unable to establish connection on channel.) 错误。

临时处理方案

// 选择目录功能暂时禁用,等待插件更新
Future<void> _selectDirectory() async {
  _showSnackBar('⚠️ 选择目录功能在当前版本中未实现\n插件实现中 getDirectoryPath 方法暂未完成');
  return;
  
  // 以下代码保留,等待插件更新后可以使用
  // final String? directoryPath = await getDirectoryPath(
  //   confirmButtonText: '选择',
  // );
  // if (directoryPath != null) {
  //   setState(() {
  //     _selectedDirectory = directoryPath;
  //   });
  // }
}

预期实现(等待插件更新后):

Future<void> _selectDirectory() async {
  try {
    final String? directoryPath = await getDirectoryPath(
      confirmButtonText: '选择',
    );
    if (directoryPath == null) {
      _showSnackBar('未选择目录');
      return;
    }

    setState(() {
      _selectedDirectory = directoryPath;
    });
    _showSnackBar('已选择目录: $directoryPath');
  } catch (e) {
    _showSnackBar('选择目录失败: $e');
  }
}

🎨 四、UI 实现

4.1 Material Design 3 风格

使用 Material Design 3 设计语言,创建现代化的用户界面:

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

  
  State<FileSelectorDemo> createState() => _FileSelectorDemoState();
}

class _FileSelectorDemoState extends State<FileSelectorDemo> {
  // ... 状态变量和方法

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('File Selector 演示'),
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        elevation: 2,
      ),
      body: SingleChildScrollView(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            // 功能卡片列表
            _buildFeatureCard(...),
          ],
        ),
      ),
    );
  }
}

4.2 功能卡片组件

Widget _buildFeatureCard({
  required IconData icon,
  required String title,
  required String description,
  required String buttonText,
  required VoidCallback onPressed,
  String? result,
  required String resultLabel,
  bool isMultiLine = false,
  String? platformNote,
}) {
  return Card(
    elevation: 2,
    child: Padding(
      padding: const EdgeInsets.all(16.0),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          // 图标和标题
          Row(
            children: [
              Icon(icon, color: Theme.of(context).colorScheme.primary, size: 32),
              const SizedBox(width: 12),
              Expanded(
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(title, style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
                    Text(description, style: TextStyle(fontSize: 14, color: Colors.grey[600])),
                    if (platformNote != null)
                      Text(platformNote, style: TextStyle(fontSize: 12, color: Colors.orange[700])),
                  ],
                ),
              ),
            ],
          ),
          const SizedBox(height: 16),
          // 操作按钮
          SizedBox(
            width: double.infinity,
            child: ElevatedButton.icon(
              onPressed: onPressed,
              icon: const Icon(Icons.open_in_new),
              label: Text(buttonText),
            ),
          ),
          // 结果显示
          if (result != null) ...[
            const SizedBox(height: 16),
            Container(
              padding: const EdgeInsets.all(12),
              decoration: BoxDecoration(
                color: Colors.grey[100],
                borderRadius: BorderRadius.circular(8),
              ),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text(resultLabel, style: TextStyle(fontSize: 12, fontWeight: FontWeight.bold)),
                  const SizedBox(height: 8),
                  Text(result, style: const TextStyle(fontSize: 12)),
                ],
              ),
            ),
          ],
        ],
      ),
    ),
  );
}

⚠️ 五、遇到的问题及解决方案

5.1 问题一:插件无法建立通道连接

错误信息

PlatformException(channel-error, Unable to establish connection on channel., null, null)

原因分析

  • getDirectoryPath 方法在插件实现中未完成,代码中只有 throw new Error('Method not implemented.')
  • 虽然文档标注支持,但实际实现尚未完成

解决方案

// 选择目录功能暂时禁用,等待插件更新
Future<void> _selectDirectory() async {
  _showSnackBar('⚠️ 选择目录功能在当前版本中未实现\n插件实现中 getDirectoryPath 方法暂未完成');
  return;
}

5.2 问题二:权限配置不完整

问题描述

  • 应用无法访问文件系统
  • 文件选择器无法正常工作

解决方案

  1. module.json5 中添加 ohos.permission.READ_MEDIA 权限
  2. string.json 中添加权限使用说明
  3. 重新安装应用以应用权限更改

5.3 问题三:依赖版本不兼容

问题描述

  • pub.dev 上的 file_selector 版本不支持 HarmonyOS 平台
  • 直接使用会报错:No registered handler for message

解决方案
使用 OpenHarmony TPC 提供的适配版本:

file_selector:
  git:
    url: https://gitcode.com/openharmony-tpc/flutter_packages.git
    path: packages/file_selector/file_selector

📱 六、平台支持情况

根据 OpenHarmony TPC 文档,file_selector 插件在 HarmonyOS 平台上的支持情况:

功能 API HarmonyOS 支持 说明
打开单个文件 openFile ✅ 支持 完全支持
打开多个文件 openFiles ✅ 支持 完全支持
选择目录 getDirectoryPath ⚠️ 文档支持但未实现 当前版本未完成实现
保存文件 getSaveLocation ❌ 不支持 鸿蒙平台不支持此功能

🚀 七、运行和测试

7.1 运行项目

# 清理构建缓存
flutter clean

# 获取依赖
flutter pub get

# 运行到鸿蒙设备
flutter run

7.2 测试功能

  1. 打开单个文件:点击"选择文件"按钮,选择图片文件
  2. 打开多个文件:点击"选择多个文件"按钮,同时选择多个图片
  3. 选择目录:当前版本暂不支持,会显示提示信息

📚 八、API 参考

8.1 openFile

打开单个文件选择对话框。

参数

  • acceptedTypeGroups: 可选的文件类型组列表
  • initialDirectory: 初始目录路径(可选)
  • confirmButtonText: 确认按钮文本(可选)

返回值Future<XFile?> - 选择的文件,如果取消则返回 null

示例

final XFile? file = await openFile(
  acceptedTypeGroups: <XTypeGroup>[
    XTypeGroup(
      label: 'images',
      extensions: <String>['jpg', 'png'],
    ),
  ],
);

8.2 openFiles

打开多文件选择对话框。

参数:与 openFile 相同

返回值Future<List<XFile>> - 选择的文件列表

示例

final List<XFile> files = await openFiles(
  acceptedTypeGroups: <XTypeGroup>[
    XTypeGroup(label: 'images', extensions: <String>['jpg', 'png']),
  ],
);

8.3 getDirectoryPath

选择目录并返回路径。

参数

  • initialDirectory: 初始目录路径(可选)
  • confirmButtonText: 确认按钮文本(可选)

返回值Future<String?> - 选择的目录路径,如果取消则返回 null

注意:当前版本未实现,会抛出异常。

🎯 九、最佳实践

9.1 错误处理

始终使用 try-catch 包装文件选择操作:

Future<void> _openFile() async {
  try {
    final XFile? file = await openFile(...);
    // 处理结果
  } catch (e) {
    _showSnackBar('操作失败: $e');
  }
}

9.2 文件类型过滤

使用 XTypeGroup 明确指定支持的文件类型:

const XTypeGroup imageGroup = XTypeGroup(
  label: 'images',
  extensions: <String>['jpg', 'png', 'gif'],
  mimeTypes: <String>['image/jpeg', 'image/png'],
);

9.3 用户体验优化

  • 提供清晰的操作反馈(SnackBar、Toast)
  • 显示文件选择结果
  • 标注平台支持情况
  • 处理用户取消操作的情况

📝 十、项目结构

file_selector_demo/
├── lib/
│   └── main.dart              # 主应用文件
├── ohos/
│   └── entry/
│       └── src/
│           └── main/
│               ├── ets/
│               │   ├── entryability/
│               │   │   └── EntryAbility.ets
│               │   └── plugins/
│               │       └── GeneratedPluginRegistrant.ets
│               ├── module.json5          # 模块配置(权限)
│               └── resources/
│                   └── base/
│                       └── element/
│                           └── string.json  # 权限说明
├── pubspec.yaml              # 依赖配置
└── README.md                 # 项目说明

🔗 十一、参考资源

🎉 十二、总结

通过本文档,我们成功在 HarmonyOS 平台上集成了 file_selector 插件,实现了文件选择功能。主要成果包括:

  1. 成功集成插件:使用 OpenHarmony TPC 适配版本
  2. 实现核心功能:文件选择、多文件选择
  3. 完善权限配置:添加必要的文件访问权限
  4. 构建美观 UI:Material Design 3 风格界面
  5. 记录问题解决:详细记录开发过程中的问题和解决方案

注意事项

  • ⚠️ 选择目录功能:虽然文档标注支持,但当前版本未实现,需要等待插件更新
  • ⚠️ 保存文件功能:鸿蒙平台不支持 getSaveLocation API
  • ⚠️ 权限配置:必须正确配置权限,否则功能无法正常工作
  • ⚠️ 版本兼容性:必须使用 OpenHarmony TPC 提供的适配版本

后续优化方向

  1. 等待插件更新,实现 getDirectoryPath 方法
  2. 添加更多文件类型支持
  3. 优化错误处理和用户提示
  4. 添加文件预览功能

🌐 社区支持

欢迎加入开源鸿蒙跨平台社区,与其他开发者交流学习,共同推进鸿蒙跨平台生态建设:

开源鸿蒙跨平台社区https://openharmonycrossplatform.csdn.net

在这里你可以:

  • 📚 获取最新的跨平台开发技术文档
  • 💬 与其他开发者交流开发经验
  • 🐛 反馈问题和建议
  • 🎯 参与开源项目贡献
  • 📖 学习更多跨平台开发最佳实践
Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐