Flutterでカメラ撮影してAndroid端末に保存する

Flutterでカメラ撮影を実装し、Android端末で動作させてみた。

## 環境

Flutterは 2019/11/10 時点の最新バージョン

```console
$ flutter --version
Flutter 1.9.1+hotfix.6 • channel unknown • unknown source
Framework • revision 68587a0916 (8 weeks ago) • 2019-09-13 19:46:58 -0700
Engine • revision b863200c37
Tools • Dart 2.5.0
```

## 使用Plugin

- **camera: ^0.5.6+3**
    - 公式カメラプラグイン
    - https://pub.dev/packages/camera
- **path_provider: ^1.4.0**
    - Android / iOSで使用するパスを取得できる公式プラグイン
    - https://pub.dev/packages/path_provider
    - 写真の保存場所の指定に使用


## コード

### minSdkVersion 21に指定

公式カメラプラグインを使うためにminSdkVersionを `21` に設定する必要があるみたい。
21以下だとビルドでエラーになるので注意。

```diff:android/app/build.gradle
    defaultConfig {
-        minSdkVersion 16
+        minSdkVersion 21
```


### Dartコード

画面とカメラ撮影コードは以下

<details><summary>CLICK ME (長いので折りたたみ）</summary>
<p>

```dart
import 'dart:async';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:camera/camera.dart';
import 'package:path_provider/path_provider.dart';

List<CameraDescription> cameras;

Future<void> main() async {
  cameras = await availableCameras();
  runApp(MainApp());
}

// Main
class MainApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: CameraApp(),
    );
  }
}

// カメラ
class CameraApp extends StatefulWidget {
  @override
  _CameraAppState createState() => _CameraAppState();
}

class _CameraAppState extends State<CameraApp> {
  CameraController controller;
  String imagePath;

  final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();

  @override
  void initState() {
    super.initState();
    // カメラ初期設定
    controller = CameraController(
      cameras[0], //背面カメラのみ
      ResolutionPreset.medium,
      enableAudio: false, //音声は不要
    );
    controller.initialize().then((_) {
      if (!mounted) {
        return;
      }
      setState(() {});
    });
  }

  @override
  void dispose() {
    controller?.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    if (!controller.value.isInitialized) {
      return Container();
    }
    return MaterialApp(
      home: Scaffold(
        key: _scaffoldKey,
        body: Column(
          children: <Widget>[
            Expanded(
              child: AspectRatio(
                aspectRatio: controller.value.aspectRatio,
                child: CameraPreview(controller),
              ),
            ),
            Padding(
              padding: const EdgeInsets.all(8.0),
              child: IconButton(
                icon: Icon(Icons.camera_alt),
                onPressed: () => onTakePictureButtonPressed(),
              ),
            )
          ],
        ),
      ),
    );
  }

  /// カメラボタン押下
  void onTakePictureButtonPressed() {
    takePicture().then((String filePath) {
      setState(() {
        imagePath = filePath;
      });
      if (filePath != null) showInSnackBar('Picture saved to $filePath');
    });
  }

  /// スナックバー表示、画像保存時にファイルパスが表示される
  void showInSnackBar(String message) {
    _scaffoldKey.currentState.showSnackBar(SnackBar(content: Text(message)));
  }

  /// タイムスタンプ取得
  String timestamp() => DateTime.now().millisecondsSinceEpoch.toString();

  /// カメラ撮影処理
  Future<String> takePicture() async {
    final Directory directory = await getApplicationDocumentsDirectory();
    final String dirPath = '${directory.path}/Pictures/flutter';
    await Directory(dirPath).create(recursive: true);
    final String filePath = '$dirPath/${timestamp()}.jpg';

    try {
      controller.takePicture(filePath); // 撮影
    } on CameraException catch (e) {
      _showCameraException(e);
    }
    return filePath;
  }

  /// カメラエラー
  void _showCameraException(CameraException e) {
    logError(e.code, e.description);
  }

  /// エラーログ
  void logError(String code, String message) =>
      print('Error: $code/nError Message: $message');
}
```

</p>
</details>

## 画面

### 初回起動で権限確認
自動で権限確認もしてくれるようだ
![2019-11-10-14-17-54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52227/fa7f2f41-b6aa-1391-7ed8-b3093ae12f73.png)

### プレビュー画面
![2019-11-10-14-18-12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52227/3f985053-acee-65e8-ed47-aaec9d874259.png)

### 撮影成功画面
画面下部にスナックバーが表示し、保存パスを表示している

![2019-11-10-14-18-39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52227/2a173500-9772-5afe-9790-5c19bfc3a4e3.png)


## GitHub
今回使用したコードをupしておいた。

https://github.com/ikemura23/flutter_lab/tree/camera

## 参考リンク
ほぼここのマネしてるだけ
https://github.com/flutter/plugins/tree/master/packages/camera/example