19

I am uploading a file using MultipartRequest from package:http. I am successfully uploading the file but I want to get the progress of the file that is being uploaded. How can I achieve that? My current code looks something like this

Future submitFile(var report, File file) async {
var uri = Uri.parse(endpoint + "v1/reports");
  var request = http.MultipartRequest("POST", uri);
  await addHeaders(request.headers);
  request.fields.addAll(Report.toMap(report));
  if (file != null)
    request.files.add(await http.MultipartFile.fromPath(
      'report_resource',
      file.path,
    ));

  String response = "";
  await (await request.send()).stream.forEach((message) {
    response = response + String.fromCharCodes(message);
  });
  return response;
}

I searched for the solution, found this. And this post is somehow not similar to what I want to achieve, as he is using different client for the request.

Maybe I am not searching on the right path. Help is appreciated.

Aawaz Gyawali
  • 3,244
  • 5
  • 28
  • 48

3 Answers3

27

Here is my take:

// multipart_request.dart

import 'dart:async';

import 'package:http/http.dart' as http;

class MultipartRequest extends http.MultipartRequest {
  /// Creates a new [MultipartRequest].
  MultipartRequest(
    String method,
    Uri url, {
    this.onProgress,
  }) : super(method, url);

  final void Function(int bytes, int totalBytes) onProgress;

  /// Freezes all mutable fields and returns a single-subscription [ByteStream]
  /// that will emit the request body.
  http.ByteStream finalize() {
    final byteStream = super.finalize();
    if (onProgress == null) return byteStream;

    final total = this.contentLength;
    int bytes = 0;

    final t = StreamTransformer.fromHandlers(
      handleData: (List<int> data, EventSink<List<int>> sink) {
        bytes += data.length;
        onProgress(bytes, total);
        if(total >= bytes) {
           sink.add(data);
        }
      },
    );
    final stream = byteStream.transform(t);
    return http.ByteStream(stream);
  }
}

Usage:

import 'package:http/http.dart' as http;
import 'package:http_parser/http_parser.dart' show MediaType;

import 'multipart_request.dart';

final uri = 'https://...';
final request = MultipartRequest(
  'POST',
  uri,
  onProgress: (int bytes, int total) {
    final progress = bytes / total;
    print('progress: $progress ($bytes/$total)');
  },
);

request.headers['HeaderKey'] = 'header_value';
request.fields['form_key'] = 'form_value';
request.files.add(
  await http.MultipartFile.fromPath(
    'field_name',
    'path/to/file',
    contentType: MediaType('image', 'jpeg'),
  ),
);

final streamedResponse = await request.send();
Classy-Bear
  • 881
  • 11
  • 19
avioli
  • 492
  • 1
  • 4
  • 9
  • 1
    I am not sure if its returning the correct progress, for me the progress is weird ```progress: 850.1721854304636 (1412136/1661)``` This one is for video and image upload in a multipart request – Mahesh Jamdade Mar 15 '21 at 13:27
  • The video upload never completes and for a image The upload happens at ```progress: 21.52558699578567 (35754/1661)``` – Mahesh Jamdade Mar 15 '21 at 13:30
  • 1
    @MaheshJamdade I updated the answer now the upload completes – Classy-Bear Mar 31 '22 at 02:56
  • @Classy-Bear I hope you've run the code with your change, because I believe by only adding data to the sink within a conditional will simply discard it, although the onProgress is called. People will end up not uploading what is coming from the byte stream!!! – avioli Apr 01 '22 at 00:21
  • @avioli `handleData` will not be executed if events to the `sink` are not added, so `onProgress` will not be executed also. – Classy-Bear Apr 05 '22 at 13:37
  • this works for me, thank you so much <3 – Mohamed_Iyed Jul 02 '23 at 16:35
  • Can you update this code to initialize onProgress as non null? – markhorrocks Aug 26 '23 at 16:54
12

After waiting for a week or so. I didn't get response. Thus I developed a plugin myself to get this behavior. Package link.

Example to use it:

var request = MultipartRequest();

request.addFile("image", imagePath);

Response response = request.send();

response.onError = () {
  print("Error");
};

response.onComplete = (response) {
  print(response);
};

//Not 100% success
response.progress.listen((int progress) {
  print("progress from response object " + progress.toString());
});

Update Jun 30, 2020

The package now supports iOS as well.

Kai
  • 149
  • 2
  • 5
Aawaz Gyawali
  • 3,244
  • 5
  • 28
  • 48
  • Did you re run the app once you added the plugin? – Aawaz Gyawali Oct 27 '20 at 15:59
  • I have a screen file and a handler file where the request is being made. In the screen file, it is waiting for the handler to post to the API. But as soon as it calls the handler it is coming back to the screen Showing it is back to the screen file and also it is printing the progress in the handler file at the same time. I want to show the progress on the screen. How to do it? – Hitesh Pandey Oct 27 '20 at 16:58
  • Thats not related to this question nor the answer. It's up to you on how you handle the callbacks from the package. You may search for another related question or ask a new one. And please don't delete your comments if a followup comment answered your question, the people how answer your comment will look stupid on the thread. – Aawaz Gyawali Oct 28 '20 at 06:29
0

Can you try this class as I did not test it yet, Let me know of any thing printed in your console.

class MF extends http.MultipartRequest{
  MF(String method, Uri url) : super(method, url);
  @override
  Future<http.StreamedResponse> send() async {

    var client = new Client();
    int byteCount = 0;
    Stream<List<int>> onDone<T>(Stream<List<int>> stream, void onDone()) =>
        stream.transform(new StreamTransformer.fromHandlers(
            handleDone: (sink) {
              sink.close();
              onDone();
        },
          handleData: (data, sink) {
            byteCount += data.length;
            print(byteCount);
            sink.add(data);
          },
        ),
        );

    try {
      var response = await client.send(this);
      var stream = onDone(response.stream, client.close);
      return new StreamedResponse(new ByteStream(stream), response.statusCode,
          contentLength: response.contentLength,
          request: response.request,
          headers: response.headers,
          isRedirect: response.isRedirect,
          persistentConnection: response.persistentConnection,
          reasonPhrase: response.reasonPhrase);
    } catch (_) {
      client.close();
      rethrow;
    }
  }

}

USAGE:

instead of var request = http.MultipartRequest("POST", uri);

use:

var request = MF("POST", uri);
Saed Nabil
  • 6,705
  • 1
  • 14
  • 36