简体   繁体   中英

Flutter Web multipart formdata file upload progress bar

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.

Hi you can use the universal_html/html.dart package to do the progress bar, here are steps:

  1. to import universal package
import 'package:universal_html/html.dart' as html;
  1. 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;
    });
 }
  1. 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.url;
    var postData = event.data.postData;
    uploadFile(file, url, postData);
});

function uploadFile(file, url, presignedPostData) {

    var xhr = new XMLHttpRequest();
    var formData = new FormData();

    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);
}
  1. 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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM