8

I am working on an app that uploads an image to Amazon S3 bucket, and instead of just showing a Spinner, I'd love to be able to get Progress on the status of that upload.

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.

  static Future<bool> uploadFile({@required userId, @required albumId, @required data, @required fileName}) async{

    const _accessKeyId = 'AKIAVV**********';
    const _secretKeyId = 'iyBmdn6v***************************';
    const _region = 'us-east-1';
    const _s3Endpoint = 'https://myalbums-content.s3.amazonaws.com';

    final file = data;
    var stream = new http.ByteStream(DelegatingStream.typed(file.openRead()));
    final length = await file.length();

    final uri = Uri.parse(_s3Endpoint);
    final req = http.MultipartRequest("POST", uri);

    var multipartFile =  http.MultipartFile('file', stream, length,
       contentType: new MediaType('image','JPG'));

    final policy = Policy.fromS3PresignedPost(userId.toString() +'/'+ albumId.toString() +'/'+ fileName,
        'myalbums-content', _accessKeyId, 15, length, region: _region);

    final key = SigV4.calculateSigningKey(_secretKeyId, policy.datetime, _region, 's3');
    final signature = SigV4.calculateSignature(key, policy.encode());
    
    req.files.add(multipartFile);
    req.fields['key'] = policy.key;
    req.fields['acl'] = 'public-read';
    req.fields['X-Amz-Credential'] = policy.credential;
    req.fields['X-Amz-Algorithm'] = 'AWS4-HMAC-SHA256';
    req.fields['X-Amz-Date'] = policy.datetime;
    req.fields['Policy'] = policy.encode();
    req.fields['X-Amz-Signature'] = signature;

    try {
      final res = await req.send();

      if(res.statusCode == 204){
        return true;
      } else {
        return false;
      }
    } catch (e) {
      print('Network issue: '+e.toString());
      return false;
    }
  }

Thank you in advance.

Kanwarpreet Singh
  • 2,037
  • 1
  • 13
  • 28
  • 1
    Checkout this https://stackoverflow.com/a/58687082/5882307 – OMi Shah Aug 14 '20 at 08:34
  • send() is s future. Just use FutureBuilder with Send as it's Future – Kent Aug 14 '20 at 17:26
  • @Kent that won't work. The progress bar would just go from 0-100 in an instant because it only has two states, Future incomplete and Future complete. Without any way to get the number of bytes transferred vs the total number of bytes or the progress of the request, there's no way to do this. – Wilson Wilson Aug 14 '20 at 20:23
  • @WilsonWilson hmmm it returns a stream. So it should be possible at least on download. I do admit I haven't tried it. But the whole point of streams is to get data in chunks not just after complete. – Kent Aug 14 '20 at 20:43
  • @Kent I agree, it's very possible on download. But OP is returning a Future, not a Stream. So if he wants to get the progress, he needs to return a Stream. I just answered the question with an example from Github that shows exactly how it can be done. – Wilson Wilson Aug 14 '20 at 20:46
  • @WilsonWilson True he is returning a bool. MultipartRequest.send() returns a Future That was what I was getting at. But you have definitely answered his question instead of just trying to point him in the right direction. Well done. – Kent Aug 14 '20 at 20:59
  • @Kent Thanks! I actually didn't know it returned a StreamedResponse. That may even be easier to implement than this. I'll look up the documentation. – Wilson Wilson Aug 14 '20 at 21:00
  • @WilsonWilson I'm curios how that goes for you. Like I said haven't had the need to do it myself. Just was reading the docs and thought it would be the way to go based on the code OP provided. :) – Kent Aug 14 '20 at 21:10
  • It seems to be the easier and more appropriate solution. But I'm not sure you can directly compare the StreamedResponse byte size to that of the image file to be uploaded. I also came across this StackOverflow post that mentions a package makes it so much easier. https://stackoverflow.com/questions/53727911/how-to-get-progress-event-while-uploading-file-on-http-multipartrequest-request?noredirect=1&lq=1 – Wilson Wilson Aug 14 '20 at 21:15

1 Answers1

1

Check out this example on Github. It shows how you can upload the file using a Stream. If you expose the stream of bytes uploaded to your Widget, you should be able to use a ProgressIndicator that compares the total number of bytes to the value in the Stream.

static Future<String> fileUploadMultipart(
      {File file, OnUploadProgressCallback onUploadProgress}) async {
    assert(file != null);

    final url = '$baseUrl/api/file';

    final httpClient = getHttpClient();

    final request = await httpClient.postUrl(Uri.parse(url));

    int byteCount = 0;

    var multipart = await http.MultipartFile.fromPath(fileUtil.basename(file.path), file.path);

    // final fileStreamFile = file.openRead();

    // var multipart = MultipartFile("file", fileStreamFile, file.lengthSync(),
    //     filename: fileUtil.basename(file.path));

    var requestMultipart = http.MultipartRequest("", Uri.parse("uri"));

    requestMultipart.files.add(multipart);

    var msStream = requestMultipart.finalize();

    var totalByteLength = requestMultipart.contentLength;

    request.contentLength = totalByteLength;

    request.headers.set(
        HttpHeaders.contentTypeHeader, requestMultipart.headers[HttpHeaders.contentTypeHeader]);

    Stream<List<int>> streamUpload = msStream.transform(
      new StreamTransformer.fromHandlers(
        handleData: (data, sink) {
          sink.add(data);

          byteCount += data.length;

          if (onUploadProgress != null) {
            onUploadProgress(byteCount, totalByteLength);
            // CALL STATUS CALLBACK;
          }
        },
        handleError: (error, stack, sink) {
          throw error;
        },
        handleDone: (sink) {
          sink.close();
          // UPLOAD DONE;
        },
      ),
    );

    await request.addStream(streamUpload);

    final httpResponse = await request.close();
//
    var statusCode = httpResponse.statusCode;

    if (statusCode ~/ 100 != 2) {
      throw Exception('Error uploading file, Status code: ${httpResponse.statusCode}');
    } else {
      return await readResponseAsString(httpResponse);
    }
  }

req.send also returns a StreamedResponse which may be easier to implement.

Wilson Wilson
  • 3,296
  • 18
  • 50