All the code is written and waiting. This document is everything needed to go from "dormant code" to "live FCM notifications in production."
Hand this file to Claude with: "activate FCM notifications using NOTIFICATIONS.md"
| File | Status |
|---|---|
lib/services/fcm_service.dart |
Complete — not imported anywhere yet |
lib/services/notification_service.dart |
Has showImmediate() ready for FCM foreground |
functions/src/index.ts |
Cloud Function — not deployed yet |
functions/package.json + tsconfig.json |
Ready |
pubspec.yaml |
firebase_messaging: ^15.2.5 added |
AndroidManifest.xml |
REQUEST_IGNORE_BATTERY_OPTIMIZATIONS added |
Scheduled Cloud Functions require pay-as-you-go. At this app's scale, cost is effectively zero (2M free invocations/month; the function runs 720 times/month).
Firebase Console → your project → top-left plan badge → Upgrade to Blaze.
gcloud services enable cloudscheduler.googleapis.com
- Open
ios/Runner.xcworkspacein Xcode - Runner target → Signing & Capabilities → + Capability → Push Notifications
- Also add: Background Modes → Remote notifications
Firebase Console → Project Settings → Cloud Messaging → Apple app configuration → Upload APNs Auth Key (generate from Apple Developer → Certificates → Keys).
flutter pub getFind this in lib/main.dart:
await NotificationService.instance.init();Add immediately after:
await NotificationService.instance.init();
FirebaseMessaging.onBackgroundMessage(firebaseMessagingBackgroundHandler); // ADD
await FcmService.instance.init(); // ADDAdd imports at the top of main.dart:
import 'services/fcm_service.dart';
import 'package:firebase_messaging/firebase_messaging.dart';In auth flow where completeSignIn() is called, add:
await FcmService.instance.onSignIn();In sign-out flow, add before signing out:
await FcmService.instance.onSignOut();Replace the current firebase.json content with:
{
"storage": { "rules": "storage.rules" },
"functions": [
{
"source": "functions",
"codebase": "default",
"ignore": ["node_modules", ".git"]
}
],
"flutter": {
"platforms": {
"android": {
"default": {
"projectId": "divine-dialogue-cb2ac",
"appId": "1:981232366558:android:b103be63f413b82cef234f",
"fileOutput": "android/app/google-services.json"
}
},
"dart": {
"lib/firebase_options.dart": {
"projectId": "divine-dialogue-cb2ac",
"configurations": {
"android": "1:981232366558:android:b103be63f413b82cef234f",
"ios": "1:981232366558:ios:9b55d714b406c50fef234f",
"macos": "1:981232366558:ios:9b55d714b406c50fef234f",
"web": "1:981232366558:web:5514d63bc8e4586def234f",
"windows": "1:981232366558:web:85e8bca86faf0700ef234f"
}
}
}
}
}
}cd functions
npm install
npm run build
cd ..
firebase deploy --only functionsFirebase Console → Functions → sendReadingReminders should appear with a
cron schedule of "every 60 minutes". Logs: Functions → Logs.
The Cloud Function runs in UTC. Reading plans store reminderHour as a
LOCAL hour (e.g., 8 for 8 AM PKT). Before going live, verify what Firestore
actually contains:
Firestore Console → a user doc → readingPlans → any plan → check reminderHour.
If it stores local hour (e.g., 8 = 8 AM PKT), update functions/src/index.ts:
// Change this line:
if (plan.reminderHour !== currentHour) continue;
// To this (UTC+5 for PKT):
const localHour = (currentHour + 5) % 24;
if (plan.reminderHour !== localHour) continue;Get your device's token (add temporarily, remove after):
final token = await FirebaseMessaging.instance.getToken();
debugPrint('FCM token: $token');Send a test message: Firebase Console → Messaging → Send your first message → paste token → Send.
Test the Cloud Function locally:
cd functions
firebase emulators:start --only functionsRemove the 3 lines added to main.dart (Step 2). The existing local
notification system (Option A) keeps working independently. No data is lost.