firebase_fcm_client 0.0.3
firebase_fcm_client: ^0.0.3 copied to clipboard
Firebase Cloud Messaging (FCM) HTTP v1 API client for Dart with secure service account impersonation. Send notifications to devices, topics, and conditions without managing private keys.
Firebase FCM Client #
Firebase Cloud Messaging (FCM) HTTP v1 API client for Dart with production-ready authentication.
Send push notifications to devices, topics, and conditions without managing private keys.
What's New in v0.0.3 #
Production-Ready Authentication
- Works in Cloud Run, GKE, Docker, Kubernetes
- No
gcloudCLI needed in production - Multiple auth methods automatically detected
Enhanced Security
- Uses
googleapis_authfor secure token handling - Automatic token refresh before expiry
- No credentials in code
Improved Reliability
- Supports GOOGLE_APPLICATION_CREDENTIALS environment variable
- Fallback authentication methods
- Better error messages for debugging
Features #
- Send to Device Tokens - Target specific devices
- Send to Topics - Broadcast to subscribed devices
- Send to Conditions - Complex targeting with logical operators
- Service Account Impersonation - Secure authentication, no private keys
- Android Configuration - Customize Android-specific behavior
- Web Push Configuration - Web-specific notification settings
- Automatic Token Caching - Efficient token management
- Automatic Token Refresh - Handles expiration transparently
- Complete Error Handling - Detailed error messages
- Full Type Safety - 100% Dart type-safe
- Enterprise-Grade Security - No credentials in code
- Production Ready - Works in all environments
Installation #
Add to pubspec.yaml:
dependencies:
firebase_fcm_client: ^0.0.3
Then run:
dart pub get
Setup (Choose One Method) #
Method 1: Cloud Run / GKE (Recommended) #
# 1. Create service account
gcloud iam service-accounts create intellitoggle-fcm \
--display-name="IntelliToggle FCM Service"
# 2. Grant permissions
gcloud projects add-iam-policy-binding PROJECT_ID \
--member=serviceAccount:intellitoggle-fcm@PROJECT.iam.gserviceaccount.com \
--role=roles/firebase.cloudmessagingServiceAgent
# 3. Deploy with service account
gcloud run deploy my-backend \
--source . \
--service-account=intellitoggle-fcm@PROJECT.iam.gserviceaccount.com
Result: Works automatically! No env vars needed.
Method 2: Docker / Kubernetes #
# 1. Create service account JSON key
gcloud iam service-accounts keys create key.json \
--iam-account=intellitoggle-fcm@PROJECT.iam.gserviceaccount.com
# 2. Keep it safe
echo "key.json" >> .gitignore
docker-compose.yml:
environment:
- GOOGLE_APPLICATION_CREDENTIALS=/secrets/service-account.json
volumes:
- ./key.json:/secrets/service-account.json:ro
Kubernetes:
env:
- name: GOOGLE_APPLICATION_CREDENTIALS
value: /var/secrets/fcm/service-account.json
volumeMounts:
- name: fcm-secret
mountPath: /var/secrets/fcm
Method 3: Local Development #
gcloud auth application-default login
Then run:
dart run bin/main.dart
Quick Start #
Basic Example #
import 'package:firebase_fcm_client/firebase_fcm_client.dart';
void main() async {
// Works in all environments:
// - Cloud Run (automatic)
// - GKE (automatic)
// - Docker (via GOOGLE_APPLICATION_CREDENTIALS)
// - Local dev (via gcloud auth)
final auth = await ServiceAccountAuth.fromImpersonation(
serviceAccountEmail: 'intellitoggle-fcm@project.iam.gserviceaccount.com',
projectId: 'intellitoggle-project',
);
final fcm = FCMClient(auth: auth, projectId: 'intellitoggle-project');
try {
// Send notification
final result = await fcm.sendToToken(
'device_token_here',
title: 'Hello!',
body: 'This is a test notification',
);
print('Message sent: $result');
await auth.close();
} catch (e) {
print('Error: $e');
}
}
API Reference #
ServiceAccountAuth #
Handles authentication for Firebase Cloud Messaging.
Initialization
final auth = await ServiceAccountAuth.fromImpersonation({
required String serviceAccountEmail,
required String projectId,
});
How it works (automatic):
- Tries googleapis_auth (works in Cloud Run, GKE, local gcloud)
- Tries GOOGLE_APPLICATION_CREDENTIALS env var (Docker, Kubernetes)
- Falls back to gcloud CLI (local dev)
Throws:
Exceptionif all auth methods fail
Methods
// Get access token (cached automatically)
Future<String> getAccessToken()
// Close resources
Future<void> close()
FCMClient #
Firebase Cloud Messaging HTTP v1 API client.
Constructor
final fcm = FCMClient({
required ServiceAccountAuth auth,
required String projectId,
});
Send to Device Token
final result = await fcm.sendToToken(
'device_token',
title: 'Title',
body: 'Body text',
data: {
'key1': 'value1',
'key2': 'value2',
},
android: AndroidConfig(
priority: 'high',
notification: AndroidNotification(
color: '#FF5733',
sound: 'default',
),
),
webpush: WebpushConfig(
notification: WebpushNotification(
title: 'Web Title',
icon: 'https://example.com/icon.png',
),
),
);
print('Sent: $result'); // Message ID
Send to Topic
final result = await fcm.sendToTopic(
'news',
title: 'Breaking News',
body: 'Important update available',
data: {
'story_id': '12345',
},
);
Send to Condition
final result = await fcm.sendToCondition(
"'sports' in topics && 'cricket' in topics",
title: 'Cricket News',
body: 'Latest updates',
);
Authentication Flow #
┌─ serviceAccountEmail: 'fcm-sa@project.iam.gserviceaccount.com'
│
└─ ServiceAccountAuth.fromImpersonation()
│
├─ Try 1: googleapis_auth + Application Default Credentials
│ ├─ Works in: Cloud Run
│ ├─ Works in: GKE
│ ├─ Works in: Local (gcloud auth)
│ └─ Success → Use this
│
├─ Try 2: GOOGLE_APPLICATION_CREDENTIALS environment variable
│ ├─ Works in: Docker
│ ├─ Works in: Kubernetes
│ ├─ Requires: env var set + JSON file mounted
│ └─ Success → Use this
│
├─ Try 3: gcloud CLI (fallback)
│ ├─ Works in: Local dev
│ ├─ Requires: gcloud installed + auth setup
│ └─ Success → Use this
│
└─ All failed → Throw helpful error
Error Handling #
try {
await fcm.sendToToken(token, title: '...', body: '...');
} on FCMException catch (e) {
print('Error: ${e.message}');
print('Code: ${e.code}');
print('Status: ${e.statusCode}');
}
Common Errors & Solutions #
| Error | Cause | Solution |
|---|---|---|
All authentication methods failed |
No auth setup | Use one of the 3 setup methods |
GOOGLE_APPLICATION_CREDENTIALS not set |
Docker/K8s issue | Set env var and mount secret |
gcloud command failed |
gcloud CLI missing | Run gcloud auth application-default login |
Invalid token |
Device token expired | Get fresh token from Firebase Messaging |
Permission denied |
Missing IAM role | Ask admin to grant proper role |
Examples #
Real-World #
class NotificationService {
late FCMClient _fcm;
late ServiceAccountAuth _auth;
Future<void> initialize() async {
_auth = await ServiceAccountAuth.fromImpersonation(
serviceAccountEmail: 'intellitoggle-fcm@project.iam.gserviceaccount.com',
projectId: 'intellitoggle-project',
);
_fcm = FCMClient(auth: _auth, projectId: 'intellitoggle-project');
}
Future<void> notifyFlagToggle({
required String userId,
required String deviceToken,
required String flagName,
required bool newValue,
}) async {
await _fcm.sendToToken(
deviceToken,
title: '$flagName was toggled ${newValue ? "ON" : "OFF"}',
body: 'Feature flag changed in production',
data: {
'actionType': 'feature-flag-update',
'flagName': flagName,
'newValue': newValue.toString(),
},
);
}
Future<void> shutdown() async {
await _auth.close();
}
}
Send with Custom Android Config #
await fcm.sendToToken(
deviceToken,
title: 'Urgent Alert',
body: 'Action required',
android: AndroidConfig(
priority: 'high',
notification: AndroidNotification(
color: '#FF0000',
sound: 'alarm',
priority: 2,
),
),
);
Send to Topic #
await fcm.sendToTopic(
'feature-updates',
title: 'New Feature Available',
body: 'Check out our latest update',
);
Best Practices #
1. Reuse ServiceAccountAuth #
// Create once at startup
final auth = await ServiceAccountAuth.fromImpersonation(...);
// Reuse for all FCM clients
final fcm1 = FCMClient(auth: auth, projectId: 'project1');
final fcm2 = FCMClient(auth: auth, projectId: 'project2');
// Close when shutting down
await auth.close();
2. Implement Retry Logic #
Future<String> sendWithRetry(String token, String title, String body) async {
int retries = 3;
while (retries > 0) {
try {
return await fcm.sendToToken(token, title: title, body: body);
} catch (e) {
retries--;
if (retries == 0) rethrow;
await Future.delayed(Duration(seconds: 2 ^ (3 - retries)));
}
}
throw Exception('Failed after retries');
}
3. Validate Tokens Before Sending #
bool isValidToken(String token) {
return token.isNotEmpty && token.length > 100;
}
if (isValidToken(deviceToken)) {
await fcm.sendToToken(deviceToken, title: '...', body: '...');
}
4. Use Topics for Bulk Messaging #
// Instead of looping through tokens
for (final token in tokens) {
await fcm.sendToToken(token, title: '...', body: '...');
}
// Use topics (more efficient)
await fcm.sendToTopic('users', title: '...', body: '...');
Testing #
Unit Tests #
dart test test/firebase_fcm_notification_test.dart
//or
dart test -r expanded
Tests cover:
- Model serialization/deserialization
- Message construction
- Error handling
- Complex message scenarios
Integration Test #
# Setup auth first
gcloud auth application-default login
# Run full test
dart test
Migration from v0.0.2 to v0.0.3 #
No breaking changes to your code! Just update the package version:
dependencies:
firebase_fcm_client: ^0.0.3
Your existing code works as-is:
final auth = await ServiceAccountAuth.fromImpersonation(
serviceAccountEmail: 'fcm-sa@project.iam.gserviceaccount.com',
projectId: 'your-project-id',
);
The difference: v0.0.3 now works in production environments (Cloud Run, Docker, K8s)!
Troubleshooting #
Test Authentication #
# Test Cloud Run/GKE auth
gcloud auth application-default login
dart run bin/main.dart
# Should print: Using Application Default Credentials
Check Environment Variable #
# Docker/Kubernetes
echo $GOOGLE_APPLICATION_CREDENTIALS # Should print path
ls -la /path/to/service-account.json # Should exist
Debug Logs #
// Enable detailed logging
final auth = await ServiceAccountAuth.fromImpersonation(
serviceAccountEmail: 'fcm-sa@project.iam.gserviceaccount.com',
projectId: 'intellitoggle-project',
);
// Will print which auth method is being used
final token = await auth.getAccessToken();
Performance #
- Token Caching: Tokens cached and reused until expiration
- Automatic Refresh: Tokens refreshed transparently before expiry
- Connection Pooling: HTTP keep-alive enabled
- Concurrent Requests: Multiple messages can be sent in parallel
Security #
No Private Keys: Uses secure service account impersonation
Automatic Token Refresh: Credentials never exposed
Credential Isolation: No secrets in code/config
Audit Trail: All operations logged in Google Cloud
Support #
- Issues: GitHub Issues
- Documentation: Check code comments for detailed explanations
- Examples: See examples/ directory for complete implementations
License #
MIT License - See LICENSE file for details
Changelog #
See CHANGELOG.md for version history
Made with ❤️ for Dart and Flutter developers