17
22

More than 3 years have passed since last update.

FlutterでiOS、Android両方で動くカメラアプリを作る

Posted at

Flutterを使ってマルチプラットフォームなカメラアプリを作っていく

「iOSでもAndroidでも動くアプリで、」
「スマホ内の画像にアクセスしてローカルのDBに保存可能」
「アプリ内でもカメラを起動できる」

っていう要件をFlutterで満たせるかの検証をバイト先に任されたからなー

Flutter

iOS、Androidといった異なるプラットフォームのアプリを一つのコードで書くことができるGoogle製のオープンソースのクロスプラットフォームのフレームワーク

公式ドキュメントも充実・サードパーティー製のプラグインも日々増えているので、旬な技術だと思っている

公式Youtubeにはお洒落な感じデザインのやらWidgetやらの説明動画がたくさんあるので楽しい

環境構築は以下のサイトを参考にした。ありがとうございました

ホットリロードが神すぎる

早速作っていきたいのだけど、Flutterは言語はdartでWidget単位でコードを完成させてアプリを作っていく

公式ドキュメントでもFlutterではWidgetが全てだよって言ってる

In Flutter, “everything is a widget”! - Flutter documentation

特に重要なWidgetが Stateless WidgetStatefull Widget の2つで順番におさらいしていく

そのあと、画像ファイル・カメラへのアクセスを可能にするプラグインであるimage_picker と、データベースを扱うためのプラグインsqfliteの紹介に入って、最後にコードを説明して終わり

Stateless Widget

名前の通り状態を持たないWidget。

以下は公式YouTubeで取り上げられているコード例

class ItemCount extends StatelessWidget {
 final String name;
 final int count;

 ItemCount({this.name, this.count});

 @overide
 Widget build(BuildContext context) {
  return Text('$name: $count')
 }
}

String型のnameとint型のcountを与えると、それらをTextWidgetに渡して表示させるようなWidgetである。

Statefull Widget

状態をもつWidgetで、Stateが更新されたときに内容が更新される。

先ほどのコードをStatefulにすると以下のような感じになる。

class ItemCount extends StatefulWidget {
 final String name;

 ItemCounter({this.name});

 @override
 _ItemCounterState createState() => _ItemCounterState();
}

class _ItemCounterState extends State<ItemCounter> {
 int count = 0;

 @overide
 Widget build(BuildContext context) {
   return GestureDetector(
     onTap: () {
       setState(() {
         count++;
       });
     ),
     child: Text('${widget.name}: $count'),
   );
 }
}

setStateはその中に書かれた変数が変化した時にクラスに変更を伝える役割を持っている

これでStatelessだった画面が、タップするとcountが上がっていくプログラムになった

image_picker

image_pickerははアプリ内でカメラを立ち上げたり、スマホ内のメディア(画像)にアクセスさせたりするのに使うプラグイン

A Flutter plugin for iOS and Android for picking images from the image library, and taking new pictures with the camera. - image_picker | pub.dev

フラッター開発チームが作った純正なのでいろいろ安心

Flutterでのプラグインの導入は pubspec.yaml にプラグインの情報を追加して $ flutter pub get してあげるだけ

このプラグインではフォトライブラリへのアクセスを許可しないといけないので、iOSでは以下の変数に関する設定をios/Runner/info.plistに事前に書き込んでおく必要がある

変数名 役割
NSPhotoLibraryUsageDescription フォトライブラリへのアクセス
NSCameraUsageDescription カメラへのアクセス
NSMicrophoneUsageDescription マイクへのアクセス

動画を取り込まないなら一番下の項目は無視できる

細かい設定の仕方は公式ドキュメントをご参照ください

公式ドキュメントをサンプルのコードに手を加え、ペッとmain.dartに貼り付けたら動くものを作った

動作環境はMacBookPro上の android-x86 emulator(Pixel_3a_API_30_x86)である

例えばカメラの起動と写真の取得はたったこれだけ。

カメラの起動・写真の取得
final pickedFile = await picker.getImage(source: ImageSource.camera);

ImageSource.gallery ならフォルダへのアクセスができる

かなり使えそう。

sqflite

SQLite(データベース)を導入・利用するためのプラグイン

Flutter plugin for SQLite, a self-contained, high-reliability, embedded, SQL database engine. - sqflite | pub.dev

導入は例に漏れず pubspec.yaml にプラグインの情報を追加して $ flutter pub get してあげるだけ

公式Githubとか他の記事見ながら理解した

主な関数の説明を列挙していく

まずはデータベースへのパスを取得する必要があるらしいので、import 'package:path/path.dart'しておいて、getDatabasesPath関数でパスを取得する

それを用いると、データベースを開く処理はこんな感じでできる

openDatabase関数
String _path = join(await getDatabasesPath(), "mydb.db");
Database database = await openDatabase(_path, version: 1, onCreate: 処理);

新規にデータベースを作成してアクセスする時などは、openDatabase関数実行時にonCreateを設定すれば良い

onCreate関数は、データを事前準備しておきたい時にも使える

onCreate関数
    String _path = join(await getDatabasesPath(), "mydb.db");

    Database _database = await openDatabase(_path, version: 1,
        onCreate: (Database db, int version) async {
      await db.execute(
          "CREATE TABLE IF NOT EXISTS mydb (id INTEGER PRIMARY KEY, text TEXT)");
      // データを事前に流し込みたい時はここにinsertを記述すれば良い
    });

データベースの内容の書き換えなどで、transaction関数を利用できる

transaction関数
    await _database.transaction((txn) async {
      await txn.rawInsert('INSERT INTO mydb(text) VALUES("$textData")');
    });

レコードの取得にはrawQuery、保存にはrawInsert、更新はrawUpdate、削除はrawDeleteを使えば良い
rawQuery関数の結果はMap型のリストで返ってくる

rawQuery関数
    List<Map> _result = await _database.rawQuery('SELECT * FROM mydb');

SQLとかを発行しない処理であれば、execute関数で行える

本題

カメラとギャラリーから画像を取ってきて、DB(SQLite)に保存するアプリを作ってみた。

まずは完成したアプリをみて欲しい.

Androidエミュレーターでの動き

andq.gif

iPhoneエミュレーターでの動き

iosq.gif

Android、iphoneの両方で動作するアプリがであることが確認できる(ただし、iphoneエミュレーター上ではカメラは起動しない)

sqfliteに画像を保存する場合は画像情報をbase64でエンコードしたテキストとして写真の実体を保存する

自分はこちらを参考にした(Thank you!)

Google’s Flutter Tutorial – Save Image as String in SQLite Database

画像を保存しておくためのテーブルは以下のように定義している


  initDB() async {
    io.Directory documentsDirectory = await getApplicationDocumentsDirectory();
    String path = join(documentsDirectory.path, DB_NAME);
    var db = await openDatabase(path, version: 1, onCreate: _onCreate);
    return db;
  }

  _onCreate(Database db, int version) async {
    await db.execute("CREATE TABLE $TABLE ($ID INTEGER, $DATA TEXT)");
  }

ギャラリーからピックアップしてきた画像をBase64でエンコードして保存


  _pickImageFromGallery() {
    final picker = ImagePicker();
    picker.getImage(source: ImageSource.gallery).then((imgFile) async {
      if (imgFile != null) {
        String imgString = Utility.base64String(await imgFile.readAsBytes());
        Photo photo = Photo(0, imgString);
        dbHelper.save(photo);
      }
    });
  }

完成したコードはGitHubにおいておきます

17
22
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
17
22