How to get progress event while uploading file on http.MultipartRequest request in flutter
Asked Answered
H

3

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.

Helban answered 11/12, 2018 at 16:0 Comment(2)
what is Iterable<int> message inside forEach loop?Ecuador
I am getting response of the request there as a stream, that's called once the upload is completed.Helban
H
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.

Helban answered 18/12, 2018 at 17:38 Comment(3)
Did you re run the app once you added the plugin?Helban
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?Almaraz
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.Helban
D
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();
Dagan answered 4/11, 2019 at 4:7 Comment(6)
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 requestAndel
The video upload never completes and for a image The upload happens at progress: 21.52558699578567 (35754/1661)Andel
@MaheshJamdade I updated the answer now the upload completesApo
@Apo 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!!!Dagan
@Dagan handleData will not be executed if events to the sink are not added, so onProgress will not be executed also.Apo
Can you update this code to initialize onProgress as non null?Estevan
H
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.

Helban answered 18/12, 2018 at 17:38 Comment(3)
Did you re run the app once you added the plugin?Helban
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?Almaraz
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.Helban
P
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);
Pedagogics answered 13/12, 2018 at 21:39 Comment(3)
I will test and revert to you soon. Thanks anyway.Helban
Sadly that didn't work, I am getting handleData called after the request is sent.Helban
Did you checked the console ? Is there any output from the print statement for byteCount?Pedagogics

© 2022 - 2024 — McMap. All rights reserved.