家計簿と AI チャット、領収書のカメラ読み取りを 1 つにまとめたモバイルアプリ。Web 版 monemory の Flutter 派生で、iOS と Android の両方で動く。
| ホーム | 統計 | チャット | 領収書スキャン | 設定 | ランチャー | iOS |
|---|---|---|---|---|---|---|
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
- Flutter 3.44.4 / Dart 3.12.2
- Riverpod 2 系(状態管理)
- go_router(画面遷移)
- Supabase Flutter(認証)
- Gemini 2.5 Flash(チャット応答と領収書の画像解析)
- sqflite(ローカルの SQLite 保存)
- flutter_local_notifications(毎日のリマインダー、FCM は使わない)
- local_auth(Face ID / Touch ID / 指紋によるアプリロック)
- home_widget(Android のホーム画面ウィジェット)
対象端末は Android(Pixel 8 エミュレータで確認)、iOS(iPhone 17 シミュレータで確認)、Web ビルド。
Web とモバイルで同じ機能を無理に揃えず、それぞれの得意分野に絞る。
- Web は音声入力とダッシュボードが強く、腰を据えて使う想定
- モバイルはカメラ、通知、ホーム画面ウィジェット、生体認証で「片手・数秒」の使い勝手に寄せる
Gemini の画像・音声入力 (マルチモーダル) は Web で音声、モバイルで画像に振り分けて、どちらも 1 回の API 呼び出しで済ませる形にした。
記録の管理 今月の合計とカテゴリ別の内訳をホームで確認できる。金額とカテゴリ、メモを入力して 1 タップで追加、スワイプで削除。SQLite に保存されるのでアプリを閉じても残る。
AI チャット Gemini に記録一覧をそのまま渡し、家計についての質問に答えてもらう。「今月の合計は?」と聞けば「¥11,350 です」と返してくる。RAG(pgvector)は使っておらず、記録数が少ないうちは全件を JSON で渡してしまう方が早くて正確。
領収書スキャン カメラかギャラリーからレシート画像を選ぶと、Gemini が画像ごと読み取って金額・カテゴリ・店名を決まった形の JSON で返す。実際にセブンイレブンのレシートで試したところ、合計 ¥658・食費・店名まで正しく取れた。
毎日 22 時のリマインダー
「今日の記録忘れてない?」を端末内でスケジュールする。Firebase Cloud Messaging を使わずに flutter_local_notifications で完結、サーバー不要。設定でトグルすると Android は AlarmManager、iOS は UNNotification に登録される。
生体認証でロック 設定でオンにすると、起動時とバックグラウンド復帰時に Face ID / Touch ID / 指紋 を要求する。認証状態は端末内の SharedPreferences に保存。
Android ホーム画面ウィジェット
今月の支出額と記録件数を表示するウィジェットを Android のホームに置ける。Kotlin で書いた AppWidgetProvider が Flutter 側の SharedPreferences(home_widget プラグイン経由)を読んで RemoteViews を更新する。長押しでウィジェット一覧を開くと「monemory」で検索できる。
Supabase Auth メールアドレスを入れて 6 桁のコードでログインする方式。実際に送信できるところまで確認済み(送信数の上限が 1 時間 4 通なので、それ以上試すには独自の SMTP 設定が要る)。
GEMINI_API_KEY を渡さずに起動しても落ちない。OCR は「MOCK」バッジ付きのダミー応答、チャットは「(MOCK) 記録は 5 件、合計 ¥11,350 です」を返す。Supabase の URL / anon key も同様で、未設定なら Login 画面に注意書きが出るだけ。
これによって、手元のリポジトリだけで起動して UI と機能フローを一通り試せる。
- レシート画像から Gemini が
¥658 / 食費 / セブンイレブンを抽出(Android Pixel 8 エミュレータ) - チャットに記録一覧を渡して「What is my total spending this month?」→「今月の合計支出は¥11,350です。」(Android)
- 22:00 のローカル通知が
adb shell dumpsys alarmで RTC_WAKEUP に登録済み - Supabase Auth の 6 桁コード送信でレート上限 429 に到達(実接続している証拠)
- Android のリリースビルドで INTERNET permission が debug と profile の manifest にしか無い罠を発見・修正
- iPhone 17 シミュレータ(iOS 26.5)で debug build 起動、Supabase init のログを確認
環境変数は env.json にまとめて --dart-define-from-file で渡す。env.json はリポジトリに入れない。
cp env.json.example env.json
# SUPABASE_URL / SUPABASE_ANON_KEY / GEMINI_API_KEY を書き込むMakefile 経由で日常的な操作を行う。
make help # ターゲット一覧
make install # 依存取得
make test # ユニットとウィジェットの全テスト
make analyze # 静的解析
make up # Android エミュレータ起動
make run-android # Android にデプロイ
make run-ios # iOS シミュレータにデプロイ
make run-web # Chrome で起動
make build-apk # Android リリースビルド
make build-ios # iOS リリースビルド(署名なし)
make build-web # Web リリースビルド
make icons # アイコンを全プラットフォームへ展開
make splash # スプラッシュを全プラットフォームへ展開
make doctor # flutter doctor
make clean # ビルド成果物の削除make test で以下がまとめて走る。
dao_test– 抽象化したEntryDaoの insert / findAll / deleteById / count を InMemory 実装で境界確認format_test– 円表示と相対日時の閾値chat_service_test– Gemini キー未設定時の mock 応答entries_provider_test– 今月合計とカテゴリ別集計widget_test– ホーム画面と下部ナビゲーションの存在
.github/workflows/ci.yml で 4 ジョブを並列に走らせる。
- test(ubuntu): analyze と全テスト
- build-web(ubuntu): Web リリースビルド
- build-android(ubuntu): APK リリースビルド
- build-ios(macos-15): iOS リリースビルド(署名なし)
Flutter のバージョンは FLUTTER_VERSION env で一元管理。
flutter-monemory/
├── lib/
│ ├── main.dart
│ ├── app.dart
│ ├── core/
│ │ ├── env.dart # --dart-define で注入する env
│ │ ├── router.dart # go_router (ShellRoute + 6 画面)
│ │ └── theme.dart
│ ├── shared/
│ │ ├── models/entry.dart
│ │ ├── providers/entries_provider.dart
│ │ ├── db/entry_dao.dart # sqflite + InMemory fallback
│ │ └── format.dart
│ └── features/
│ ├── shell/ # BottomNavigation shell
│ ├── home/
│ ├── entries/ # 記録追加フォーム
│ ├── chat/ # Gemini に記録一覧を渡すチャット
│ ├── stats/
│ ├── settings/
│ ├── auth/ # Supabase 6 桁 OTP
│ ├── receipt/ # Gemini OCR
│ ├── notifications/ # 毎日 22:00 リマインダー
│ ├── biometric/ # local_auth によるアプリロック
│ └── widget/ # Android ホームウィジェット同期
├── test/
├── assets/icon/icon.png
├── android/ # AndroidManifest, MonemoryWidgetProvider.kt, widget layout
├── ios/ # Info.plist (permission / URL scheme)
├── web/
├── docs/screenshots/
├── Makefile
├── flutter_launcher_icons.yaml
├── flutter_native_splash.yaml
└── .github/workflows/ci.yml






