✅ Fixed: Code now separates FCM tokens from Expo tokens
Security: FCM requires a Server Key which should NEVER be exposed in client code.
Current Flow:
Admin updates price → sendPushNotifications() called
├── ✅ Expo tokens: Sent via Expo service (works)
└── ❌ FCM tokens: Logged but not sent (needs backend)
When admin updates prices, manually send from Firebase Console:
- Admin updates price in app
- Check console logs for FCM tokens
- Open Firebase Console → Cloud Messaging
- Click "Send test message"
- Paste FCM token
- Enter notification details
- Send
Pros: Works immediately, no code changes Cons: Manual process each time
Add FCM Server Key to client code for testing.
1. Get FCM Server Key:
- Firebase Console → Project Settings → Cloud Messaging
- Copy Server Key (starts with "AAAA...")
2. Add to .env:
EXPO_PUBLIC_FCM_SERVER_KEY="AAAA..." # Your server key3. Update AdminPriceFormScreen.tsx:
Replace the FCM section (lines 171-180) with:
// Send to FCM tokens (production users)
if (fcmTokens.length > 0) {
console.log(`Sending to ${fcmTokens.length} FCM tokens`);
const FCM_SERVER_KEY = process.env.EXPO_PUBLIC_FCM_SERVER_KEY;
if (!FCM_SERVER_KEY) {
console.error('FCM_SERVER_KEY not found in .env');
} else {
for (const token of fcmTokens) {
try {
await fetch('https://fcm.googleapis.com/fcm/send', {
method: 'POST',
headers: {
'Authorization': `key=${FCM_SERVER_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
to: token,
notification: {
title: notificationTitle,
body: notificationBody,
icon: 'notification_icon',
color: '#3B82F6',
},
data: {
screen: 'Market',
market: priceData.market,
breed: priceData.breed,
},
}),
});
console.log(`FCM notification sent to: ${token.substring(0, 20)}...`);
} catch (error) {
console.error('FCM send error:', error);
}
}
}
}Pros: Works automatically, immediate solution Cons:
⚠️ Security risk (server key in client)⚠️ Not recommended for production⚠️ Anyone can decompile APK and steal key
Proper backend solution - notifications sent from secure server.
Admin updates price → Firestore updated → Cloud Function triggered → Send FCM notifications
1. Install Firebase CLI:
npm install -g firebase-tools
firebase login2. Initialize Cloud Functions:
firebase init functions
# Select: JavaScript or TypeScript
# Install dependencies: Yes3. Create Function (functions/index.js):
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
exports.sendPriceUpdateNotification = functions.firestore
.document('cocoonPrices/{priceId}')
.onWrite(async (change, context) => {
// Triggered when price is added or updated
const priceData = change.after.data();
if (!priceData) return; // Document was deleted
try {
// Get all FCM tokens
const tokensSnapshot = await admin.firestore()
.collection('pushTokens')
.where('tokenType', '==', 'fcm')
.get();
const tokens = tokensSnapshot.docs.map(doc => doc.data().token);
if (tokens.length === 0) {
console.log('No FCM tokens found');
return;
}
// Create notification message
const message = {
notification: {
title: `${priceData.market} - ${priceData.breed} Price Update`,
body: `Min: ₹${priceData.minPrice} | Max: ₹${priceData.maxPrice} | Avg: ₹${priceData.avgPrice}/kg`,
},
data: {
screen: 'Market',
market: priceData.market,
breed: priceData.breed,
},
tokens: tokens,
};
// Send to all FCM tokens
const response = await admin.messaging().sendEachForMulticast(message);
console.log(`✅ Successfully sent: ${response.successCount}`);
console.log(`❌ Failed: ${response.failureCount}`);
// Clean up invalid tokens
if (response.failureCount > 0) {
const failedTokens = [];
response.responses.forEach((resp, idx) => {
if (!resp.success) {
failedTokens.push(tokens[idx]);
}
});
// Delete invalid tokens
for (const token of failedTokens) {
await admin.firestore().collection('pushTokens').doc(token).delete();
}
console.log(`🗑️ Cleaned up ${failedTokens.length} invalid tokens`);
}
} catch (error) {
console.error('Error sending notification:', error);
}
});4. Deploy:
firebase deploy --only functions5. Remove client-side notification code:
Update AdminPriceFormScreen.tsx - remove the await sendPushNotifications(priceData); call on line 148 since Cloud Function handles it automatically.
Pros:
- ✅ Secure (server key never exposed)
- ✅ Automatic (triggers on Firestore changes)
- ✅ Production-ready
- ✅ Reliable (runs on Google servers)
Cons:
- Requires Firebase billing (free tier available)
- Initial setup time
For Immediate Testing (Today): → Use Option 1 (Manual Firebase Console)
For Production (This Week): → Implement Option 3 (Cloud Functions)
Avoid: → Option 2 (Client-side FCM) - Security risk
After implementing your chosen option:
- Admin updates price in app
- Check Firestore - price updated
- Check console logs - FCM tokens identified
- Notification sent automatically (Option 2/3) or manually (Option 1)
- Production APK users receive notification
- Notification shows app icon
- Tapping notification opens app to Market screen
✅ What's Working:
- Token type detection (FCM vs Expo)
- Expo token notifications (Expo Go users)
- Logging of FCM tokens
- FCM notification sending (choose Option 1, 2, or 3)
Option 1 (Manual): Already working - just use Firebase Console Option 2 (Client-side): I can add the code if you provide FCM Server Key Option 3 (Cloud Functions): I can set up the complete Cloud Functions project
Which option would you like to implement?