🌟

【Flutter】cameraプラグインを使ってカメラロールに保存する : takePicture(path)が機能しない時の対応

2022/03/14に公開

初めてのFlutterでカメラアプリを作っている際に詰まった内容です。

技術ブログで紹介されていた内容を参考に、cameraプラグインを用いて「画像を保存 → パスを取得」 して画面に表示するサンプルを実行すると、takePicture()に引数を持たせる部分でエラーになってしまいました。

main.dart
// これがエラーになる
await _controller.takePicture(path);

多くの技術記事でこのように実装されているのが見つかりましたが、調べてみるとこちらはcameraプラグインのバージョン0.6x未満での実装方法で、0.6以降では対応していないことがわかりました。

現在は以下のように実装することで対応できます。 ※ camera: ^0.9.4+14 の場合


実装

cameraプラグインを扱ってパスを取得する・指定したパスに保存する・カメラロールに保存する3つの例

対応環境

 camera: ^0.9.4+14 
 image_gallery_saver: ^1.7.1
 Flutter 2.10.3
 動作確認OS : Android

例1. パスを取得する

main.dart
final image = await _controller.takePicture();
// image.pathでパスの取得が可能
print(image.path);

例2. 指定したパスに保存する

cacheフォルダに加えて、指定したパスにも画像を保存されます。

main.dart

final image = await _controller.takePicture();

final directory = await getExternalStorageDirectory();
String path = "";

if(directory != null)
{
    path = join(directory.path,'${DateTime.now()}.png',
    // saveTo()を使って指定したパスに画像を保存
    await image.saveTo(path);
);

例3. カメラロールに保存する

takePicture()ではカメラロールに保存されません。image_gallery_saverプラグインを使って保存します。

パッケージの追加 (pubspec.yaml)

dependencies:
  image_gallery_saver: ^1.7.1
main.dart
// image_gallery_saverのimport追加
import 'package:image_gallery_saver/image_gallery_saver.dart';
// Uint8Listを扱うために追加
import 'dart:typed_data';

// 略

final image = await _controller.takePicture();
final Uint8List buffer = await image.readAsBytes();
// カメラロールに保存する
await ImageGallerySaver.saveImage(buffer, name: image.name);

カメラロールに保存するサンプル

カメラロールに画像を保存するサンプルの全文です。

main.dart
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'dart:async';
import 'package:camera/camera.dart';
import 'package:image_gallery_saver/image_gallery_saver.dart';
import 'dart:io';

Future<void> main() async {
  // Ensure that plugin services are initialized so that `availableCameras()`
  // can be called before `runApp()`
  WidgetsFlutterBinding.ensureInitialized();

  // Obtain a list of the available cameras on the device.
  final cameras = await availableCameras();

  // Get a specific camera from the list of available cameras.
  final firstCamera = cameras.first;

  runApp(
    MaterialApp(
      theme: ThemeData.dark(),
      home: TakePictureScreen(
        // Pass the appropriate camera to the TakePictureScreen widget.
        camera: firstCamera,
      ),
    ),
  );
}

// A screen that allows users to take a picture using a given camera.
class TakePictureScreen extends StatefulWidget {
  const TakePictureScreen({
    Key? key,
    required this.camera,
  }) : super(key: key);

  final CameraDescription camera;

  
  TakePictureScreenState createState() => TakePictureScreenState();
}

class TakePictureScreenState extends State<TakePictureScreen> {
  late CameraController _controller;
  late Future<void> _initializeControllerFuture;

  
  void initState() {
    super.initState();
    // To display the current output from the Camera,
    // create a CameraController.
    _controller = CameraController(
      // Get a specific camera from the list of available cameras.
      widget.camera,
      // Define the resolution to use.
      ResolutionPreset.medium,
    );

    // Next, initialize the controller. This returns a Future.
    _initializeControllerFuture = _controller.initialize();
  }

  
  void dispose() {
    // Dispose of the controller when the widget is disposed.
    _controller.dispose();
    super.dispose();
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Take a picture')),
      body: FutureBuilder<void>(
        future: _initializeControllerFuture,
        builder: (context, snapshot) {
          if (snapshot.connectionState == ConnectionState.done) {
            return CameraPreview(_controller);
          } else {
            return const Center(child: CircularProgressIndicator());
          }
        },
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () async {
          try {
            await _initializeControllerFuture;
            final image = await _controller.takePicture();

            final Uint8List buffer = await image.readAsBytes();
            await ImageGallerySaver.saveImage(buffer, name: image.name);

            await Navigator.of(context).push(
              MaterialPageRoute(
                builder: (context) => DisplayPictureScreen(
                  imagePath: image.path,
                ),
              ),
            );
          } catch (e) {
            print(e);
          }
        },
        child: const Icon(Icons.camera_alt),
      ),
    );
  }
}

class DisplayPictureScreen extends StatelessWidget {
  final String imagePath;

  const DisplayPictureScreen({Key? key, required this.imagePath})
      : super(key: key);

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Display the Picture')),
      body: Image.file(File(imagePath)),
    );
  }
}

最後に

Flutter,Dartの公式パッケージが検索できるpub.devが充実しているので、まずはこちらのExampleから最新パッケージのコードを見てみるとよさそうですね。


Twitter : https://twitter.com/koyoarai_

Discussion