| Info | Value |
| ------------------------------ | ----------------------------------------------------- |
| Platform Name | android |
| Platform Version | 9.0 |
| Dio Version | 3.0.1 |
| Android Studio | Android Studio 3.5 |
| Repro rate | all the time (100%) |
| Repro with our demo prj | no |
| Demo project link | no |
[√] Flutter (Channel stable, v1.9.1+hotfix.2, on Microsoft Windows [versão 10.0.17763.737], locale pt-BR)
[√] Android toolchain - develop for Android devices (Android SDK version 28.0.3)
[√] Android Studio (version 3.5)
[√] Connected device (1 available)
The update token example is not working if repeat is performed with a Post / FormData.
1- Use an interceptor to update token, as in the interceptor_lock example;
2- Run a POST/FormData;
3- After updating the token, the request is resubmitted;
I/flutter ( 9472): DioError [DioErrorType.DEFAULT]: Bad state: Can't finalize a finalized MultipartFile.
I/flutter ( 9472): #0 FormData.finalize (package:dio/src/form_data.dart:124:7)
I/flutter ( 9472): #1 DioMixin._transformData (package:dio/src/dio.dart:987:23)
I/flutter ( 9472): <asynchronous suspension>
I/flutter ( 9472): #2 DioMixin._dispatchRequest (package:dio/src/dio.dart:897:26)
I/flutter ( 9472): <asynchronous suspension>
I/flutter ( 9472): #3 DioMixin._request._interceptorWrapper.<anonymous closure>.<anonymous closure>.<anonymous closure> (package:dio/src/dio.dart:828:37)
I/flutter ( 9472): #4 DioMixin.checkIfNeedEnqueue (package:dio/src/dio.dart:1100:22)
I/flutter ( 9472): #5 DioMixin._request._interceptorWrapper.<anonymous closure>.<anonymous closure> (package:dio/src/dio.dart:825:22)
I/flutter ( 9472): #6 new Future.<anonymous closure> (dart:async/future.dart:176:37)
I/flutter ( 9472): #7 _rootRun (dart:async/zone.dart:1120:38)
I/flutter ( 9472): #8 _CustomZone.run (dart:async/zone.dart:1021:19)
I/flutter ( 9472): #9 _CustomZone.runGuarded (dart:async/zone.dart:923:7)
I/flutter ( 9472): #10 _CustomZone.bindCallbackGuarded.<anonymous closure> (dart:async/zone.dart:963:23)
I/flutter ( 9472): #11 _rootRun (dart:async/zo
I/flutter ( 9472): #0 DioMixin._request._errorInterceptorWrapper.<anonymous closure> (package:dio/src/dio.dart:848:13)
I/flutter ( 9472): <asynchronous suspension>
I/flutter ( 9472): #1 _rootRunUnary (dart:async/zone.dart:1132:38)
I/flutter ( 9472): #2 _CustomZone.runUnary (dart:async/zone.dart:1029:19)
I/flutter ( 9472): #3 _FutureListener.handleError (dart:async/future_impl.dart:155:20)
I/flutter ( 9472): #4 Future._propagateToListeners.handleError (dart:async/future_impl.dart:690:47)
I/flutter ( 9472): #5 Future._propagateToListeners (dart:async/future_impl.dart:711:24)
I/flutter ( 9472): #6 Future._completeError (dart:async/future_impl.dart:530:5)
I/flutter ( 9472): #7 _SyncCompleter._completeError (dart:async/future_impl.dart:55:12)
I/flutter ( 9472): #8 _Completer.completeError (dart:async/future_impl.dart:27:5)
I/flutter ( 9472): #9 Future.any.<anonymous closure> (dart:async/future.dart:462:45)
I/flutter ( 9472): #10 _rootRunBinary (dart:async/zone.dart:1144:38)
I/flutter ( 9472): #11 _CustomZone.runBinary (dart:async/zone.dart:1037:19)
I/flutter ( 9472): #12 _FutureListener.handleError (dart:async/future_impl.dart:151:20)
I/flutter ( 9472): #13 Future._propagateToListeners.handleError (dart:async/future_impl.dart:6
Because MultipartFile is based on Stream, and a Stream can be read only once, you should create a new MultipartFile when the request is resubmitted.
Because
MultipartFileis based onStream, and aStreamcan be read only once, you should create a newMultipartFilewhen the request is resubmitted.
I tried this way, but the original MultipartFile does not have the file path to create a new FormData.
// The rest is the same of interceptor_lock example
}).then((e) {
//repeat
if (options.data is FormData) {
FormData formData = FormData();
formData.fields.addAll(options.data.fields);
for (MapEntry mapFile in options.data.files) {
formData.files.add(MapEntry(
mapFile.key,
MultipartFile.fromFileSync(???mapFile.value.FILE_PATH???,
filename: mapFile.value.filename)));
}
options.data = formData;
}
return dio.request(options.path, options: options);
}
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. If this is still an issue, please make sure it is up to date and if so, add a comment that this is still an issue to keep it open. Thank you for your contributions.
I have the same issue, any tips?
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. If this is still an issue, please make sure it is up to date and if so, add a comment that this is still an issue to keep it open. Thank you for your contributions.
Because
MultipartFileis based onStream, and aStreamcan be read only once, you should create a newMultipartFilewhen the request is resubmitted.I tried this way, but the original
MultipartFiledoes not have the file path to create a newFormData.// The rest is the same of interceptor_lock example }).then((e) { //repeat if (options.data is FormData) { FormData formData = FormData(); formData.fields.addAll(options.data.fields); for (MapEntry mapFile in options.data.files) { formData.files.add(MapEntry( mapFile.key, MultipartFile.fromFileSync(???mapFile.value.FILE_PATH???, filename: mapFile.value.filename))); } options.data = formData; } return dio.request(options.path, options: options); }
thx!saved my day!
Because
MultipartFileis based onStream, and aStreamcan be read only once, you should create a newMultipartFilewhen the request is resubmitted.I tried this way, but the original
MultipartFiledoes not have the file path to create a newFormData.// The rest is the same of interceptor_lock example }).then((e) { //repeat if (options.data is FormData) { FormData formData = FormData(); formData.fields.addAll(options.data.fields); for (MapEntry mapFile in options.data.files) { formData.files.add(MapEntry( mapFile.key, MultipartFile.fromFileSync(???mapFile.value.FILE_PATH???, filename: mapFile.value.filename))); } options.data = formData; } return dio.request(options.path, options: options); }thx!saved my day!
how to get the multipartFile file path ???
Why is this issue closed? it's still relevant today.
MultipartFile and has an extra field filePath.MultipartFile class called extra, much like the one in Options.If anyone is looking for a workaround, one can:
Options.extra field and pass a map between the keys in FormData to a path@matanshukry you can easily add field path to your request header part, and use it in repeat request.
@liweiwh Sending extra data over the network when it's not needed (it's used locally only) is a bad option
@matanshukry you can delete it when you do the request.
if (_options.data is FormData) {
FormData formData = FormData();
formData.fields.addAll(_options.data.fields);
for (MapEntry mapFile in _options.data.files) {
formData.files.add(MapEntry(
mapFile.key,
await MultipartFile.fromFile(_options.headers['path'],
filename: mapFile.value.filename)));
}
_options.data = formData;
}
// empty the path field
_options.headers['path'] = null;
return dio.request(path, options: _options);
If you delete it from the request, how will it exist in the interceptor?
@matanshukry oh, sorry, my mistake, I use it in my refresh token process, but when the first time do the request the path is in the header. Not solve your problem, sorry.
If you are resending the same formData in a nested dio request eg
FormData formData = ...
Dio().post('url', data:formData).then((_){
//re-initialise form data here
Dio().post('url', data:formData).then((_){
...
});
}).catch( ...
be sure to re-initiate form data because it is already finalized
I made my own custom multipart class that can store file path, the solution as @matanshukry pointed out, if anyone is interested:
import 'dart:async';
import 'dart:io';
import 'package:dio/dio.dart';
import 'package:http_parser/http_parser.dart';
import 'package:path/path.dart' as p;
class MultipartFileExtended extends MultipartFile {
final String filePath; //this one!
MultipartFileExtended(
Stream<List<int>> stream,
length, {
filename,
this.filePath,
contentType,
}) : super(stream, length, filename: filename, contentType: contentType);
static MultipartFileExtended fromFileSync(
String filePath, {
String filename,
MediaType contentType,
}) => multipartFileFromPathSync(filePath, filename: filename, contentType: contentType);
}
MultipartFileExtended multipartFileFromPathSync(
String filePath, {
String filename,
MediaType contentType,
}) {
filename ??= p.basename(filePath);
var file = File(filePath);
var length = file.lengthSync();
var stream = file.openRead();
return MultipartFileExtended(
stream,
length,
filename: filename,
contentType: contentType,
filePath: filePath,
);
}
Now you can call request like this:
Dio dio = new Dio();
FormData formData = FormData.fromMap({
"file": MultipartFileExtended.fromFileSync("./example/upload.txt",
filename: "upload.txt")
});
var response = await dio.post("/info", data: formData).then((e) {
FormData newFormData = FormData();
newFormData.fields.addAll(formData.fields);
for (MapEntry mapFile in formData.files) {
newFormData.files.add(MapEntry(
mapFile.key,
MultipartFileExtended.fromFileSync(mapFile.value.filePath, //fixed!
filename: mapFile.value.filename)));
}
return dio.post("/info", data: newFormData);
});
thanks @indokan2001 , your solution works for my case.
here is the full code
}).then((e) {
// repeat
if (options.data is FormData) {
// https://github.com/flutterchina/dio/issues/482
FormData formData = FormData();
formData.fields.addAll(options.data.fields);
for (MapEntry mapFile in options.data.files) {
formData.files.add(MapEntry(
mapFile.key,
MultipartFileExtended.fromFileSync(mapFile.value.filePath,
filename: mapFile.value.filename)));
}
options.data = formData;
}
return dio.request(options.path, options: options);
});
Most helpful comment
I tried this way, but the original
MultipartFiledoes not have the file path to create a newFormData.