1. Lunch Log とは
1-1. 開発の背景・課題
毎日のランチをどこで食べたか、いくら使ったかを手軽に記録したいと思ったことはありませんか。既存のグルメアプリは「お店を探す」ことに特化しており、「自分の食事履歴を管理する」用途には不向きです。そこで、写真・位置情報・金額・ハッシュタグをセットで記録し、地図・カレンダー・統計で振り返れるAndroidアプリ「Lunch Log」を開発しました。
さらに、Google Driveへの自動バックアップと友達とのQRコード共有機能を搭載し、スマホの機種変更時も安心してデータを引き継げます。
1-2. 既存アプリ・ツールとの比較
| 機能 | Lunch Log | 食べログ | Googleマップ | メモアプリ |
|---|---|---|---|---|
| 食事記録 | ✅ 専用設計 | ❌ 口コミ向き | ⚠️ 手動保存 | ⚠️ テキストのみ |
| 位置情報付き記録 | ✅ 自動取得 | ✅ | ✅ | ❌ |
| 支出管理 | ✅ 統計・グラフ | ❌ | ❌ | ⚠️ 手動計算 |
| Driveバックアップ | ✅ 自動 | ❌ | ❌ | ⚠️ 手動 |
| 友達と共有 | ✅ QRコード | ✅ SNS | ✅ リスト共有 | ❌ |
| 広告なし購入 | ✅ IAP対応 | ❌ | ❌ | 多数対応 |
2. 技術スタック
| カテゴリ | ライブラリ・技術 | バージョン |
|---|---|---|
| 言語 | Kotlin | 最新安定版 |
| UI | Jetpack Compose / Material3 | BOM 2024.x |
| DI | Hilt | 2.51 |
| DB | Room | v9 |
| 設定永続化 | DataStore Preferences | 1.1.x |
| バックグラウンド | WorkManager / AlarmManager | — |
| 地図 | Google Maps Compose + maps-android-utils | — |
| 画像読み込み | Coil (AsyncImage) | 3.x |
| 広告 | Google Mobile Ads SDK | 23.1.0 |
| 課金 | Google Play Billing Library | 7.1.1 |
| QRコード生成 | qrose | 1.0.1 |
| QRスキャン | ML Kit + CameraX | 17.3.0 / 1.4.1 |
| JSON | Gson | — |
3. アーキテクチャ・データフロー
3-1. アーキテクチャ解説
本アプリは MVVM(Model-View-ViewModel) アーキテクチャを採用しています。UIはJetpack Composeで構築し、StateFlowを通じてViewModelの状態を購読します。データアクセスはRepositoryパターンで抽象化し、ViewModel からはRepositoryのインターフェースのみに依存します。依存性注入はHiltで管理し、テスト容易性も確保しています。
3-2. APIシーケンス(Driveバックアップ)
3-3. DBスキーマ
4. 画面構成・機能一覧
| 画面 | 主な機能 |
|---|---|
| マップ(ホーム) | ランチ記録と行きたいリストをカスタムマーカーで表示。クラスタリング・訪問回数バッジ付き |
| ギャラリー | 写真グリッド(3列)。ハッシュタグフィルター・並び替え・フルスクリーンビューア(ピンチズーム対応) |
| 行きたい | Googleマップ共有URLまたは手動検索で登録。自分/友達タブ切り替え。マップ連携 |
| カレンダー | 月間カレンダー。記録のある日にドット表示。タップで記録一覧確認・追加 |
| 統計 | 月別支出グラフ・店舗TOP10・ハッシュタグ内訳・連続記録日数・今月vs先月比較 |
| 設定 | リマインダー・Driveバックアップ/復元・ハッシュタグ管理・IAP広告非表示 |
| 記録追加/編集 | 店名・写真・金額・ハッシュタグ・メモ・位置情報を入力。オフライン記録にも対応 |
| 共有/QRスキャン | DriveにJSON公開→QRコード生成。友達のQRをスキャンして記録を取り込み |
5. 使い方ガイド
5-1. 初回セットアップ
- アプリをインストールして起動します
- マップ画面のFABボタン(+)をタップして最初のランチを記録します
- 設定画面からGoogleアカウントを接続してDriveバックアップを有効にします
- リマインダーを設定して毎日の記録忘れを防ぎます(推奨:昼12時)
- 友達と共有したい場合は「友達と共有」セクションからQRコードを生成します
5-2. 主要機能の使い方
- ランチを記録する:マップ画面FAB →店名(または「店名なし」)→写真追加→位置情報自動取得→保存
- 過去のお店を再利用する:店名フィールドの時計アイコンをタップ→よく行くお店TOP5から選択→位置情報も自動入力
- 行きたいリストに追加する:Googleマップでお店を開いて「共有」→Lunch Logに転送。または行きたい画面から手動検索
- Driveバックアップを実行する:設定→Google Drive→「今すぐバックアップ」。自動バックアップも設定可能(6時間〜7日間隔)
- 友達の記録を取り込む:設定→友達と共有→「QRコードをスキャン」→友達のQRを読み取る
6. 主な実装上のポイント
6-1. Drive写真バックアップの差分同期
isSyncedフラグだけでは「フラグは立っているが実際にはDriveに写真が存在しない」ケースが発生しました。そこでバックアップ実行時にDriveの実際のファイル一覧を照合し、フラグと実態の不整合を検出して自動再アップロードする方式を採用しています。
// Drive上の写真entryIdを取得して照合
val drivePhotoEntryIds = driveRepository.listPhotoFileIds(token).keys
entries.filter { entry ->
!entry.isSynced || (entry.allPhotoUris.isNotEmpty() && entry.id !in drivePhotoEntryIds)
}.forEach { entry ->
// 差分のみアップロード
}
6-2. appDataFolder スコープによる安全なバックアップ
当初はdrive.fileスコープを使用していましたが、再インストール後に新しいOAuthグラントが発行されると旧グラントで作成したファイルがfiles.listに返ってこなくなる問題が発生しました。drive.appdataスコープに切り替えることで、OAuthグラントをまたいでも常に同一アカウントのバックアップデータにアクセスできます。
// appDataFolderへのアップロード
val url = URL("=multipart")
// spaces=appDataFolder でアクセス
val listUrl = URL("=appDataFolder&q=")
6-3. ClusterManagerの2分割によるマーカー色分け
自分と友達のマーカーを混在させると同じクラスターにグループ化されてしまいます。ownClusterManagerとfriendClusterManagerの2つを独立して管理することで、それぞれ独自のカスタムレンダラーで色分けしつつ、正しくクラスタリングできます。
// 2つのClusterManagerを独立管理
val ownClusterManager = ClusterManager(context, map)
val friendClusterManager = ClusterManager(context, map)
ownClusterManager.renderer = CustomClusterRenderer(context, map, ownClusterManager, emptyMap())
friendClusterManager.renderer = CustomClusterRenderer(context, map, friendClusterManager, friendColorMap)
6-4. リマインダーの正確なアラーム実装
WorkManagerのPeriodicWorkRequestはバッテリー最適化の影響で正確な時刻に発火しません。AlarmManager.setExactAndAllowWhileIdle()とBroadcastReceiverの組み合わせで毎日自走する自走型アラームを実装しています。Android 12以上で権限が取得できない場合は近似アラームにフォールバックします。
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && !alarmManager.canScheduleExactAlarms()) {
alarmManager.setAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, cal.timeInMillis, pendingIntent)
} else {
alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, cal.timeInMillis, pendingIntent)
}
7. 使用技術の解説
Jetpack ComposeはAndroid公式のモダンUIツールキットです。XML不要で、KotlinコードだけでUIを宣言的に記述できます。状態変化を自動的にUIに反映するため、従来のビュー操作が不要になります。
HiltはDagger2をベースにしたAndroid向けDI(依存性注入)フレームワークです。ViewModelやWorkerへの依存を自動的に解決し、テストの差し替えも容易です。
RoomはSQLiteをラップしたAndroid公式ORMです。DAOインターフェースに定義したアノテーションだけでSQL文を自動生成します。FlowやSuspend関数と組み合わせてリアクティブなDBアクセスが可能です。
Google Drive API(appDataFolder)はアプリ専用の隠しストレージです。ユーザーのDriveに保存されますが、ユーザーには見えないため誤削除の心配がありません。アカウントに紐づくため再インストール後も復元できます。
ML Kit Barcode ScanningはGoogleが提供するオンデバイスのQRコード解析SDKです。CameraXと組み合わせてリアルタイムスキャンを実現しています。サーバー通信が不要なためオフラインでも動作します。
8. 開発プロセス
| フェーズ | 期間 | 主な内容 |
|---|---|---|
| 基盤構築 | 〜2026-05-21 | MVVM/Hilt/Room基盤・マップ表示・記録CRUD・Drive写真バックアップ |
| UI/UX改善 | 2026-05-22〜24 | サムネイル事前生成・ギャラリービューア・統計画面リニューアル・ピンチズーム |
| 共有機能 | 2026-05-26〜28 | QRコード共有・友達マーカー色分け・行きたいリスト・CameraX対応 |
| ToS準拠 | 2026-05-29〜31 | Places API ToS対応・Powered by Google表示・HTTPS強制・ProGuard設定 |
| 安定化 | 2026-06-01〜06 | Drive appDataFolder移行・友達行きたいリスト共有・権限整理・バグ修正42件 |
| リリース | 2026-06-07 | v2.0 Play Store 製品版審査申請 |
9. 今後の改善候補
- GCP APIクォータ設定(Places API 200req/日、Maps 1000loads/日)
- Unit テスト・UIテストの充実
- Widget(ホーム画面からワンタップ記録)
- 店舗の写真をDriveに保存する複数写真対応の拡充
- 月次レポートの自動生成・共有機能
- Apple Watch / Wear OS への対応
10. 動作要件
- OS: Android 7.0(API 24)以上
- Googleアカウント(Driveバックアップ・共有機能に必要)
- カメラ権限(QRコードスキャン・写真撮影)
- 位置情報権限(ランチ記録の位置情報自動取得)
- インターネット接続(Driveバックアップ・マップ表示・Places API)
- 通知権限(Android 13以上・リマインダー通知)
サラリーマンの相場道 
