LoginSignup
6
4

More than 1 year has passed since last update.

Flutter で Image Picker(0.8.2 以降)を使って撮った写真をファイル保存する

Last updated at Posted at 2021-10-13

以前書いたこちらの記事、

ですが、その後

を経て、

  • 地図上にピンを立て、ピンについての情報を登録する機能
    • ピンおよびピンについての情報は SQLite で DB 保存し、起動時に再読み込みする
  • ピンに関連する写真を撮影してファイルとギャラリーに保存する機能

を追加しました。

写真の撮影には image_picker を使いました。

【参考】

ただし、↑の頃と image_picker の仕様が若干変更されているので、こちらにメモとして残しておきます。

準備

主に以前の記事からの差分です。ただし写真撮影に関連のない部分は省略しています。

※記事執筆時点で Flutter SDK のバージョンは 2.5.2 です。

  • pubspec.yamldependencies:に追加)
pubspec.yaml(部分)
  image_picker: ^0.8.4+2
  cross_file: ^0.3.1+5
  image_gallery_saver: ^1.7.0
  path_provider: ^2.0.5

2021/12/09 追記:
Flutter 2.8(Dart 2.15)の環境ではcross_fileの明示的な指定が不要になりました(image_picker内でインポートされているものを利用可)。

pubspec.yaml(部分)
  image_picker: ^0.8.4+4
  image_gallery_saver: ^1.7.1
  path_provider: ^2.0.7

  • Info.plist<dict></dict>内に追加・iOS のみ)
Info.plist(image_picker関連)
    <key>NSPhotoLibraryUsageDescription</key>
    <string>This app requires to access your photo library</string>
    <key>NSCameraUsageDescription</key>
    <string>This app requires to add file to your camera</string>
    <key>NSMicrophoneUsageDescription</key>
    <string>This app requires to add file to your photo library your microphone</string>
Info.plist(path_providerファイル保存先関連)
    <key>UISupportsDocumentBrowser</key>
    <true/>
    <key>LSSupportsOpeningDocumentsInPlace</key>
    <true/>

コード例(関連部分)

関連部分のみ一部改変して抽出しています。

※コード全体は前掲の GitHub リポジトリをご確認ください。

パッケージインポート

パッケージインポート
import 'dart:async';
import 'dart:io';
import 'dart:typed_data';
import 'package:cross_file/cross_file.dart';
import 'package:image_gallery_saver/image_gallery_saver.dart';
import 'package:image_picker/image_picker.dart';
import 'package:path_provider/path_provider.dart';

2021/12/09 追記:
前述のとおりですが、Flutter 2.8(Dart 2.15)の環境ではcross_fileのインポートが不要になりました。


写真を撮影する

image_picker 0.8.2 より、一次ファイルとして取得する対象がFileから cross_fileXFileに変わりました。

なお、flutter_image_compress を使わなくても画像サイズや品質の編集が可能です。

※画像サイズの指定方法が flutter_image_compress とは異なる点に注意。

写真撮影
  final ImagePicker _picker = ImagePicker();
(中略)
  Future<XFile?> _takePhoto() {
    return _picker.pickImage(
        source: ImageSource.camera,
        maxWidth: 1600,
        maxHeight: 1600,
        imageQuality: 85);
  }

撮影した写真をファイルに保存する

事前に path_providergetApplicationDocumentsDirectoryで保存先のパスを取得・セットしておきます。

保存先パスをセット
  String _imagePath = '';
(中略)
  _setImagePath() async {
    _imagePath = (await getApplicationDocumentsDirectory()).path;
  }

XFile.readAsBytesで内容をUint8Listに読み出して、保存先のパスにFile.writeAsBytesSyncで書き出します。

写真を保存する
  Future<String> _savePhoto(XFile photo) async {
    final Uint8List buffer = await photo.readAsBytes();
    final String savePath = '$_imagePath/${photo.name}';
    final File saveFile = File(savePath);
    saveFile.writeAsBytesSync(buffer, flush: true, mode: FileMode.write);
    // 画像ギャラリーにも保存(オプション)
    await ImageGallerySaver.saveImage(buffer, name: photo.name);

    return saveFile.path;
  }

写真撮影→保存の呼び出し元はこのようになります。

呼び出し元
  final XFile? photo = await _takePhoto();
  if (photo != null) {
    await _savePhoto(photo);
  }

iOS の場合、通常はアプリケーションで書き出したファイルをユーザが直接確認することはできませんが、先述のInfo.plistの指定によって「ファイル」から確認できるようになります。

※SQLite の DB 保存ファイルも見えるので、うっかり削除しないよう注意。

【参考】

保存したファイルを呼び出す

 path_providergetApplicationDocumentsDirectoryで取得した保存先パスですが、これは仮想的なパスのようで、アプリケーションを起動するごとに変わる可能性があります。

ファイルの内容が消えるわけではありませんが、このパスは DB に直接保存して使うのではなく、アプリケーション機動時に取得し直して指定します。

もし DB にフルパスで保存している場合は、ファイルを読み取る際にパス部分を切り落として、ファイル名のみを新しい保存先パスに繋げて使用します。

画像ファイル取得
// 画像の登録情報
class Picture {
  int id;
  String comment;
  DateTime dateTime;
  String filePath;

  Picture(this.id, this.comment, this.dateTime, this.filePath);
}
(中略)
  // 画像ファイル取得
  File? _localFile(Picture picture) {
    // filePath がパス付きの場合はファイル名のみを抽出
    final int pathIndexOf = picture.filePath.lastIndexOf('/');
    final String fileName = (pathIndexOf == -1
        ? picture.filePath
        : picture.filePath.substring(pathIndexOf + 1));
    final String filePath = '$_imagePath/$fileName';
    try {
      if (File(filePath).existsSync()) {
        return File(filePath);
      }
      return null;
    } catch (e) {
      return null;
    }
  }

これをFile? file = _localFile(picture);Image.file(file)などで表示します。

※余談ですが、ここだけPhotoではなくPictureになっているのは写真以外も共通して扱う想定だからです。

6
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
4