05. FCM / R2 / Mapbox / ジオフェンス 05. FCM / R2 / Mapbox / Geofence

モバイルで特別扱いが必要な 4 つの連携を、シーケンス図つきで。 The four integrations that need special handling on mobile — with sequence diagrams.

通知・ファイルアップロード・地図・位置情報の 4 連携は、BFF の通常エンドポイントと比べて 「クライアント側の手続きが増える」という特徴があります。ここでは各連携の端から端までのフローを整理します。

These four integrations — push, uploads, maps, and location — require more client-side choreography than ordinary BFF calls. This page lays out the end-to-end flow for each.

1. FCM(プッシュ通知) 1. FCM (push notifications)

1-1. トークン登録フロー 1-1. Token registration flow

sequenceDiagram
  participant App as Flutter App
  participant OS as iOS/Android
  participant FCM as FCM
  participant BFF as Parky BFF

  App->>OS: request notification permission
  OS-->>App: granted / denied
  App->>FCM: getToken()
  FCM-->>App: fcm_token
  App->>BFF: PUT /v1/me/push-tokens
{ token, device_type, app_version } BFF-->>App: 200 OK Note over App,FCM: Token can rotate — listen to onTokenRefresh FCM-->>App: onTokenRefresh(new_token) App->>BFF: PUT /v1/me/push-tokens (upsert)

1-2. 受信と画面遷移 1-2. Receive and route

sequenceDiagram
  participant BFF as Parky BFF
  participant Queue as parky-fcm-dispatch
  participant FCM as FCM v1
  participant OS as iOS/Android
  participant App as Flutter App

  BFF->>Queue: enqueue (user_ids, notif_id)
  Queue->>FCM: send batch
  FCM->>OS: push { notif_type, notif_id, deep_link? }
  OS->>App: onMessage / onBackgroundMessage
  App->>BFF: GET /v1/me/notifications/{id}
  BFF-->>App: 200 OK { notification }
  App->>App: navigate by deep_link

2. R2(ファイルアップロード) 2. R2 (file upload)

駐車メモ写真とレビュー写真は Cloudflare R2 に保存します。 モバイルは BFF から presigned PUT URL を取得R2 に直接 PUT返却された asset_id を関連レコードに紐付けという 3 ステップで実行します。

Parking-memo photos and review photos go to Cloudflare R2. The mobile app gets a presigned PUT URL from the BFF, PUTs the bytes directly to R2, and then links the returned asset_id to a parent record — three steps.

sequenceDiagram
  participant App as Flutter App
  participant BFF as Parky BFF
  participant R2 as Cloudflare R2
  participant PG as PostgreSQL

  App->>BFF: POST /v1/storage/upload-url
{ file_name, mime_type, file_size, category } BFF->>PG: INSERT assets (pending) PG-->>BFF: asset_id BFF-->>App: 200 OK
{ asset_id, upload_url, s3_key, public_url, expires_in } App->>R2: PUT upload_url
Content-Type: <exact MIME>
Body: <bytes> R2-->>App: 200 OK App->>BFF: POST /v1/parking-sessions/{id}/memo
{ memo_photo_asset_id: asset_id, ... } BFF->>PG: UPDATE parking_sessions SET memo_photo_asset_id = ... BFF->>PG: UPDATE assets SET status = 'active' BFF-->>App: 200 OK

注意点 Key points

3. Mapbox(地図表示) 3. Mapbox (maps)

Parky は地図に Mapbox を採用しています。Flutter では mapbox_maps_flutter パッケージを使います(Mapbox 公式)。 アクセストークンは Parky 運営から別途提供します(Android / iOS 両方)。

Parky uses Mapbox for maps. On Flutter we use mapbox_maps_flutter (the official package). Access tokens (for Android and iOS) are provided separately by Parky.

4. ジオフェンス(位置情報) 4. Geofence (location)

位置情報は以下の 3 用途で使います。プラットフォームネイティブのジオフェンス (iOS CLLocationManager.startMonitoring / Android GeofencingClient)を利用し、 常時ポーリングは行わないこと(バッテリー消費と審査リスクのため)。

Location is used for three cases. Use the platform-native geofencing APIs (iOS CLLocationManager.startMonitoring / Android GeofencingClient) — do not continuously poll (battery drain and store-review risk).

用途Use case 必要な権限Required permission 実装Implementation
ホーム画面の現在地センター、周辺検索Center map on current location, nearby search When in use(使用中のみ)When in use only geolocator.getCurrentPosition()、取得したら即座にトラッキング停止geolocator.getCurrentPosition(), stop tracking immediately after
駐車場到着時の「駐車開始」促進通知(任意機能)Prompt to start a session when the user arrives (optional feature) Always(バックグラウンド)— 任意許可、拒否時は機能オフAlways (background) — opt-in; feature disabled if denied ネイティブジオフェンス、半径 100m、駐車場座標を登録Native geofence at 100m radius around the parking lot
スポンサー近接検知(v1 オプション)Sponsor proximity (optional in v1) Always(上記と同じセット)Always (same permission as above) ネイティブジオフェンス、area_sponsors.radius_m を使用Native geofence using area_sponsors.radius_m

権限要求フロー Permission request flow

Realtime(Supabase Realtime) Realtime (Supabase Realtime)

v1 スコープでは Realtime は使いません(画面上の自動更新はすべてプル型で実装)。 将来「自分の通知の即時更新」「管理者からの一括通知」を実装する場合は、 Supabase Realtime の user_notifications 行フィルタ購読を追加する方針です。 Realtime is not used in v1 — all in-app updates use pull-based fetching. When we add "live notification updates" or "broadcast messages" later, we'll subscribe to user_notifications row-filtered streams via Supabase Realtime.
次のステップNext: 実装に入る前の最後のページ、06. 非機能要件とストア対応 で品質・ストア審査・A11y を確認してください。 The last page before implementation: 06. NFR and store submission covers quality, store review, and A11y.