Flutter Web multipart formdata file upload progress bar
Hi you can use the universal_html/html.dart package to do the progress bar, here are steps:
- to import universal package
import 'package:universal_html/html.dart' as html;
- Select files from html input element instead using file picker packages
_selectFile() {
html.FileUploadInputElement uploadInput = html.FileUploadInputElement();
uploadInput.multiple = false;
uploadInput.accept = '.png,.jpg,.glb';
uploadInput.click();
uploadInput.onChange.listen((e) {
_file = uploadInput.files.first;
});
}
- Create upload_worker.js into web folder, my example is upload into S3 post presigned url
self.addEventListener('message', async (event) => {
var file = event.data.file;
var url = event.data.uri;
var postData = event.data.postData;
uploadFile(file, url, postData);
});
function uploadFile(file, url, presignedPostData) {
var xhr = new XMLHttpRequest();
var formData = new FormData();
// if you use postdata, you can open the comment
//Object.keys(presignedPostData).forEach((key) => {
// formData.append(key, presignedPostData[key]);
//});
formData.append('Content-Type', file.type);
// var uploadPercent;
formData.append('file', file);
xhr.upload.addEventListener("progress", function (e) {
if (e.lengthComputable) {
console.log(e.loaded + "/" + e.total);
// pass progress bar status to flutter widget
postMessage(e.loaded/e.total);
}
});
xhr.onreadystatechange = function () {
if (xhr.readyState == XMLHttpRequest.DONE) {
// postMessage("done");
}
}
xhr.onerror = function () {
console.log('Request failed');
// only triggers if the request couldn't be made at all
// postMessage("Request failed");
};
xhr.open('POST', url, true);
xhr.send(formData);
}
- Flutter web call upload worker to upload and listener progress bar status
class Upload extends StatefulWidget {
@override
_UploadState createState() => _UploadState();
}
class _UploadState extends State<Upload> {
html.Worker myWorker;
html.File file;
_uploadFile() async {
String _uri = "/upload";
final postData = {};
myWorker.postMessage({"file": file, "uri": _uri, "postData": postData});
}
_selectFile() {
html.InputElement uploadInput = html.FileUploadInputElement();
uploadInput.multiple = false;
uploadInput.click();
uploadInput.onChange.listen((e) {
file = uploadInput.files.first;
});
}
@override
void initState() {
myWorker = new html.Worker('upload_worker.js');
myWorker.onMessage.listen((e) {
setState(() {
//progressbar,...
});
});
super.initState();
}
@override
Widget build(BuildContext context) {
return Column(
children: [
RaisedButton(
onPressed: _selectFile,
child: Text("Select File"),
),
RaisedButton(
onPressed: _uploadFile,
child: Text("Upload"),
),
],
);
}
}
that's it, I hope it can help you.
Arthur Lima
Updated on December 12, 2022Comments
-
Arthur Lima less than a minute
I'm using Flutter web and strapi headless cms for backend. I'm able to send the files successfully, but would like its progress indication. Backend restrictions: File upload must be multipart form-data, being it a buffer or stream. Frontend restrictions: Flutter web doesn't have access to system file directories; files must be loaded in memory and sent using its bytes.
I'm able to upload the file using flutter's http package or the Dio package, but have the following problems when trying to somehow access upload progress:
Http example code:
http.StreamedResponse response; final uri = Uri.parse(url); final request = MultipartRequest( 'POST', uri, ); request.headers['authorization'] = 'Bearer $_token'; request.files.add(http.MultipartFile.fromBytes( 'files', _fileToUpload.bytes, filename: _fileToUpload.name, )); response = await request.send(); var resStream = await response.stream.bytesToString(); var resData = json.decode(resStream);
What I tryed: When acessing the response.stream for the onData, it only responds when the server sends the finished request (even though the methods states it's supposed to gets some indications of progress).
Dio package code
Response response = await dio.post(url, data: formData, options: Options( headers: { 'authorization': 'Bearer $_token', }, ), onSendProgress: (int sent, int total) { setState(() { pm.progress = (sent / total) * 100; });
The problems:
It seems the package is able to get some progress indication, but Dio package for flutter web has a bug which has not been fixed: requests block the ui and the app freezes until upload is finished.
-
Arthur Lima almost 2 yearsThe dio issues still open: github.com/flutterchina/dio/issues/961 github.com/flutterchina/dio/issues/925
-
Ven Shine 9 monthsThe following answer I tried had no problem and could adopt.
-
-
Ven Shine 9 monthsIt's work for me
-
Piercarlo Slavazza 8 monthsKudos - at the present time this is the only viable solution for a) picking of very large files b) reliable upload progress tracking