You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Flutter中使用enough_mail从MimeMessage对象下载附件

Dart Flutter MimeMessage附件提取下载实现

前置依赖

你需要在pubspec.yaml中添加以下依赖:

  • enough_mail:邮件解析核心库,提供MimeMessage相关操作API
  • path_provider:用于获取设备存储路径
  • permission_handler:用于申请存储权限
  • device_info_plus:用于判断安卓系统版本适配权限逻辑(可选,可按需删除相关代码)

完整实现代码

import 'dart:io';
import 'package:flutter/material.dart';
import 'package:enough_mail/enough_mail.dart';
import 'package:path_provider/path_provider.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:device_info_plus/device_info_plus.dart';

class EmailScreen extends StatefulWidget {
  EmailScreen({
    Key? key,
    required this.mimeMessage,
    required this.userInfo
  }) : super(key: key);

  final MimeMessage mimeMessage;
  final UserInfo userInfo;

  @override
  EmailScreenState createState() => EmailScreenState(
      mimeMessage: mimeMessage,
      userInfo: userInfo
  );

}

class EmailScreenState extends State<EmailScreen> {

  MimeMessage mimeMessage;
  UserInfo userInfo;
  EmailScreenState({Key? key, required this.mimeMessage, required this.userInfo});

  // 附件列表状态
  List<MimePart> _attachments = [];
  bool _isLoading = true;
  String _operateTip = '';

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

  // 提取所有附件
  void _extractAttachments() {
    _attachments = mimeMessage.findContentInfo()
        .where((contentInfo) => contentInfo.isAttachment)
        .map((contentInfo) => contentInfo.part)
        .toList();
    setState(() {
      _isLoading = false;
    });
  }

  // 下载保存附件到本地
  Future<void> _saveAttachment(MimePart attachment) async {
    // 存储权限申请逻辑
    PermissionStatus permissionStatus;
    if (Platform.isAndroid) {
      final androidInfo = await DeviceInfoPlugin().androidInfo;
      if (androidInfo.version.sdkInt >= 33) {
        permissionStatus = await Permission.photos.request();
      } else {
        permissionStatus = await Permission.storage.request();
      }
    } else if (Platform.isIOS) {
      permissionStatus = await Permission.photosAddOnly.request();
    } else {
      permissionStatus = PermissionStatus.granted;
    }

    if (!permissionStatus.isGranted) {
      setState(() {
        _operateTip = '存储权限被拒绝,无法保存附件';
      });
      return;
    }

    // 获取附件信息
    final fileName = attachment.fileName ?? 'unnamed_attachment_${DateTime.now().millisecondsSinceEpoch}';
    final fileContent = attachment.decodeContentBinary();
    if (fileContent == null) {
      setState(() {
        _operateTip = '附件内容解析失败,下载终止';
      });
      return;
    }

    // 确定保存路径
    Directory? saveDirectory;
    if (Platform.isAndroid) {
      saveDirectory = await getExternalStorageDirectory();
      saveDirectory ??= await getTemporaryDirectory();
    } else {
      saveDirectory = await getApplicationDocumentsDirectory();
    }

    final savePath = '${saveDirectory.path}/$fileName';
    final targetFile = File(savePath);
    await targetFile.writeAsBytes(fileContent);

    setState(() {
      _operateTip = '附件已成功保存到:$savePath';
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('邮件详情')),
      body: _isLoading 
        ? const Center(child: CircularProgressIndicator())
        : Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            if (_operateTip.isNotEmpty)
              Padding(
                padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
                child: Text(_operateTip, style: const TextStyle(color: Colors.green, fontSize: 14)),
              ),
            if (_attachments.isEmpty)
              const Expanded(
                child: Center(child: Text('当前邮件没有附件'))
              )
            else
              Expanded(
                child: ListView.builder(
                  padding: const EdgeInsets.all(8),
                  itemCount: _attachments.length,
                  itemBuilder: (context, index) {
                    final currentAttach = _attachments[index];
                    final sizeInKb = (currentAttach.size ?? 0) / 1024;
                    return ListTile(
                      leading: const Icon(Icons.attachment, color: Colors.blue),
                      title: Text(currentAttach.fileName ?? '未命名附件'),
                      subtitle: Text('${sizeInKb.toStringAsFixed(2)} KB'),
                      trailing: IconButton(
                        icon: const Icon(Icons.download, color: Colors.green),
                        onPressed: () => _saveAttachment(currentAttach),
                      ),
                    );
                  },
                )
              )
          ],
        ),
    );
  }
}

注意事项

  • 如果你使用的是其他邮件库的MimeMessage对象,只需要调整_extractAttachments方法中的附件提取逻辑即可,下载保存逻辑可以通用
  • 如需打开下载完成的附件,可以添加open_file依赖调用打开方法
  • 公共下载目录的获取可以用getDownloadsDirectory()方法替换,按需调整即可

内容的提问来源于stack exchange,提问作者Shahariar Kabir

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.06 19:00:01