flutterfile-uploadout-of-memorylarge-filesflutter-web

Flutter web: How to Upload a Large File?


Is there a way to upload large files to server?

I am using MultipartRequest with MultipartFile like:

  List<int> fileBytes) async {
  var request = new http.MultipartRequest("POST", Uri.parse(url));
  request.files.add(http.MultipartFile.fromBytes(
    'file',
    fileBytes,
    contentType: MediaType('application', 'octet-stream'),
    filename: fileName));
  request.headers.addAll(headers);
  var streamedResponse = await request.send();
  return await http.Response.fromStream(streamedResponse);

and reading the file like:

    html.InputElement uploadInput = html.FileUploadInputElement();
    uploadInput.multiple = false;
    uploadInput.draggable = true;
    uploadInput.click();

    uploadInput.onChange.listen((e) {
      final files = uploadInput.files;
      final file = files[0];

      final reader = new html.FileReader();

      reader.onLoadEnd.listen((e) {
        setState(() {
          _bytesData =
              Base64Decoder().convert(reader.result.toString().split(",").last);
          _selectedFile = _bytesData;
        });
      });

      reader.readAsDataUrl(file);
    });

It is OK for files around 30 MB but for more than that, I am getting Error code: Out of Memory. enter image description here

Am I doing something wrong? I saw somewhere

MultipartFile.fromBytes will give you some issues on bigger files, as the browser will limit your memory consumption.

And I think his solution is:

There’s a fromStream constructor. Usually, for bigger files, I just use HttpRequest, and put the File object in a FormData instance.

I used MultipartFile and MultipartFile.fromString and both times (for 150 MB file) that happened again. How can I use this solution? or Is there a better way to do that for files more than 500 MB?

Update

Added an answer using Worker. This is not a great solution but I think this might help someone.


Solution

  • Update

    A project to show how to do this task using this solution with a progress indicator is available here.

    Currently, I solved the problem:

    Import:

    import 'package:universal_html/html.dart' as html;
    

    Flutter part:

    class Upload extends StatefulWidget {
      @override
      _UploadState createState() => _UploadState();
    }
    
    class _UploadState extends State<Upload> {
      html.Worker myWorker;
      html.File file;
    
      _uploadFile() async {
        String _uri = "/upload";
    
        myWorker.postMessage({"file": file, "uri": _uri});
      }
    
      _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"),
            ),
          ],
        );
      }
    }
    

    Javascript part:

    In the web folder (next to index.html), create the file 'upload_worker.js' .

    self.addEventListener('message', async (event) => {
        var file = event.data.file;
        var url = event.data.uri;
        uploadFile(file, url);
    });
    
    function uploadFile(file, url) {
        var xhr = new XMLHttpRequest();
        var formdata = new FormData();
        var uploadPercent;
    
        formdata.append('file', file);
    
        xhr.upload.addEventListener('progress', function (e) {
            //Use this if you want to have a progress bar
            if (e.lengthComputable) {
                uploadPercent = Math.floor((e.loaded / e.total) * 100);
                postMessage(uploadPercent);
            }
        }, false);
        xhr.onreadystatechange = function () {
            if (xhr.readyState == XMLHttpRequest.DONE) {
                postMessage("done");
            }
        }
        xhr.onerror = function () {
            // only triggers if the request couldn't be made at all
            postMessage("Request failed");
        };
    
        xhr.open('POST', url, true);
    
        xhr.send(formdata);
    }