Flutter × sqflite:AIの画像分類結果をローカルでサクサク管理・表示するDB設計
はじめに
写真やスクリーンショットをAI(OpenAI API)に投げて自動分類するアプリ『SnapFolder』を開発しました。 ここで問題になるのが「分類結果をどう保存するか」です。画面を開くたびに毎回APIを叩いていたら、API料金が破産してしまいます。
今回は、一度分類した結果をローカルで効率よく保存・管理するための sqflite を使ったDB設計と、複数画像をさばくバッチ処理の工夫を紹介します。
テーブル設計
ローカルDBには folders と screenshots の2つのテーブルを作成し、リレーションを組みました。
// database_service.dart の抜粋
await db.execute('''
CREATE TABLE folders (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
order_index INTEGER NOT NULL,
created_at TEXT NOT NULL
)
''');
await db.execute('''
CREATE TABLE screenshots (
id TEXT PRIMARY KEY,
path TEXT NOT NULL,
asset_id TEXT,
folder_id TEXT,
confidence TEXT NOT NULL DEFAULT 'low',
confirmed INTEGER NOT NULL DEFAULT 0,
created_at TEXT NOT NULL,
FOREIGN KEY (folder_id) REFERENCES folders(id) ON DELETE SET NULL
)
'''); 写真の実体自体はDBに保存せず、端末のローカルパス(またはアセットID)のみを path に保存します。これによりDBの肥大化を防ぎ、Flutterの Image.file(File(path)) で超高速にレンダリングできます。
OpenAI APIのバッチ処理化
1枚ずつ写真をOpenAIに投げると、リクエストのオーバーヘッドが大きくなります。そこで、最大5枚の画像を1回のプロンプトに含めてバッチ処理する工夫をしました。
// GPT-4o-miniに複数枚を一度に分類させるプロンプトの工夫
String prompt = '''
あなたは画像整理アシスタントです。
\$imageCount枚の画像を以下のフォルダに分類してください。
フォルダ一覧:
- レシート
- メモ
- 欲しいもの
以下のJSON配列で回答してください(画像の順番通り):
[{"folder": "フォルダ名", "confidence": "high"}, ...]
''';
// 5枚ずつチャンクに分割して並列リクエスト
for (int i = 0; i < imagePaths.length; i += 5) {
final batch = imagePaths.sublist(i, i + 5);
// バッチリクエスト実行...
} GPT-4o(または4o-mini)は複数画像の入力に対応しているため、このようにJSON配列で順序通りに返答させることで、APIの呼び出し回数を劇的に減らし、ユーザーの待ち時間を短縮できました。
UI表示の工夫(日付でのグルーピング)
DBから取得した写真リストをそのまま一覧表示するのではなく、「今日」「昨日」「◯日前」と動的にグルーピングして表示することで、純正の写真アプリのようなリッチなUXを実現しています。
DB設計をシンプルに保ちつつ、AI呼び出しの最適化とUI表示ロジックを分離することで、ネイティブアプリに劣らないサクサク感を出せました。 Flutterでローカルファイル管理+AIを組み合わせるアプリを作る方は、ぜひ参考にしてみてください!