SaverGallery는 Android, iOS 및 HarmonyOS에서 디바이스 갤러리에 이미지와 비디오를 저장할 수 있는 Flutter 플러그인입니다. 주요 플랫폼 간 호환성을 보장하기 위해 Flutter 앱에 미디어 저장 기능을 통합할 수 있는 간단한 API를 제공합니다. 플러그인은 플랫폼별 권한과 파일 작업을 처리하여 개발자가 저수준 세부 사항을 관리하지 않고도 갤러리 저장 기능을 쉽게 구현할 수 있도록 합니다. 이미지 및 비디오 파일을 모두 지원하며, 신뢰할 수 있는 성능과 최소한의 설정으로 작동합니다. 미디어를 생성하거나 캡처하여 사용자 고유의 갤러리에 저장해야 하는 앱에 적합합니다.
사용 사례
카메라에서 캡처한 사진 저장
앱 내에서 생성된 이미지를 저장하는 것
사용자 갤러리로 비디오 내보내기
갤러리 공유 기능 활성화
사진 편집 도구와의 통합
주요 기능
크로스 플랫폼 지원 (Android, iOS, HarmonyOS)
이미지 및 비디오를 위한 간단한 저장 API
자동 권한 처리
이미지 및 비디오 형식을 모두 지원합니다
신뢰할 수 있는 파일 경로 해결
적합한 대상
미디어 캡처 앱
사진 편집 애플리케이션
소셜 공유 기능
콘텐츠 생성 도구
갤러리 통합이 필요한 앱
고려 사항
대상 플랫폼에서 적절한 권한이 필요합니다
iOS용 추가 구성이 필요할 수 있습니다
파일 경로는 유효하고 접근 가능해야 합니다
시스템 갤러리에만 저장 가능
하모니OS 지원은 제한된 테스트를 거칠 수 있습니다
원본 README
연결된 소스 저장소의 README 원문 스냅샷입니다. 최신 내용은 저장소에서 확인하세요.
Saver Gallery
Overview
The saver_gallery plugin enables you to save images and other media files (such as videos) directly to the Android and iOS gallery. While the image_picker plugin allows you to select images from the gallery, it does not support saving them back to the gallery. saver_gallery provides this essential functionality, making it easy to save media files in Flutter applications.
HarmonyOS support is also included starting from version 4.0.0.
Features
Save images of various formats (png, jpg, gif, etc.) to the gallery.
Save video and other media files to the gallery.
Save media into named albums on Android and iOS.
Batch save multiple images or files at once.
Handle conditional saving with the skipIfExists parameter.
Compatible with Android, iOS, and HarmonyOS platforms.
Installation
To include saver_gallery in your project, add it as a dependency in your pubspec.yaml file:
dependencies:saver_gallery:^5.0.0
Requirements
Flutter >=3.44.0
Dart >=3.12.0 <4.0.0
JDK 17 for Android builds
Android compileSdkVersion 36
Android minSdkVersion 19
iOS 13.0+
Version 5.0.0 and later require Flutter 3.41.0+. If your project still uses an older Flutter SDK, use saver_gallery4.1.2.
The iOS implementation supports Swift Package Manager on Flutter 3.44.0+ while keeping CocoaPods support.
iOS Configuration
If you are targeting iOS, ensure that your project is configured to use Swift. Add the following keys to your Info.plist file located at <project_root>/ios/Runner/Info.plist:
<key>NSPhotoLibraryAddUsageDescription</key><string>We need access to your photo library to save images.</string><key>NSPhotoLibraryUsageDescription</key><string>We need access to your photo library to save images.</string>
Explanation:
These keys provide descriptions for permission prompts shown to users when your app requests access to their photo library.
Android Configuration
For Android, you need to handle storage permissions to save files to the gallery. Use the permission_handler package to manage permissions.
Required Permissions
Add the following permissions to your AndroidManifest.xml file:
<uses-permissionandroid:name="android.permission.WRITE_EXTERNAL_STORAGE"android:maxSdkVersion="28"tools:ignore="ScopedStorage" /><!-- Required if skipIfExists is set to true on Android 12 and below --><uses-permissionandroid:name="android.permission.READ_EXTERNAL_STORAGE"android:maxSdkVersion="32" /><!-- Required if skipIfExists is set to true on Android 13+ for the media types you save --><uses-permissionandroid:name="android.permission.READ_MEDIA_IMAGES" /><uses-permissionandroid:name="android.permission.READ_MEDIA_VIDEO" /><uses-permissionandroid:name="android.permission.READ_MEDIA_AUDIO" />
If your manifest does not already declare the tools namespace, add xmlns:tools="http://schemas.android.com/tools" to the root <manifest> element.
Handling Permissions
To handle permissions properly, use the permission_handler package. Depending on the Android SDK version, permissions requirements vary. Here's how you can implement permission handling:
import'dart:io';
import'package:permission_handler/permission_handler.dart';
import'package:device_info_plus/device_info_plus.dart';
enumMediaType {
image,
video,
audio,
}
Future<bool> checkAndRequestPermissions({
requiredbool skipIfExists,
MediaType mediaType = MediaType.image,
}) async {
if (!Platform.isAndroid && !Platform.isIOS) {
returnfalse; // Only Android and iOS platforms are supported
}
if (Platform.isAndroid) {
final deviceInfo = awaitDeviceInfoPlugin().androidInfo;
final sdkInt = deviceInfo.version.sdkInt;
if (sdkInt < 29) {
returnawaitPermission.storage.request().isGranted;
}
if (!skipIfExists) {
returntrue;
}
if (sdkInt < 33) {
returnawaitPermission.storage.request().isGranted;
}
switch (mediaType) {
caseMediaType.image:
returnawaitPermission.photos.request().isGranted;
caseMediaType.video:
returnawaitPermission.videos.request().isGranted;
caseMediaType.audio:
returnawaitPermission.audio.request().isGranted;
}
} elseif (Platform.isIOS) {
// iOS permission for saving images to the galleryreturn skipIfExists
? awaitPermission.photos.request().isGranted
: awaitPermission.photosAddOnly.request().isGranted;
}
returnfalse; // Unsupported platforms
}
Explanation:
For Android:
SDK 29+: Does not require read permission for writing files.
SDK 33+: Requires Permission.photos, Permission.videos, or Permission.audio to check if a file exists.
SDK < 29: Requires Permission.storage for read and write operations.
For iOS:
Uses Permission.photos to check if a file exists.
Uses Permission.photosAddOnly for saving files without needing full photo library access.
Usage
Saving an Image
To save an image (e.g., png, jpg, or gif) to the gallery from the internet:
quality: Set the image quality (0-100) for compressing images. This only applies to jpg format.
fileName: The name of the file being saved. This should be a file name, not an album path.
albumPath: Album hierarchy path for Android and iOS, e.g. "appName/images" saves images to "Pictures/appName/images" on Android and appName > images in iOS Photos.
skipIfExists: If true, skips saving the image if it already exists in the specified path.
Saving to an Album
Use albumPath when you want Android and iOS to save into a named album:
final result = awaitSaverGallery.saveImage(
imageBytes,
fileName: 'album_image.jpg',
albumPath: 'MyAlbum',
skipIfExists: false,
);
On iOS, albumPath creates or reuses a PhotoKit user album. On Android, it maps to the default media directory for the file type, such as "Pictures/MyAlbum" for images or "Movies/MyAlbum" for videos.
Use nested albumPath when you need folder-like organization:
final result = awaitSaverGallery.saveImage(
imageBytes,
fileName: 'album_image.jpg',
albumPath: 'appName/images',
skipIfExists: false,
);
This saves to Pictures/appName/images on Android and appName > images in iOS Photos. The last segment is the iOS album; parent segments are iOS folders.
Android public-directory prefixes are also accepted for migration compatibility, for example albumPath: 'Pictures/appName/images'. albumPath must be a relative album hierarchy path, not an absolute filesystem path.
Saved URI Result
SaveResult includes the saved media location returned by the platform:
final result = awaitSaverGallery.saveImage(
imageBytes,
fileName: 'album_image.jpg',
albumPath: 'appName/images',
skipIfExists: false,
);
print(result.savedUri);
print(result.savedUris);
savedUri: The saved URI for single-file saves.
savedUris: Saved URIs for batch saves. Single-file saves also mirror savedUri into this list.
Android 10+ returns content://....
Android 9 and below returns file://....
iOS returns ph://..., based on the Photos asset local identifier.
OHOS returns the media library URI from the asset creation dialog.
savedUri is a platform location identifier, not a guaranteed filesystem path.
Saving a File (e.g., Video)
To save other types of files (e.g., videos) to the gallery:
skipIfExists: If true, skips saving the file if it already exists.
fileName: Desired name of the file in the gallery.
albumPath: Album hierarchy path for Android and iOS. For videos, "appName/videos" saves to "Movies/appName/videos" on Android and appName > videos in iOS Photos.
Batch Saving
Save multiple images or files at once:
import'package:saver_gallery/saver_gallery.dart';
// Batch save images_saveBatchImages() async {
final images = [
SaveImageData(bytes: imageBytes1, fileName: 'image1.jpg', albumPath: 'MyAlbum'),
SaveImageData(bytes: imageBytes2, fileName: 'image2.png', albumPath: 'MyAlbum'),
];
final result = awaitSaverGallery.saveImages(images, skipIfExists: false);
print(result);
}
// Batch save files_saveBatchFiles() async {
final files = [
SaveFileData(filePath: '/path/to/file1.mp4', fileName: 'video1.mp4', albumPath: 'MyAlbum'),
SaveFileData(filePath: '/path/to/file2.mp4', fileName: 'video2.mp4', albumPath: 'MyAlbum'),
];
final result = awaitSaverGallery.saveFiles(files, skipIfExists: false);
print(result);
}
Additional Information
For more advanced usage and detailed API documentation, refer to the official documentation.
License
This project is licensed under the MIT License. For more details, see the LICENSE file.