about
Flutterで画像を保存するときアルバムに保存したい
モバイル向け
- Androidではフォルダの中に保存
- iOSではアルバムに保存
環境
- macOS Ventura 13.5 (22G74)
- Dart SDK version: 3.1.3
- Flutter 3.13.6
- Android Studio Giraffe 2022.3.1
- Xcode 14.3.1
How to
ライブラリはphoto_managerを利用します。
wechat_assets_pickerを利用している場合、wechat_assets_pickerがphoto_managerに依存しているので追加インポートは不要です。
要点は以下
-
PhotoManager.editor.saveImageWithPath()
を利用して画像を保存 - iOSでは
PhotoManager.editor.copyAssetToPath()
を利用してアルバムに紐付け
プラットフォームごとに処理の分岐が必要なので以下で説明します。
Android
/storage/emulated/0/Pictures/
以下にフォルダを作成して保存します。
LINE等、多くのアプリと同様の実装です。
Pictursのパスを取得
/storage/emulated/0/Pictures
大抵上記なのですが、内部パーティションの切り方によっては1のときもあるかも知れないので
external_pathというライブラリを使用してPicturesのパスを取得しました。
final picturesPath = await ExternalPath.getExternalStoragePublicDirectory(
ExternalPath.DIRECTORY_PICTURES,
);
フォルダの作成
Directory.create()
でフォルダを作成します。
final albumPath = '$picturesPath/$albumName';
dir = await Directory(albumPath).create(recursive: true);
recursive: true
は無ければ作成というオプションです。
作成後は戻り値でDirectoryを取得しておきます。
画像を用意する
今回はWeb上からサンプルJPEGをダウンロードしました。
PhotoManagerのsaveImageWithPath()にFileを渡す必要があるのでFileにします。
final path = '${dir.path}/$fileName';
final file = File(path)..writeAsBytesSync(response.bodyBytes);
画像の保存
画像を保存します。任意のフォルダがパスになっているので保存するだけでOKです。
先程のFile
をPhotoManager.editor.saveImageWithPath()
に渡します。
await PhotoManager.editor.saveImageWithPath(
file.path,
title: fileName,
relativePath: albumName,
);
Androidでは以上で完了です。
ファイルアプリでディレクトリ構造を確認できます。
GoogleフォトではMediaStoreの同期がとれていないのか表示されない場合がありました。
iOS
一旦TemporaryDirectoryに保存してからアルバムに紐づけます。
TemporaryDirectoryのパスを取得
/data/Containers/Data/Application/{ID}/Library/Caches
を取得します。
ここではpath_providerを使いました。
dir = await getTemporaryDirectory();
iOSでは画像を先に用意する必要があるので、アルバムは後程処理します。
画像を用意する
Androidと共通です。
画像の保存
Androidと共通です。
アセットの一覧を取得
final paths = await PhotoManager.getAssetPathList();
アルバム名でPathEntityを取得
var assetPathEntity = paths.firstWhereOrNull((e) => e.name == albumName);
firstWhereOrNull
はcollectionのオペレータです。
見つからないときに安全にnullを返してくれます。
アルバムを作成
アルバム≒Assetを作成します。
「無ければ作成」というメソッドではない為、上記でassetPathEntityがnullの場合にのみ作成します。
assetPathEntity ??= await PhotoManager.editor.darwin.createAlbum(albumName);
アルバムに紐付け
await PhotoManager.editor.copyAssetToPath(
asset: assetEntity!,
pathEntity: assetPathEntity!,
);
iOSでは以上で完了です。
写真.appでアルバムに保存されていることが確認できます。
その他
以下でパーミッションの設定画面を開くことができます。
メディアアクセスが無い場合にダイアログなどで誘導してあげると親切です。
PhotoManager.openSetting();
トラブルシューティング
-
iOSでパーミッションが設定されていない
- ios/Runner/Info.plistに以下を追加します
- NSPhotoLibraryUsageDescription (iOS10以降)
- NSPhotoLibraryAddUsageDescription (iOS11以降)
- ios/Runner/Info.plistに以下を追加します
-
photo_manager / wechat_assets_pickerをインポートしたらAndroidアプリが起動しない
- app/build.gradleでminSdkVersionを21以上にする必要があります
-
Androidでパーミッションが設定されていない
- photo_managerのexampleを参考にAndroidManifestへパーミッションを追加します
- 特に、Android 10 ではプライバシー ポリシーの問題により、位置情報と EXIF メタデータを含む元のデータを取得するには、位置情報のアクセス許可を付与する必要があります。
- ACCESS_MEDIA_LOCATIONの追加も忘れないようにして下さい
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="29"/>
<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" />
<uses-permission android:name="android.permission.ACCESS_MEDIA_LOCATION" />
- AndroidでrelativePathを指定すると保存できない (またはパスを指定できない)
- Androidでは
Pictures
以下の相対パスで指定する必要があります。そうしないとpicturesPath
が無視され仮で保存したパスに保存されます (実プロダクトへの組み込みで気付きました) - 間接的に以下のissueで分かりましたが難しいですね
https://github.com/fluttercandies/flutter_photo_manager/issues/865 -
PhotoManager.editor.saveImageWithPath()
の戻り地がnullになっていると失敗なので確認して下さい
- Androidでは
Sample
全体を通して見ないと分かりづらいと思うので、GitHubにサンプルアプリを作成しました
https://github.com/kanari3/save_image_to_album_flutter
refs
photo_manager
https://pub.dev/packages/photo_manager
wechat_assets_picker
https://pub.dev/packages/wechat_assets_picker
path_provider
https://pub.dev/packages/path_provider
external_path
https://pub.dev/packages/external_path
collection
https://pub.dev/packages/collection
おわり
photo_managerの使い方、なかなか難しいですね。
iOSでは同じ名前のアルバムを複数作成することができるので、名前でフィルターするのは厳密には良くないです。
AssetPathEntityのIDで一致させれば良いのですが、その実現にはIDをどこかに保存しておく必要があります。
今回は使い方が分かりやすいよう1ファイルに流れで記載しましたが、プロダクトではAndroid/iOS共通で呼び出せるラッパーを作成するのが良いと思います。