Tag Archives: Download large files with flutter

Realization of breakpoint download based on DIO in flutter

As there are not many articles downloaded from the shutter breakpoint, and there are few cases, I found them on the DIO case after searching for them for a long time, and it took me some time to be wronged
based on DIO: ^ 4.0.0 and Path_ Provider: ^ 2.0.2 implements the function of downloading a large file

Core code

import 'dart:async';
import 'dart:io';
import 'dart:typed_data';
import 'package:dio/dio.dart';
import 'package:path_provider/path_provider.dart';

class DownloadFile {
  /// Used to record the url being downloaded to avoid duplicate downloads
  static var downloadingUrls = Map<String, CancelToken>();

  /// Breakpoint downloading of large files
  static Future<void> download({
    required String url,
    required String savePath,
    ProgressCallback?onReceiveProgress,
    void Function()?done,
    void Function(DioError)?failed,
  }) async {
    int downloadStart = 0;
    bool fileExists = false;
    File f = File(savePath);
    if (await f.exists()) {
      downloadStart = f.lengthSync();
      fileExists = true;
    }
    print("start: $downloadStart");
    if (fileExists && downloadingUrls.containsKey(url)) {
      return;
    }
    var dio = Dio();
    CancelToken cancelToken = CancelToken();
    downloadingUrls[url] = cancelToken;
    try {
      var response = await dio.get<ResponseBody>(
        url,
        options: Options(
          /// Receive response data as a stream
          responseType: ResponseType.stream,
          followRedirects: false,
          headers: {
            /// Downloading key locations in segments
            "range": "bytes=$downloadStart-",
          },
        ),
      );
      File file = File(savePath);
      RandomAccessFile raf = file.openSync(mode: FileMode.append);
      int received = downloadStart;
      int total = await _getContentLength(response);
      Stream<Uint8List> stream = response.data!.stream;
      StreamSubscription<Uint8List>?subscription;
      subscription = stream.listen(
        (data) {
          /// Write files must be synchronized
          raf.writeFromSync(data);
          received += data.length;
          onReceiveProgress?.call(received, total);
        },
        onDone: () async {
          downloadingUrls.remove(url);
          await raf.close();
          done?.call();
        },
        onError: (e) async {
          await raf.close();
          downloadingUrls.remove(url);
          failed?.call(e as DioError);
        },
        cancelOnError: true,
      );
      cancelToken.whenCancel.then((_) async {
        await subscription?.cancel();
        await raf.close();
      });
    } on DioError catch (error) {
      /// The request has been sent and the server responds with a status code that it is not in the range of 200
      if (CancelToken.isCancel(error)) {
        print("Download cancelled");
      } else {
        failed?.call(error);
      }
      downloadingUrls.remove(url);
    }
  }

  /// Get the size of the downloaded file
  static Future<int> _getContentLength(Response<ResponseBody> response) async {
    try {
      var headerContent =
          response.headers.value(HttpHeaders.contentRangeHeader);
      print("download files: $headerContent");
      if (headerContent != null) {
        return int.parse(headerContent.split('/').last);
      } else {
        return 0;
      }
    } catch (e) {
      return 0;
    }
  }

  /// Cancel Mission
  static void cancelDownload(String url) {
    downloadingUrls[url]?.cancel();
    downloadingUrls.remove(url);
  }
}

Call case

void main() {
  runApp(TestMyApp());
}

class TestMyApp extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return _TestMyAppState();
  }
}

class _TestMyAppState extends State<TestMyApp> {
  void download() async {
    var url = "mp4";
    Directory dir = await getApplicationDocumentsDirectory();
    var sDCardDir = dir.path;
    var savePath = sDCardDir + "/video/1.mp4";
    File f = File(sDCardDir + "/video");
    if (!await f.exists()) {
      new Directory(sDCardDir + "/video").createSync();
    }

    await DownLoadManage().download(
      url: url,
      savePath: savePath,
      onReceiveProgress: (received, total) {
        if (total != -1) {
          print("Download1Received:" +
              received.toString() +
              "Total:" +
              total.toString() +
              "Progress.+${(received/total * 100).floor()}%");
        }
      },
      done: () {
        print("Download 1 completed");
      },
      failed: (e) {
        print("Download 1 failed:" + e.toString());
      },
    );
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.white,
      child: Center(
        child: GestureDetector(
          onTap: () {
            download();
          },
          child: Container(
            width: 150,
            height: 150,
            color: Colors.red,
          ),
        ),
      ),
    );
  }
}