nice_downloader 1.3.0 copy "nice_downloader: ^1.3.0" to clipboard
nice_downloader: ^1.3.0 copied to clipboard

A clean, extensible file downloader for Flutter with IDM-style segmented downloads, pause/resume/cancel, persistence, retry policies and an interceptor pipeline.

Nice Downloader #

A clean, extensible file downloader for Flutter — pause / resume / cancel, resume-after-restart persistence, pluggable retry policies, and an interceptor pipeline that lets you hook into every step of a download's lifecycle.

https://github.com/user-attachments/assets/e2c462d3-dcfa-4811-9dea-8f729b667fb3

Features #

  • Segmented downloads (IDM-style) — large files are split into up to 8 parallel connections, each downloading its own byte range, for significantly faster transfers
  • ⏸️ Pause / Resume / Cancel — resumes from the exact byte offset using HTTP range requests (per segment for segmented downloads)
  • 💾 Resume after restart — download state is persisted (Hive CE by default) and picked up in the next session
  • 🔌 Interceptors — observe or modify every lifecycle step (create, start, chunk, pause, resume, complete, cancel, error) without touching the engine
  • 🔁 Retry policies — exponential backoff out of the box; mid-stream connection drops auto-resume from the bytes already on disk
  • 🚦 Speed limiting — cap a download's total speed (across all its segments) and change it live; no limit set = max speed
  • 🛡️ Integrity verification — downloaded bytes are checksummed (CRC-32); if the partial file is edited, truncated or corrupted while paused, resume detects it and re-fetches only the damaged range
  • 📡 Pluggable everything — transport, storage, connectivity check, file writing and retry behavior are all interfaces with sensible defaults
  • 📊 Rich progress — immutable snapshots with percent, readable sizes (12.5 MB) and speed (1.2 MB/s)

Quick start #

final manager = DownloadManager();

final task = await manager.createDownload(
  url: 'https://example.com/video.mp4',
  directory: (await getDownloadsDirectory())!.path,
  fileName: 'my_video', // optional — server-suggested name used otherwise
);

task.progressStream.listen((progress) {
  print('${progress.status.name} ${progress.percent}% '
      '(${progress.readableDownloaded} / ${progress.readableTotal} '
      'at ${progress.readableSpeed ?? '-'})');
});

await task.start();

// Later…
await task.pause();
await task.resume();
await task.cancel(); // deletes the partial file + persisted state

// Speed control — applies instantly, even mid-download:
task.speedLimit = 1024 * 1024; // cap at 1 MB/s
task.speedLimit = null;        // back to max speed (the default)

Configuration #

Everything is configured once on the manager and applies to all downloads it creates:

final manager = DownloadManager(
  config: DownloadConfig(
    interceptors: [LoggingInterceptor()],
    retryPolicy: ExponentialBackoffRetryPolicy(maxRetries: 5),
    waitForConnection: true,           // block until online instead of failing
    progressInterval: Duration(milliseconds: 250),
    segmentPlanner: DefaultSegmentPlanner(maxSegments: 8), // parallel connections
    // segmentPlanner: NoSegmentationPlanner(),   // force single-stream
    // speedLimit: 2 * 1024 * 1024,               // cap at 2 MB/s (default: max)
    // repository: InMemoryDownloadRepository(),  // disable persistence
    // client: MyCustomDownloadClient(),          // custom transport
    // connectivityChecker: MyChecker(),          // e.g. connectivity_plus
  ),
);

Segmented downloads — how the speed comes from #

When the server supports HTTP range requests (206 Partial Content) and the file is large enough, the download is automatically split — exactly like IDM on Windows:

File: 100 MB → 4 segments

Connection 1 ──▶ bytes 0–25 MB    ──┐
Connection 2 ──▶ bytes 25–50 MB   ──┤── all write into ONE pre-allocated
Connection 3 ──▶ bytes 50–75 MB   ──┤   file, each at its own offset
Connection 4 ──▶ bytes 75–100 MB  ──┘   (no merge step needed)
  • Falls back to a single stream automatically when the server lacks range support, the size is unknown, or the file is smaller than 2 × minSegmentSize.
  • Each segment retries and resumes independently — a dropped connection re-attaches at its own offset.
  • Per-segment progress is persisted (≤ 1 s behind), so even a killed app resumes every segment where it stopped.
  • Your code doesn't change: same task.start()/pause()/resume()/cancel(), same aggregated progress stream.

Interceptors — hook into every step #

Extend DownloadInterceptor and override only what you need. Interceptors run in registration order; onCreate can rewrite the request before the download is built:

class AuthInterceptor extends DownloadInterceptor {
  @override
  Future<DownloadRequest> onCreate(DownloadRequest request) async {
    return request.copyWith(
      headers: {...request.headers, 'Authorization': 'Bearer $token'},
    );
  }

  @override
  Future<void> onComplete(DownloadTask task) async {
    // e.g. verify checksum, fire a local notification, log analytics…
  }

  @override
  Future<void> onError(DownloadTask task, Object error, StackTrace st) async {
    // report to Sentry / Crashlytics
  }
}

A throwing interceptor never breaks a download — lifecycle notifications are isolated.

Resuming after an app restart #

// Rebuild your downloads screen from persisted state:
final records = await manager.persistedDownloads();
for (final record in records) {
  final task = await manager.createDownload(
    url: record.url,
    directory: File(record.filePath).parent.path,
  );
  await task.start(); // continues from the last written byte
}

Architecture #

DownloadManager (Facade)
 └─ DownloadTask (state machine: idle → connecting → downloading → completed/paused/failed/canceled)
     ├─ DownloadClient        (Strategy)  — transport, default: package:http
     ├─ DownloadRepository    (Repository) — persistence, default: Hive CE
     ├─ ConnectivityChecker   (Strategy)  — default: DNS lookup
     ├─ RetryPolicy           (Strategy)  — default: exponential backoff
     ├─ SegmentPlanner        (Strategy)  — default: ≤8 parts of ≥2 MB
     ├─ DownloadFileWriter    (Strategy)  — default: RandomAccessFile
     ├─ InterceptorChain      (Chain of Responsibility) — your hooks
     └─ SegmentDownloader × N — one per parallel connection

Every collaborator is an abstraction injected through DownloadConfig, so the package is extended by adding implementations — never by editing the engine.

Statuses #

Status Meaning
idle Created, not started
connecting Checking connectivity / opening the connection
downloading Bytes are being written
paused Stopped by the user; resumable
completed All bytes written
failed Unrecoverable error (progress.error holds it)
canceled Canceled; partial file and state removed
2
likes
160
points
35
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

A clean, extensible file downloader for Flutter with IDM-style segmented downloads, pause/resume/cancel, persistence, retry policies and an interceptor pipeline.

Repository (GitHub)
View/report issues

Topics

#download #downloader #download-manager #http #network

License

MIT (license)

Dependencies

flutter, hive_ce, hive_ce_flutter, http, meta, path

More

Packages that depend on nice_downloader