Context
The Roomy app currently has no push notification infrastructure. Users have no way of knowing when household members add or complete shopping list items without manually checking the app. The existing lib/inbox/ feature has UI scaffolding and a MessageDto but no backend integration.
Stakeholders:
- End users: Receive timely notifications about household activity
- Household members: Stay coordinated on shopping tasks
- Developers: Maintain clean architecture patterns
Constraints:
- Must follow existing MVVM architecture and service patterns
- Must integrate with existing
MessageDto and inbox UI
- Must support both iOS and Android platforms
- Must handle FCM configuration via Playwright automation
- Must batch notifications within 30-second windows to reduce noise
Goals / Non-Goals
Goals:
- Implement FCM push notifications for shopping list item add/complete events
- Store notifications in Firestore for in-app inbox viewing
- Provide per-category notification preferences (Shopping, Cleaning, etc.)
- Support deep linking from notification tap to relevant shopping list
- Track read/unread state with visual indicators
Non-Goals:
- Email notifications (existing email infrastructure is for marketing only)
- Quiet hours / do-not-disturb scheduling (future enhancement)
- Rich media notifications (images, actions beyond deep link)
- Web push notifications
Decisions
1. FCM Token Storage Strategy
Decision: Store FCM tokens as a subcollection under users (users/{userId}/fcmTokens/{tokenId})
Alternatives considered:
- Array field on user document: Limited to 1MB document size, no token metadata
- Separate top-level collection: Requires additional security rules complexity
Rationale: Subcollection allows multiple devices per user, automatic cleanup via TTL, and simple security rules (user owns their tokens).
2. Notification Dispatch Architecture
Decision: Use Firestore onCreate/onUpdate triggers on shoppingListItems collection with Cloud Task-based batching
Alternatives considered:
- Direct FCM send from Flutter app: Requires service account on client, security risk
- Scheduled Cloud Function polling: Higher latency, more complex state management
Rationale: Triggers provide real-time response. Cloud Tasks enable 30-second batching window without blocking the trigger function.
3. Notification Document Structure
Decision: Extend existing MessageDto structure for notifications, add householdId and notificationType fields
Alternatives considered:
- Create separate
NotificationDto: Duplicates existing message structure
- Store only in FCM data payload: No persistence for inbox viewing
Rationale:
MessageDto already has notificationTitle, notificationMessage, readAt, and recipientId fields. Minor extension maintains consistency.
4. Batching Implementation
Decision: Use Cloud Tasks with 30-second delay. Batch key: {householdId}:{actorUserId}:{action}:{timestamp_bucket}
Alternatives considered:
- In-memory aggregation in Cloud Function: Stateless functions lose state on cold starts
- Firestore-based aggregation: Additional reads/writes, complex cleanup
Rationale: Cloud Tasks guarantee at-least-once delivery. Failed batches can be retried. Timestamp buckets (30s) ensure deterministic batch grouping.
5. Notification Preferences Schema
Decision: Store as subcollection users/{userId}/settings/notifications with category-based toggles
Alternatives considered:
- Add to existing
SettingsDto: Increases document size, couples concerns
- Top-level collection: Complicates security rules
Rationale: Subcollection under settings follows existing pattern (
users/{userId}/settings). Easy to extend with additional categories.
6. Deep Linking Strategy
Decision: Use FCM data payload with route and params fields. Flutter app parses and navigates via go_router.
Alternatives considered:
- Firebase Dynamic Links: Being deprecated by Google
- Custom URL scheme: Requires additional platform configuration
Rationale: FCM data payloads are reliable and already supported. go_router can handle programmatic navigation.
Data Models
Firestore Collections
users/{userId}/fcmTokens/{tokenId}
- token: string
- platform: 'ios' | 'android'
- createdAt: timestamp
- lastActiveAt: timestamp
users/{userId}/settings/notifications
- shoppingEnabled: boolean (default: true)
- cleaningEnabled: boolean (default: true)
- globalEnabled: boolean (default: true)
- updatedAt: timestamp
notifications/{notificationId}
- id: string
- householdId: string
- recipientId: string
- senderId: string
- notificationType: 'shoppingItemAdded' | 'shoppingItemCompleted' | ...
- title: string
- message: string
- created: timestamp
- readAt: timestamp | null
- data: { shoppingListId: string, itemCount: number, ... }
Cloud Function Flow
1. shoppingListItems.onCreate/onUpdate trigger fires
2. Check if action is add or complete
3. Fetch household members (excluding actor)
4. For each member: check notification preferences
5. Queue Cloud Task with 30s delay, batch key
6. Cloud Task handler:
a. Aggregate items in batch window
b. Create notification document per recipient
c. Fetch recipient FCM tokens
d. Send FCM multicast
Risks / Trade-offs
| Risk | Mitigation |
|------|------------|
| FCM token stale/invalid | Implement token refresh on app launch, handle FCM error codes to remove invalid tokens |
| Cloud Task batch race conditions | Use idempotent batch keys, deduplicate notifications in handler |
| Notification spam | 30-second batching, per-category preferences, future quiet hours |
| APNs certificate expiry | Document renewal process, add monitoring/alerting |
| Deep link to deleted item | Handle gracefully in Flutter - show error toast, navigate to shopping list |
Migration Plan
Phase 1: Infrastructure (no user impact)
- Add FCM dependencies to Flutter app
- Create Cloud Functions structure
- Configure FCM in Firebase Console (Playwright)
- Configure APNs in Apple Developer Portal (Playwright)
Phase 2: Backend (no user impact)
- Deploy Cloud Functions (disabled triggers)
- Create Firestore security rules for new collections
- Implement FCM token registration in Flutter
Phase 3: Feature rollout
- Enable Cloud Function triggers
- Deploy Flutter notification handling
- Add notification preferences to settings
- Enhance inbox UI with notification display
Rollback:
- Disable Cloud Function triggers to stop new notifications
- FCM tokens persist but are unused
- Existing inbox functionality unchanged
Open Questions
- Resolved: Should notifications be batched? → Yes, 30-second window
- Resolved: Per-category or global preferences? → Per-category (Shopping, Cleaning, etc.)
- Future: Should we add quiet hours? → Not in initial scope, can be added later
- Future: Should we support notification actions (mark complete from notification)? → Not in initial scope
Reacties