Flutter: Как получить прогресс загрузки / выгрузки для http-запросов

Я пишу приложение, которое загружает изображение на сервер, и вместо того, чтобы просто показывать счетчик, я хотел бы получить возможность отслеживать статус этой загрузки.

Кроме того, я хочу сделать это без использования данных формы Multipart. Это код, который я сейчас использую - но, похоже, он застопорился из-за разорванного канала, и у меня нет нулевой обратной связи относительно того, отправляются ли данные на сервер:

Future<String> _uploadFile(File assetFile) async {
  final url = <removed>;

  final stream = await assetFile.openRead();
  int length = assetFile.lengthSync();

  final client = new HttpClient();

  final request = await client.postUrl(Uri.parse(url));
request.headers.add(HttpHeaders.CONTENT_TYPE,  "application/octet-stream");
  request.contentLength = length;

  await request.addStream(stream);
  final response = await request.close();
  // response prociessing.
}

Можно ли отправлять большие данные в виде потока, не считывая их в память, и могу ли я добиться прогресса в этой загрузке с помощью текущих API дротиков / флаттеров?

4 ответа

Решение

То, что вы уже используете Stream означает, что вы не читаете весь файл в память. Это читается как, вероятно, куски 64 КБ.

Вы можете перехватить поток между производителем (File) и потребителем (HttpClient) с помощью StreamTransformer, например так:

  int byteCount = 0;
  Stream<List<int>> stream2 = stream.transform(
    new StreamTransformer.fromHandlers(
      handleData: (data, sink) {
        byteCount += data.length;
        print(byteCount);
        sink.add(data);
      },
      handleError: (error, stack, sink) {},
      handleDone: (sink) {
        sink.close();
      },
    ),
  );
....
  await request.addStream(stream2);

Тебе следует увидеть byteCount инкремент в 64k кусков.

Снимок экрана:


Это решение

  1. Скачивает изображение с сервера.
  2. Показывает прогресс загрузки.
  3. После загрузки изображение сохраняется в хранилище устройства.

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

int _total = 0, _received = 0;
http.StreamedResponse _response;
File _image;
List<int> _bytes = [];

Future<void> _downloadImage() async {
  _response = await http.Client().send(http.Request('GET', Uri.parse("https://upload.wikimedia.org/wikipedia/commons/f/ff/Pizigani_1367_Chart_10MB.jpg")));
  _total = _response.contentLength;

  _response.stream.listen((value) {
    setState(() {
      _bytes.addAll(value);
      _received += value.length;
    });
  }).onDone(() async {
    final file = File("${(await getApplicationDocumentsDirectory()).path}/image.png");
    await file.writeAsBytes(_bytes);
    setState(() {
      _image = file;
    });
  });
}

@override
Widget build(BuildContext context) {
  return Scaffold(
    floatingActionButton: FloatingActionButton.extended(
      label: Text("${_received ~/ 1024}/${_total ~/ 1024} KB"),
      icon: Icon(Icons.file_download),
      onPressed: _downloadImage,
    ),
    body: Padding(
      padding: const EdgeInsets.all(20.0),
      child: Center(
        child: SizedBox.fromSize(
          size: Size(400, 300),
          child: _image == null ? Placeholder() : Image.file(_image, fit: BoxFit.fill),
        ),
      ),
    ),
  );
}

Попробуйте дио библиотеку. ВonSendProgress обратный звонок будет полезен.

пример:

response = await dio.post(
  "http://www.example.com",
  data: data,
  onSendProgress: (int sent, int total) {
    print("$sent $total");
  },
);

Ссылка: https://github.com/flutterchina/dio/issues/103

Обновлять:

Через день после публикации я понял, что на самом деле это не измерение прогресса загрузки , а только прогресс чтения байтов из локальной полезной нагрузки JSON, что происходит почти мгновенно. Если/когда я выясню, как на самом деле измерить прогресс загрузки , я обновлю этот ответ.


Оригинальный ответ:

Это работает для меня без использования Multipart:

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

// ...

final jsonPayload = {'base64File': 'abc123', 'something': 'else'};

// We are using a StreamedRequest so we can track the upload progress
final streamedRequest = http.StreamedRequest("POST", apiUri);
streamedRequest.headers['content-type'] = 'application/json';

// Length transferred (to calculate upload progress)
var transferredLength = 0;
// Upload progress (from 0.0 to 1.0)
var uploadProgress = 0.0;
// The stringified JSON payload
var stringEncodedPayload = jsonEncode(jsonPayload);
// Total length (to calculate upload progress)
var totalLength = stringEncodedPayload.length;

// Create a stream of the payload string
Stream.value(stringEncodedPayload)
  // Transform the string-stream to a byte stream (List<int>)
  .transform(utf8.encoder)
  // Start reading the stream in chunks, submitting them to the streamedRequest for upload
  .listen((chunk) {
    transferredLength += chunk.length;
    uploadProgress = transferredLength / totalLength;
    print("Chunk: ${chunk.length}, transferred: $transferredLength, progress: $uploadProgress");
    streamedRequest.sink.add(chunk);
  }, onDone: () {
    print("Done. Total: $totalLength, transferred: $transferredLength, progress: $uploadProgress");
    streamedRequest.sink.close();
  });

final result = await client.send(streamedRequest).then(http.Response.fromStream);


print("----------->");
print(result.statusCode);
print(result.body);
print("<-----------");

Выход:

      flutter: Chunk: 1024, transferred: 1024, progress: 0.0008807503580198599
flutter: Chunk: 1024, transferred: 2048, progress: 0.0017615007160397197
flutter: Chunk: 1024, transferred: 3072, progress: 0.0026422510740595796
...
flutter: Chunk: 1024, transferred: 1159168, progress: 0.9970094052784814
flutter: Chunk: 1024, transferred: 1160192, progress: 0.9978901556365013
flutter: Chunk: 1024, transferred: 1161216, progress: 0.9987709059945211
flutter: Chunk: 1024, transferred: 1162240, progress: 0.9996516563525409
flutter: Chunk: 405, transferred: 1162645, progress: 1.0
flutter: Done. Total: 1162645, transferred: 1162645, progress: 1.0
Другие вопросы по тегам