20

I have the following code to upload a file.

var client = new dio.Dio();
await client.put(
  url,
  data: formData,
  options: dio.Options(
    headers: headers,
  ),
  onSendProgress: (int sent, int total) {
    final progress = sent / total;
    print('progress: $progress ($sent/$total)');
  },
);

Uploading the file works fine. The thing is, I have a view that has a circular progress bar that indicates the upload progress when onSendProgress is triggered. When I upload a ~10MB file the progress bars jumps from 0% to 100% within a second and then waits for a couple of seconds (depending on the file size) before continuing with the rest of the code. This seems weird to me because I expect the progress bar to gradually increase its progress.

Below you can find the print outputs from my example with a ~10MB file.

flutter: progress: 0.000003035281907563734 (29/9554302)
flutter: progress: 0.000013187776563897604 (126/9554302)
flutter: progress: 0.999996546058519 (9554269/9554302)
flutter: progress: 0.9999967553883057 (9554271/9554302)
flutter: progress: 1.0 (9554302/9554302)

This one is with a smaller file, but the same amount of callbacks.

flutter: progress: 0.00001847214941293598 (29/1569931)
flutter: progress: 0.00008025830434585978 (126/1569931)
flutter: progress: 0.9999789799679094 (1569898/1569931)
flutter: progress: 0.9999802539092483 (1569900/1569931)
flutter: progress: 1.0 (1569931/1569931)

As you can see it jumps from 0% to 100%. I can imagine you thinking, that is just fast internet. But I've tried Wifi, 4G, 3G and they all show the same issue. I hit the upload button, it start on 0% it jumps to 100% and then I have to wait some time (depending on the file size) for the upload to finish.

Is there a way to get more amount of onSendProgress callbacks triggered during the upload or somehow to delay the upload so I can get a smooth upload progress?

EDIT:

I've tried the following with the HTTP package

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

class UploadRequest extends http.MultipartRequest {
  final void Function(int bytes, int totalBytes) onProgress;

  UploadRequest(
    String method,
    Uri url, {
    this.onProgress,
  }) : super(method, url);

  http.ByteStream finalize() {
    final byteStream = super.finalize();
    if (onProgress == null) return byteStream;

    final total = this.contentLength;

    final t = StreamTransformer.fromHandlers(
      handleData: (List<int> data, EventSink<List<int>> sink) {
        onProgress(data.length, total);
        sink.add(data);
      },
    );

    final stream = byteStream.transform(t);
    return http.ByteStream(stream);
  }
}

Trigger:

final request = UploadRequest(
  'PUT',
  Uri.parse(url),
  onProgress: (int bytes, int total) {
    print('progress: $progress ($sent/$total)');
  },
);

request.headers.addAll(headers);
request.files.add(
  http.MultipartFile.fromBytes(
    'file',
    attachment.file.buffer.asUint8List(),
    filename: attachment.name,
    contentType: MediaType.parse(attachment.contentType),
  ),
);


var response = await request.send();

But sadly, this has the same issue. The file uploads fine, but the progress callback is called only a few times. It hits 100% right away, then I have to wait a while (depending on the file size) before getting a 2xx response from the server. So I don't think this is a specific DIO issue.

I think that it looks like it is not actually showing the upload progress, but rather the progress of the file being read into a stream clientside. Then the stream is uploaded to the server and that is what you are waiting on (of course depending on the file size) even though the progress shows 100%.

Tom Aalbers
  • 4,574
  • 5
  • 29
  • 51
  • I believe you need to use setstate inside onSendProgress – Abbas May 06 '20 at 12:33
  • 2
    Hi Abbas, thank you for your reply. I know that I need to use setState to re-render the view, this is a simplified example, but that is not the issue. The issue is that `onSendProgress` is only a few times. Only when its 0% and when its 100%. I need to it to be called between those percentages as well. E.g. 10%, 20%, 50%, 75% etc. – Tom Aalbers May 06 '20 at 12:40
  • Yes, my bad. It's weird though cause I did something similar yesterday with dio.download and it worked perfectly fine as you are expecting this to work. Following! – Abbas May 06 '20 at 12:47
  • This has happened to me before, when I send only one file the counter jumps from 0% to 100% and then it gets a little bit late, and then it's done, I think this is a problem in **dio** package, you can try to send more than one file – farouk osama May 30 '20 at 23:15
  • @faroukosama Thanks for the reply. See my EDIT on the OP. I don't think this is just DIO bug. – Tom Aalbers Jun 02 '20 at 11:27
  • Well, if dio uses the HTTP package under the hood, than this could be a bug on the HTTP Package that dio can't avoid. – Michel Feinstein Jun 02 '20 at 19:36
  • @mFeinstein Correct, I agree. Basically DIO does the same as I did in my example that I've added to the OP. See [here](https://github.com/flutterchina/dio/blob/5e3eb797a4202b9aca0acc74d325c10523b79584/dio/lib/src/dio.dart#L1035-L1051) how DIO does it – Tom Aalbers Jun 03 '20 at 07:03
  • Open a bug report on the HTTP package then. – Michel Feinstein Jun 03 '20 at 07:06
  • Was looking into that already. [This issue is old and closed](https://github.com/dart-lang/http/issues/153) and [this issue is still open](https://github.com/dart-lang/sdk/issues/35427). It has an answer with something that I haven't tried yet, but he is saying that it only works for Android, so it doesn't sound as a proper solution to me. – Tom Aalbers Jun 03 '20 at 07:26
  • Any update on this @TomAalbers?Have you find anything? – Jigar Fumakiya Jan 07 '21 at 05:13
  • Nope. Sadly still having the same issue. – Tom Aalbers Jan 07 '21 at 10:18
  • As I assume, this is due to the work of the algorithm, which counts the transferred bytes when reading the file from disk (by filepath while using the fromFile method) instead of actually accounting for the bytes transferred by network. When using fromBytes, no chunks are read because all the bytes have already been read and allocated in memory. – HiTech Aug 26 '21 at 15:51

6 Answers6

9

This appears to be a bug on dio, I can see two ways you can get around this:

  1. Just add a number to your circular progress, it will say 99% (truncate the rest) so the users will know its not done yet, as 100% will only happen when you get 1.0 as a result).

  2. use the http package instead. the http package as a StreamedRequest which would give you finer control over what's being sent, so you can reflect this on your UI.

Michel Feinstein
  • 13,416
  • 16
  • 91
  • 173
  • Thanks for the reply. 1). See the edit on the OP, I'm afraid it's not just a DIO bug. I understand what you're trying to do, will try to use that as a backup. But its not really a solution to the issue. 2) Got any examples on how to use it? – Tom Aalbers Jun 02 '20 at 11:24
  • Try [this](https://stackoverflow.com/a/52947845/1227166) and see if it helps. I am not 100% sure it will be what you want as I am not sure if everything added to the Stream can be considered to be sent, the docs don't make it clear and they also don't mention how errors are being reported (are they immediate? Is it there a retry?). – Michel Feinstein Jun 02 '20 at 19:56
  • Regarding using StreamedRequest, it's actually behaving the same way: https://stackoverflow.com/a/70933507/2263395. It is possible that this would work: https://stackoverflow.com/a/50456212/2263395, however I haven't tried myself. Dio's onSendProgress seem to be working fine for file streams. Artificial streams e.g.: `Stream.fromIterable([bytes])` update progress correctly on web, but on desktop (e.g. linux) it's same issue (100% straight away, then waits). Talking about Dio 4.0.6. – Tom Raganowicz Jan 25 '23 at 22:10
4

I have found the resolution.

Just use file.openRead() as the request's data param while PUT a file.

For MultipartFile maybe it can use MultipartFile(file.openRead(), file.lenthSync()). But I did not verify this, someone can try it if necessary.

var client = Dio();
await client.put(
  url,
  data: file.openRead(),
  options: dio.Options(
    headers: headers,
  ),
  onSendProgress: (int sent, int total) {
    print('progress: ${(sent / total * 100).toStringAsFixed(0)}% ($sent/$total)');
  },
);

The progress callback is smooth now.

I/flutter (19907): progress: 9% (65536/734883)
I/flutter (19907): progress: 18% (131072/734883)
I/flutter (19907): progress: 27% (196608/734883)
I/flutter (19907): progress: 36% (262144/734883)
I/flutter (19907): progress: 45% (327680/734883)
I/flutter (19907): progress: 54% (393216/734883)
I/flutter (19907): progress: 62% (458752/734883)
I/flutter (19907): progress: 71% (524288/734883)
I/flutter (19907): progress: 80% (589824/734883)
I/flutter (19907): progress: 89% (655360/734883)
I/flutter (19907): progress: 98% (720896/734883)
I/flutter (19907): progress: 100% (734883/734883)
zhpoo
  • 726
  • 4
  • 10
0

I came across the same problem. I tried dozens of tricks but nothing worked. I had to replace MultipartFile.fromBytes with MultipartFile.fromFile. It worked for me.

0

you have to use formData as Map not FormData.fromMap() .

and you have to put options like :

Map formData ={
   your data here
 };

var client = new Dio();
await client.put(
  url,
  data: formData,
  options: Options(
    headers: {
      HttpHeaders.acceptHeader: "json/application/json",
      HttpHeaders.contentTypeHeader: "application/x-www-form-urlencoded"
    },
  ),
  onSendProgress: (int sent, int total) {
    final progress = sent / total;
    print('progress: $progress ($sent/$total)');
  },
);
-2

so simple in this example

function dioProcess(processfunction){
 var client = new dio.Dio();
 await client.put(
   url,
   data: formData,
   options: dio.Options(
    headers: headers,
  ),
  onSendProgress: processfunction
}

usage

dioProcess((int sent, int total) {
    final progress = sent / total;
    print('progress: $progress ($sent/$total)');
  },)
Rahman Rezaee
  • 1,943
  • 16
  • 24
-2

1.==use below function is working :==

1.use MultipartFile.fromFile(file.path) 
2.use MultipartFile.fromFileSync(file.path, filename: basename(file.path))

those percentages as well. E.g. 10%, 20%, 50%, 75% etc.

2.==use below function is not working :==

1.  MultipartFile.fromBytes(fileData)

As you can see it jumps from 0% to 100%

example : i use await MultipartFile.fromFile(file.path), onSendProgress is work fine.

Future<OSSResponse> postObjectWithFile(
    File file,
    String bucketName,
    String fileKey, {
      ProgressCallback onSendProgress,
    ObjectACL acl = ObjectACL.inherited,
  }) async {
    assert(acl != null, "ACL不能为空");
    final OSSCredential credential = await credentialProvider.getCredential();

    FormData formData = FormData.fromMap({
      'Filename': fileKey,
      'key': fileKey, 
      'policy': SignUtil.getBase64Policy(),
      'OSSAccessKeyId': credential.accessKeyId,
      'success_action_status': '200',
      'signature': SignUtil.getSignature(credential.accessKeySecret),
      'x-oss-security-token': credential.securityToken,
      'x-oss-object-acl': acl.parameter,
      'file': await MultipartFile.fromFile(file.path),//change code
    });

    final dio = DioUtil.getDio();
    final res = await dio.post(
      'https://$bucketName.$endpoint',
      options: Options(
        responseType: ResponseType.plain,
      ),
      data: formData,
      onSendProgress: (int sent, int total) {
        final progress = sent / total;
        print('progress: $progress ($sent/$total)');
      },
    );

    return OSSResponse(res.statusCode, res.statusMessage, fileKey);
  }

onSendProgress is working.

dabai
  • 1
  • 1
  • What is this? Is this an answer or a question? In any case you should elaborate on it because it is not really clear. If it is a question, you should not post is as an answer. – Tom Aalbers Feb 02 '21 at 12:35
  • this is answer. use await MultipartFile.fromFile(file.path) replace, onSendProgress working fine – dabai Feb 03 '21 at 02:12
  • Hi, thanks. Sorry but I think your answer is wrong. If you would have read the question better then you would've read that it jumping from 0% to 100% *is* the problem. It is showing the progress of the file being *read in to a stream* instead of showing the progress of it being *send to the server*. – Tom Aalbers Feb 04 '21 at 12:06