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 Widget と Statefull 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関数でパスを取得する
それを用いると、データベースを開く処理はこんな感じでできる
String _path = join(await getDatabasesPath(), "mydb.db");
Database database = await openDatabase(_path, version: 1, onCreate: 処理);
新規にデータベースを作成してアクセスする時などは、openDatabase関数実行時に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関数を利用できる
await _database.transaction((txn) async {
await txn.rawInsert('INSERT INTO mydb(text) VALUES("$textData")');
});
レコードの取得にはrawQuery、保存にはrawInsert、更新はrawUpdate、削除はrawDeleteを使えば良い
rawQuery関数の結果はMap型のリストで返ってくる
List<Map> _result = await _database.rawQuery('SELECT * FROM mydb');
SQLとかを発行しない処理であれば、execute関数で行える
本題
カメラとギャラリーから画像を取ってきて、DB(SQLite)に保存するアプリを作ってみた。
まずは完成したアプリをみて欲しい.
Androidエミュレーターでの動き
iPhoneエミュレーターでの動き
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においておきます