flutter_saver
一个用于在所有平台上保存图像和可下载链接的 Flutter 包,支持多种格式。
SwanFlutter/flutter_saver open-source repository details.
{"sdk":"flutter"}{"sdk":"flutter"}^1.1.1^1.6.0^1.9.1^0.3.5+4^2.1.8{"sdk":"flutter"}^6.0.0以下为英文项目原文快照,最新内容请访问 GitHub。
A cross-platform Flutter plugin for saving any file type — images, videos, PDFs, audio, zip archives, and more — to device storage, and for downloading files from a network URL.
One API, every platform. No per-platform branching in your code.
| Platform | Save local file | Download from network |
|---|---|---|
| Android | ✅ | ✅ |
| iOS | ✅ | ✅ |
| macOS | ✅ | ✅ |
| Windows | ✅ | ✅ |
| Linux | ✅ | ✅ |
| Web | ✅ (browser download) | ✅ (browser download) |
dependencies:
flutter_saver: ^2.4.0
Add the following to android/app/src/main/AndroidManifest.xml:
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<!-- Android 13+ (API 33) granular media permissions -->
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES"/>
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO"/>
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO"/>
<application
...
android:requestLegacyExternalStorage="true"> <!-- Required for API 29 -->
Note on API levels:
requestLegacyExternalStorage="true" is required.READ_MEDIA_* permissions instead of
(or in addition to) the legacy READ/WRITE_EXTERNAL_STORAGE permissions.Add to ios/Runner/Info.plist:
<key>NSPhotoLibraryUsageDescription</key>
<string>This app saves files to your device.</string>
<key>NSPhotoLibraryAddUsageDescription</key>
<string>This app saves files to your Photos library.</string>
Files are saved as named sub-folders inside the app's sandbox Documents/
directory (e.g. Documents/Pictures/, Documents/Downloads/).
Add to macos/Runner/DebugProfile.entitlements and
macos/Runner/Release.entitlements:
<key>com.apple.security.files.downloads.read-write</key>
<true/>
<key>com.apple.security.files.pictures.read-write</key>
<true/>
<key>com.apple.security.files.movies.read-write</key>
<true/>
macOS saves to real system folders (~/Downloads, ~/Pictures, ~/Movies,
~/Documents, ~/Music). Directories without a native macOS equivalent
(dcim, podcasts, ringtones, etc.) fall back to ~/Downloads.
No extra setup required. Files are saved to the user's Downloads folder via
SHGetKnownFolderPath (Win32 Shell API). File writes happen on a background
thread and are marshalled back to the platform thread safely.
Other SaveDirectory values fall back to Downloads with a debug warning.
No extra setup required. Directories are resolved via GLib's XDG user-dirs
(g_get_user_special_dir). Supported directories:
| SaveDirectory | XDG directory |
|---|---|
| downloads | G_USER_DIRECTORY_DOWNLOAD |
| pictures | G_USER_DIRECTORY_PICTURES |
| movies / videos | G_USER_DIRECTORY_VIDEOS |
| documents | G_USER_DIRECTORY_DOCUMENTS |
| music | G_USER_DIRECTORY_MUSIC |
| all others | → Downloads fallback |
No extra setup required. The browser handles the save dialog.
SaveDirectory is ignored on Web (the browser controls the location).
downloadFile on Web first fetches the URL and downloads the response as a
Blob — this works for every format (jpg, png, svg, mp4, mp3, pdf, zip, …)
and keeps your suggested file name, but it requires the remote host to allow
CORS (Access-Control-Allow-Origin). Browsers deliberately block reading
cross-origin bytes without it, and they also ignore the download attribute
on cross-origin links — no library can bypass this.
When the host blocks CORS, flutter_saver automatically re-fetches the file through a CORS proxy so the download still happens. The proxies are configurable:
// Override with your own proxy (recommended for production — privacy:
// public proxies see the fetched URLs), or set to [] to disable the
// fallback (non-CORS hosts then open in a new tab instead).
FlutterSaver.webCorsProxies = [
'https://my-proxy.example.com/{rawUrl}', // {rawUrl} = plain URL
// 'https://my-proxy.example.com/?url={url}', // {url} = encoded URL
];
To make a file directly downloadable from your own server (best option — no proxy involved), serve it with:
Access-Control-Allow-Origin: *
Content-Disposition: attachment # optional — forces download even on navigation
import 'package:flutter_saver/flutter_saver.dart';
final Uint8List bytes = /* your file bytes */;
final SaveResult result = await FlutterSaver.saveFile(
bytes: bytes,
fileName: 'my_photo.jpg', // optional — random name if omitted
directory: SaveDirectory.pictures, // optional — defaults to downloads
);
if (result.success) {
print('Saved to: ${result.filePath}');
} else {
print('Error: ${result.error}');
}
Works with image_picker (camera & gallery) on every platform — the XFile
is read and pushed through the same unified save pipeline:
final XFile? photo = await ImagePicker().pickImage(
source: ImageSource.camera,
);
if (photo != null) {
final SaveResult result = await FlutterSaver.saveXFile(
photo,
directory: SaveDirectory.pictures,
);
print(result.success ? 'Saved: ${result.filePath}' : result.error);
}
The file name is taken from the XFile (name/path, or inferred from its
MIME type) unless you pass fileName explicitly.
final SaveResult result = await FlutterSaver.downloadFile(
link: 'https://example.com/document.pdf',
fileName: 'document.pdf', // optional — inferred from URL / Content-Type
directory: SaveDirectory.documents, // optional — defaults to downloads
);
if (result.success) {
print('Downloaded to: ${result.filePath}');
}
| Value | Android | iOS (sandbox) | macOS | Windows | Web |
|---|---|---|---|---|---|
downloads |
DOWNLOADS |
Documents/Downloads/ |
~/Downloads |
~/Downloads |
browser |
pictures |
PICTURES |
Documents/Pictures/ |
~/Pictures |
⬇ Downloads | browser |
movies |
MOVIES |
Documents/Movies/ |
~/Movies |
⬇ Downloads | browser |
dcim |
DCIM |
Documents/DCIM/ |
⬇ Pictures | ⬇ Downloads | browser |
documents |
DOCUMENTS |
Documents/Documents/ |
~/Documents |
⬇ Downloads | browser |
music |
MUSIC |
Documents/Music/ |
~/Music |
⬇ Downloads | browser |
podcasts |
⬇ Music | Documents/Podcasts/ |
⬇ Downloads | ⬇ Downloads | browser |
ringtones |
RINGTONES |
Documents/Ringtones/ |
⬇ Downloads | ⬇ Downloads | browser |
alarms |
ALARMS |
Documents/Alarms/ |
⬇ Downloads | ⬇ Downloads | browser |
notifications |
NOTIFICATIONS |
Documents/Notifications/ |
⬇ Downloads | ⬇ Downloads | browser |
screenshots |
⬇ Pictures | Documents/Screenshots/ |
⬇ Downloads | ⬇ Downloads | browser |
audiobooks |
⬇ Music | Documents/Audiobooks/ |
⬇ Downloads | ⬇ Downloads | browser |
⬇ = falls back to the indicated directory; debug warning is printed.
class SaveResult {
final bool success;
final String? filePath; // null on Web
final String? error; // non-null when success == false
}
Images: jpg, png, gif, webp, svg, tiff, ico, bmp, psd, djvu, xbm
Video: mp4, avi, mpeg, mov, wmv, flv, webm, ogv, mkv, 3gp, 3g2
Audio: mp3, wav, ogg, flac, aac, m4a, opus, wma, aiff, mka
Documents: pdf, zip, docx, xlsx, pptx, exe, dwg
| v1 method | v2 equivalent |
|---|---|
FlutterSaver.saveImageAndroid(data, name, dir) |
FlutterSaver.saveFile(bytes: data, fileName: name, directory: dir) |
FlutterSaver.saveImageIos(data, name, dir) |
FlutterSaver.saveFile(bytes: data, fileName: name, directory: dir) |
FlutterSaver.saveImageMacOs(data, name, dir) |
FlutterSaver.saveFile(bytes: data, fileName: name, directory: dir) |
FlutterSaver.saveImageWindowsWeb(data, name) |
FlutterSaver.saveFile(bytes: data, fileName: name) |
FlutterSaver.saveNetworkFileAndroid(link, name, dir) |
FlutterSaver.downloadFile(link: link, fileName: name, directory: dir) |
FlutterSaver.saveNetworkFileIos(link, name, dir) |
FlutterSaver.downloadFile(link: link, fileName: name, directory: dir) |
FlutterSaver.saveNetworkFileMac(link, name, dir) |
FlutterSaver.downloadFile(link: link, fileName: name, directory: dir) |
FlutterSaver.saveNetworkFileWindowsWeb(link, name) |
FlutterSaver.downloadFile(link: link, fileName: name) |
AndroidDirectory.downloads |
SaveDirectory.downloads |
DirectoryType.pictures |
SaveDirectory.pictures |
MacDirectoryType.movies |
SaveDirectory.movies |
Return bool |
Return SaveResult — check result.success |
MIT