Skip to content

Replace flutter_downloader with file_saver_ffi#104

Open
vanvixi wants to merge 1 commit intoNetShareOSS:masterfrom
vanvixi:features/implement-file_saver_ffi
Open

Replace flutter_downloader with file_saver_ffi#104
vanvixi wants to merge 1 commit intoNetShareOSS:masterfrom
vanvixi:features/implement-file_saver_ffi

Conversation

@vanvixi
Copy link

@vanvixi vanvixi commented Mar 5, 2026

Replace flutter_downloader with file_saver_ffi for cross-platform downloads

Motivation

flutter_downloader works well on Android/iOS, but its architecture adds significant complexity to the codebase:

  • Requires a background isolate with IsolateNameServer and ReceivePort just to receive download callbacks
  • Needs a @pragma("vm:entry-point") static callback and manual IsolateNameServer registration/cleanup in initState/dispose
  • Desktop downloads had to fall back to a separate http-based implementation with no progress tracking
  • Task state must be queried from an internal SQLite DB via loadTasksWithRawQuery
  • Contains a workaround for Flutter engine bug #119589

All of this makes ClientWidget hard to understand and maintain.

Solution

This PR replaces flutter_downloader with file_saver_ffi — a cross-platform file saving library that:

  • Runs on a native background thread (Swift on iOS/macOS, Kotlin on Android) — no UI blocking, no Dart isolate management needed
  • On Windows and Linux, uses pure Dart with chunked I/O so the main thread is never blocked
  • Saves files in chunks and reports progress on all platforms
  • Wraps everything behind a clean Stream<SaveProgress> API — callers need zero knowledge of threading or platform internals

Key improvements:

Before After
Platform support Android + iOS only (desktop: separate http impl) Android, iOS, macOS, Windows, Linux
Threading Manual Dart isolate + IsolateNameServer boilerplate Handled internally by the library (native threads / chunked Dart)
Progress tracking Not implemented Real-time via SaveProgressUpdate(double progress)
Lines removed ~100 lines of boilerplate deleted

Changes

  • ClientWidget: Removed ReceivePort, IsolateNameServer, _initDownloadModule(), downloadCallback() static method, and 3 unused imports. _downloadStreamListener() is unchanged.
  • DownloadService: Replaced two platform-branched download methods with a single startDownloading() using FileSaver.save(). MIME type detected automatically via the existing mime package.
  • DownloadEntity: Removed unused id and manner fields; added progress field and copyWith().
  • FileProvider: Added in-memory _progressMap + getProgress() for granular progress updates without polluting the Hive entity.
  • FileTileClient: Progress indicator now shows determinate progress (0-100%) using Selector<FileProvider, double> with shouldRebuild to avoid full-list rebuilds on every tick.
  • Removed download_manner.dart, uuid dependency, and android_path_provider.

Result

The entire download flow — including real-time progress — now works identically on all platforms with significantly less code. Threading and chunk management are handled by file_saver_ffi internally; the app just listens to a stream.


Note: file_saver_ffi is published on pub.dev (^0.8.1). No additional native setup is required beyond what Flutter already provides.

Copy link
Member

@huynguyennovem huynguyennovem left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awsome. Thank you for doing this! Overall looks good to me, except two issues:

  1. ios and macos file changes don't relate to this work. Could you please revert them and re-commit the change?
  2. After downloading a file, it is no longer possible to open it (please watch the video). I printed the output path and saw that it is something like content://media/external_primary/file/1000000232 now, which seems to be exposed from the file_saver_ffi package, doesn't it? Previously, a file was downloaded and stored to AndroidPathProvider.downloadsPath. I also see it's AndroidSaveLocation.downloads on Android by default in package file_saver_ffi. Not sure if these two paths are the same. Could you please check this?
Screen.Recording.2026-03-07.at.17.01.34.mov

@vanvixi vanvixi force-pushed the features/implement-file_saver_ffi branch from b2bc6ee to 1a8d844 Compare March 9, 2026 10:31
@vanvixi
Copy link
Author

vanvixi commented Mar 9, 2026

@huynguyennovem Thank you for the review!

Regarding the iOS and macOS changes:

  • iOS AppDelegate changes: These are necessary. flutter_downloader requires specific delegate setup in the native AppDelegate file. Since we're removing it from the Dart side, the corresponding native boilerplate must be cleaned up as well to avoid build errors and dead code.

  • macOS Podfile change (platform :osx, '10.15'platform :osx, '10.15.4'): This is also required. file_saver_ffi has a minimum macOS deployment target of 10.15.4. Without this bump, the macOS build will fail due to the platform version constraint.


Regarding the Android open-file issue:

You're right that file_saver_ffi now returns a content:// URI (e.g., content://media/external_primary/file/1000000232) instead of a raw file path. This is intentional and aligns with Android's evolving storage security model.

Starting from Android 10 (API 29), apps are required to use Scoped Storage, and direct access to file paths on shared storage via Environment.getExternalStorageDirectory() is restricted. In Android 11+ (API 30), this is strictly enforced. Google has been progressively deprecating raw file:// URI access in favor of content:// URIs backed by MediaStore or FileProvider. You can refer to the official documentation here:

The issue is that open_filex currently does not support content:// URIs on Android — it only handles file:// path URIs. This is not a coincidental gap: there are open issues on both the open_file and open_filex repositories requesting content:// URI support, but the maintainers have shown no intention of addressing it, treating it as out of scope. Rather than working around this by resolving the URI back to a file path (which defeats the purpose of Scoped Storage compliance and reintroduces the same security concerns Android is trying to eliminate), file_saver_ffi provides a built-in method to open the saved file directly, using the proper Android intent mechanism that fully supports content:// URIs.

@vanvixi vanvixi requested a review from huynguyennovem March 9, 2026 15:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants