Flutter Dio上传图片遇FormData已终结错误,求修复方案
修复Dio刷新Token后上传图片的FormData已Finalized错误
错误
Bad state: The FormData has already been finalized的核心原因是:同一个FormData实例只能被Dio处理一次。当拦截器中复用原请求的requestOptions时,其中的FormData已经在第一次请求中被消费,无法再次使用。
解决方案
1. 拦截器中重新构建请求的FormData
在拦截器的错误处理逻辑里,必须重新创建MultipartFile和FormData,替换原请求的data,不能直接复用原requestOptions。
修改后的拦截器代码:
dio.interceptors.addAll([ PrettyDioLogger( requestHeader: true, requestBody: true, responseBody: true, responseHeader: false, error: true, compact: true, request: true, maxWidth: 150), dios.InterceptorsWrapper( onError: (dios.DioException e, handler) async { if (e.response?.data['code'] == 'token_not_valid') { // 刷新Token RefreshTokenResponse? newAccessToken = await refreshToken( RefreshTokenBody( refresh: storageService.getRefreshToken() ?? '')); if (newAccessToken?.access == null) { // 刷新失败,直接传递错误 return handler.next(e); } // 保存新Token storageService.setAccessToken(newAccessToken!.access); // 复制原请求配置,避免修改原实例 final newRequestOptions = e.requestOptions.copyWith(); // 更新Authorization header newRequestOptions.headers['Authorization'] = 'Bearer ${newAccessToken.access}'; // 重新构建FormData(关键步骤) if (e.requestOptions.data is dios.FormData) { final originalFormData = e.requestOptions.data as dios.FormData; final newFormData = dios.FormData(); // 复制原字段 for (final entry in originalFormData.fields) { newFormData.fields.add(entry); } // 重新创建MultipartFile for (final entry in originalFormData.files) { final file = entry.value; if (file is dios.MultipartFile && file.path != null) { final newMultipartFile = await dios.MultipartFile.fromFile( file.path!, filename: file.filename, ); newFormData.files.add(MapEntry(entry.key, newMultipartFile)); } } newRequestOptions.data = newFormData; } // 用新配置重新发起请求 final response = await dio.fetch(newRequestOptions); return handler.resolve(response); } return handler.next(e); }, ), ]);
2. 简化业务代码,移除冗余逻辑
拦截器已经处理了token_not_valid的重试逻辑,业务代码中无需重复判断,同时优化header配置:
final token = storageService.getAccessToken(); String fileName = photo.path.split('/').last; Map<String, String> headers = <String, String>{ 'Accept': 'application/json', 'Authorization': 'Bearer $token!', // 统一添加Bearer前缀 }; final formData = dios.FormData.fromMap({ "image": await dios.MultipartFile.fromFile( photo.path, filename: fileName, ), }); final response = await dio.patch( "${AppConfig.endPoint}api/upload-picture", data: formData, options: dios.Options(headers: headers), ); if (response.statusCode == 200) { getUserProfile(); return true; } else { throw Exception(response.statusMessage); }
关键注意事项
- 禁止复用FormData实例:每次请求(包括重试)都要创建新的FormData,避免实例被finalized后无法使用。
- 复制请求配置:使用
requestOptions.copyWith()创建新配置,防止修改原请求实例引发未知问题。 - 自动处理Content-Type:当请求data为FormData时,Dio会自动设置
content-type: multipart/form-data,无需手动添加,避免冲突。
内容的提问来源于stack exchange,提问作者Umar Saidu
相关产品推荐
相关产品推荐

