如何实现带附件上传的Flutter表单并发送Multipart POST请求?
问题排查与实现方案
1. readAsBytes报错原因
该报错有两个核心诱因:
- 你的
file变量未完成赋值,为空状态下调用实例方法触发异常 file变量类型不是dart:io库的File类型,比如你使用跨端文件选择工具拿到的是PlatformFile等封装类型,该类本身没有readAsBytes方法
另外你现有请求代码里手动设置Content-Type: text/plain是错误的,MultipartRequest会自动生成带边界标识的多部分表单请求头,手动覆盖会导致后端无法正确解析请求数据,需要删除这段header设置逻辑。
2. 完整实现步骤
你的Multipart请求思路本身没有问题,只需要补全文件选择、表单校验逻辑即可,具体实现如下:
第一步:添加依赖
在pubspec.yaml中添加文件选择和网络请求依赖:
dependencies: flutter: sdk: flutter http: ^1.1.0 file_picker: ^6.1.1
执行flutter pub get完成依赖安装。
第二步:构建表单页面
import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; import 'package:file_picker/file_picker.dart'; import 'dart:convert'; class HomeworkFormPage extends StatefulWidget { const HomeworkFormPage({super.key}); @override State<HomeworkFormPage> createState() => _HomeworkFormPageState(); } class _HomeworkFormPageState extends State<HomeworkFormPage> { final _formKey = GlobalKey<FormState>(); final TextEditingController _titleController = TextEditingController(); final TextEditingController _limitDateController = TextEditingController(); PlatformFile? _selectedFile; // 这两个参数根据你的实际业务逻辑赋值,这里写示例固定值 final int _subjectId = 1; final int _teacherId = 10; // 选择TXT文件方法 Future<void> _pickFile() async { FilePickerResult? result = await FilePicker.platform.pickFiles( type: FileType.custom, allowedExtensions: ['txt'], // 仅允许TXT格式 allowMultiple: false, // 仅允许选1个文件 ); if (result != null) { setState(() { _selectedFile = result.files.first; }); } } // 修正后的提交请求方法 Future<void> _submitForm() async { if (_formKey.currentState!.validate() && _selectedFile != null) { var url = Uri.parse('http://localhost:8000/newHomework'); var request = http.MultipartRequest('POST', url); // 添加文件 request.files.add(http.MultipartFile.fromBytes( 'text', // 确保和后端接收文件的字段名一致 _selectedFile!.bytes!, filename: _selectedFile!.name, // 用选中的实际文件名 )); // 添加表单字段 request.fields['title'] = _titleController.text.trim(); request.fields['limitDate'] = _limitDateController.text.trim(); request.fields['subjectid'] = _subjectId.toString(); request.fields['teacherid'] = _teacherId.toString(); request.fields['ext'] = '.txt'; var res = await request.send(); final respStr = await res.stream.bytesToString(); final parsedJson = json.decode(respStr); print(parsedJson); // 这里可以加请求成功/失败的交互提示 } else if (_selectedFile == null) { // 提示未选择附件 ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('请选择TXT格式的附件'))); } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('提交作业')), body: Padding( padding: const EdgeInsets.all(16.0), child: Form( key: _formKey, child: Column( children: [ TextFormField( controller: _titleController, decoration: const InputDecoration(labelText: '作业标题'), validator: (value) => value?.trim().isEmpty == true ? '请输入标题' : null, ), TextFormField( controller: _limitDateController, decoration: const InputDecoration(labelText: '截止日期'), validator: (value) => value?.trim().isEmpty == true ? '请输入截止日期' : null, ), const SizedBox(height: 20), Row( children: [ ElevatedButton( onPressed: _pickFile, child: const Text('选择TXT附件'), ), const SizedBox(width: 10), Expanded(child: Text(_selectedFile?.name ?? '未选择文件')), ], ), const SizedBox(height: 30), ElevatedButton( onPressed: _submitForm, child: const Text('提交数据'), ), ], ), ), ), ); } }
注意事项
- 确保请求中文件对应的字段名
text和后端接收文件的参数名一致,否则后端无法拿到上传的文件 - 本地测试时如果用安卓模拟器,需要将
localhost替换为模拟器对应的宿主机IP10.0.2.2,否则无法访问本地后端服务
内容的提问来源于stack exchange,提问作者Irwin
相关产品推荐
相关产品推荐

