Flutterを使ったらカメラ機能が爆速で実装できた話

この記事は [Life is Tech ! #1 Advent Calendar 2020](https://qiita.com/advent-calendar/2020/life-is-tech) の19日目の記事です。(少し遅刻しました...)

## はじめに
昨今、写真を扱うアプリは増えているように思います。
例えばSNSみたいなアプリを作っているとして

- カメラを起動してそのまま投稿！
- 端末内にある画像を選択して投稿！

なんて機能がほしい！という声、多いと思います。
ただエンジニアのみなさん、**カメラ機能の実装大変ですよね。**

例えば、Androidのカメラ機能に注目してみると(参考: [Take photos | Android Developers](https://developer.android.com/training/camera/photobasics))

- SDKバージョンによって条件分岐が必要(昔は本当にカオスだった)
- 純粋に書くコード量が多い
- フルサイズの写真を取得するのに一工夫必要

などがあり、実装して！と言われると少し気合いを入れないといけません。
そして、iOS, Androidでカメラの機構が全然違うため、それぞれの知見を持った上で両プラットフォーム対応しなければなりません。正直結構大変です。

その上で以前クロスプラットフォームに対応しているFlutterを採用して開発したところ、カメラ・アルバム選択機能を簡単に実装することができました。今回はそれについて紹介したいと思います。

## 目次
- Flutterとは？
- 使用するライブラリ
- サンプルアプリの作成
  - 初期セットアップ
  - レイアウトの作成
  - カメラから画像取得し表示
- 終わりに


## Flutterとは？
Google社が開発しているオープンソースのアプリケーションフレームワークで、クロスプラットフォーム対応の技術として近年普及してきています。
クロスプラットフォームとは簡単にいうと、1つのソースコードからiOS, Android, Web, Desktopアプリ(Windows, Mac, Linux)を一気に作れるという夢のような技術になります。
開発言語はDartです。
参考: [Flutter - Beautiful native apps in record time ](https://flutter.dev/)

## 使用するライブラリ
`image_picker`というFlutter公式ライブラリがあります。このpackageがプラットフォームやAPIレベルの差を吸収してくれています。
そしてこれを使うと、なんと**たった3行でカメラ機能・アルバム選択機能が作れます！**

```dart
final picker = ImagePicker();
final pickedFile = await picker.getImage(source: ImageSource.camera);
final File file = File(pickedFile.path);
```
参考: [image_picker | Flutter Package](https://pub.dev/packages/image_picker)

このfileという変数にファイルの中身が保持され、`Image.file(file)` という形で渡してあげるだけで画像を表示できます。
また、アルバムから取得するには、2行目の`picker.getImage()`の`source`を`ImageSource.gallery`にするだけです。

## サンプルアプリの作成

では実際に簡単なサンプルを作ってみます。
全ソースコード(GitHub): https://github.com/konatsup/flutter_camera_sample

1. 初期セットアップ
2. レイアウトの作成
3. カメラから画像取得し表示

## 1. 初期セットアップ
### 新規プロジェクト作成
```
$ flutter create camera_sample
```

作成したプロジェクトをエディタで開きます。(VSCode, AndroidStudioなど)


### パッケージの追加
pubspec.yamlに以下を追加します。

```pubspec.yaml
dependencies:
  flutter:
    sdk: flutter
  
  // ↓この1行を追加
  image_picker: ^0.6.7+17
```

このpackageを取得するため以下のコマンドを打ちます。

```shell
$ flutter pub get
```


### iOSでの設定
カメラやアルバムを利用するために、以下の設定を`ios/Runner/Info.plist`に追加します。

```Info.plist
...
<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>
...
```

### Androidでの設定
Android 10 (APIレベル29) 未満のものにも対応させるため、`android/app/src/main/AndroidManifest.xml`の`<application>` タグに以下を追加します。

```AndroidManifest.xml
android:requestLegacyExternalStorage="true"
```

これで初期セットアップは完了です。
## 2. レイアウトの作成
今回は画面中央に画像などのコンテンツ、画面右下にFloatingActionButtonを配置してみます。

```main.dart
import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Image Picker Sample'),
      ),
      body: Center(
        child: Text('No image selected.'),
      ),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.add_a_photo),
      ),
    );
  }
}
```
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/152658/28f4fedd-36c9-d46c-24dc-a29cf27bc429.png" width=300>


## 3. カメラから画像取得
FloatingActionButtonを押すことでカメラを起動するようにします。`_MyHomePageState`を以下のように修正します。

```main.dart
...
class _MyHomePageState extends State<MyHomePage> {
  File _image;
  final picker = ImagePicker();

  Future getImage() async {
    final pickedFile = await picker.getImage(source: ImageSource.camera);

    setState(() {
      if (pickedFile != null) {
        _image = File(pickedFile.path);
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Image Picker Sample'),
      ),
      body: Center(
        child: _image == null
            ? Text('No image selected.')
            : Image.file(_image),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: getImage,
        child: Icon(Icons.add_a_photo),
      ),
    );
  }
}
```
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/152658/c29d9518-8ce0-6b2a-d40e-e9a706618664.png" width=300>

撮影した画像を表示できました。(また、サムネイルではなくフルサイズの画像を取得できています。)
これで完成です。

## 終わりに
Flutterを使うことでiOS, Android両対応のカメラ機能を簡単に実装することができました。
今まで使っていた技術では難しかったことも、他の技術を使うことで容易に解決することも結構あります。
今の技術スタックにとらわれずに視野を広げていくことで、より良い技術選定ができ、高品質なものを素早く作り上げていけるようになると思っています。

FlutterはGoogleが開発しているのもあり、これからもっと発展していくと思います。興味のある方はぜひやってみてください！