firebase_fcm_client 0.0.2
firebase_fcm_client: ^0.0.2 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 secure service account impersonation.
Send push notifications to devices, topics, and conditions without managing private keys.
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 - Battle-tested patterns
Installation #
Add to pubspec.yaml:
dependencies:
firebase_fcm_client: ^0.0.1
Then run:
dart pub get
Setup #
1. Install Google Cloud SDK #
Download and install from: https://cloud.google.com/sdk/docs/install
2. Authenticate with Google Cloud #
gcloud auth application-default login
This opens a browser where you authenticate with your Google account and grants local credentials.
3. Get Service Account Email #
Ask your Google Cloud administrator for the FCM service account email:
fcm-sa@your-project.iam.gserviceaccount.com
Or find it yourself:
gcloud iam service-accounts list
4. Verify Permissions #
Ensure your account has the "Service Account Token Creator" role on the service account:
gcloud projects get-iam-policy YOUR_PROJECT \
--flatten="bindings[].members" \
--filter="bindings.members:YOUR_EMAIL@company.com"
Quick Start #
Basic Example #
import 'package:firebase_fcm_client/firebase_fcm_notification.dart';
void main() async {
// Initialize with service account impersonation
final auth = await ServiceAccountAuth.fromImpersonation(
serviceAccountEmail: 'fcm-sa@your-project.iam.gserviceaccount.com',
projectId: 'your-project-id',
);
final fcm = FCMClient(auth: auth, projectId: 'your-project-id');
try {
// Send notification to a device
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 #
Service account authentication using Google Cloud impersonation.
Constructor
final auth = await ServiceAccountAuth.fromImpersonation({
required String serviceAccountEmail,
required String projectId,
});
Parameters:
serviceAccountEmail: Service account email (e.g.,fcm-sa@project.iam.gserviceaccount.com)projectId: Google Cloud project ID
Throws:
Exceptionif Application Default Credentials not configured
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
Send notification to a specific device:
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
Parameters:
deviceToken(required): Device registration tokentitle(required): Notification titlebody(required): Notification body textdata(optional): Custom data key-value pairs (max 4KB)android(optional): Android-specific configurationwebpush(optional): Web push configuration
Returns: Message ID string
Throws: FCMException on error
Send to Topic
Send notification to all devices subscribed to a topic:
final result = await fcm.sendToTopic(
'news',
title: 'Breaking News',
body: 'Important update available',
data: {
'story_id': '12345',
'priority': 'high',
},
);
print('Sent to topic: $result');
Parameters:
topic(required): Topic nametitle(required): Notification titlebody(required): Notification body textdata(optional): Custom data
Send to Condition
Send notification based on complex conditions:
final result = await fcm.sendToCondition(
"'sports' in topics && 'cricket' in topics",
title: 'Cricket News',
body: 'Latest updates',
);
print('Sent to condition: $result');
Condition Examples:
// Single topic
"'topic1' in topics"
// Multiple topics (OR)
"'topic1' in topics || 'topic2' in topics"
// Multiple topics (AND)
"'topic1' in topics && 'topic2' in topics"
// Complex
"('sports' in topics && 'cricket' in topics) || 'breaking_news' in topics"
Models #
NotificationMessage
final message = NotificationMessage(
token: 'device_token', // OR topic/condition
notification: NotificationPayload(
title: 'Title',
body: 'Body',
imageUrl: 'https://example.com/image.png',
),
data: {'key': 'value'},
android: AndroidConfig(...),
webpush: WebpushConfig(...),
);
AndroidConfig
final androidConfig = AndroidConfig(
priority: 'high', // 'high' or 'normal'
notification: AndroidNotification(
title: 'Custom Title',
body: 'Custom Body',
icon: 'ic_notification',
color: '#FF5733',
sound: 'default',
priority: 1,
),
collapseKey: 'message_group',
ttl: 86400, // Time to live in seconds
);
WebpushConfig
final webConfig = WebpushConfig(
notification: WebpushNotification(
title: 'Web Title',
body: 'Web Body',
icon: 'https://example.com/icon.png',
badge: 'https://example.com/badge.png',
),
data: {
'url': 'https://example.com',
},
);
Error Handling #
FCMException #
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 #
| Error | Cause | Solution |
|---|---|---|
ADC not configured |
Missing gcloud auth application-default login |
Run: gcloud auth application-default login |
Permission denied |
Missing IAM role | Ask admin to grant "Service Account Token Creator" |
Invalid service account |
Wrong email format | Verify email: fcm-sa@project.iam.gserviceaccount.com |
Invalid token |
Device token expired | Get fresh token from Firebase Messaging |
Topic not found |
Invalid topic name | Ensure devices are subscribed to topic |
Examples #
Send with Data Only (No Notification) #
await fcm.sendToToken(
deviceToken,
title: '',
body: '',
data: {
'action': 'open_settings',
'screen': 'profile',
},
);
Send to Multiple Topics (Condition) #
await fcm.sendToCondition(
"'notifications' in topics && 'offers' in topics",
title: 'Special Offer',
body: 'Limited time deal',
data: {'offer_id': 'XYZ123'},
);
Send with Custom Android Configuration #
await fcm.sendToToken(
deviceToken,
title: 'Urgent Alert',
body: 'Action required',
android: AndroidConfig(
priority: 'high',
notification: AndroidNotification(
color: '#FF0000',
sound: 'alarm',
priority: 2,
),
collapseKey: 'alert',
ttl: 3600,
),
);
Send with Web Push Configuration #
await fcm.sendToToken(
deviceToken,
title: 'Web Notification',
body: 'Click to open',
webpush: WebpushConfig(
notification: WebpushNotification(
title: 'Click Me',
icon: 'https://example.com/icon.png',
badge: 'https://example.com/badge.png',
),
data: {
'link': 'https://example.com/article',
},
),
);
Send with Image #
await fcm.sendToToken(
deviceToken,
title: 'Check This Out',
body: 'Image notification',
data: {},
android: AndroidConfig(
notification: AndroidNotification(
icon: 'ic_notification',
),
),
);
Best Practices #
1. Reuse ServiceAccountAuth #
// Create once
final auth = await ServiceAccountAuth.fromImpersonation(...);
// Use multiple times
final fcm1 = FCMClient(auth: auth, projectId: 'project1');
final fcm2 = FCMClient(auth: auth, projectId: 'project2');
// Close when done
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));
}
}
throw Exception('Failed after retries');
}
3. Validate Tokens Before Sending #
bool isValidToken(String token) {
return token.isNotEmpty && token.length > 50;
}
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
await fcm.sendToTopic('users', title: '...', body: '...');
Testing #
Run unit tests:
dart test -r expanded
The package includes 24+ comprehensive tests covering:
- Model serialization/deserialization
- Message construction
- Error handling
- Complex message scenarios
Troubleshooting #
"ADC not configured" Error #
# Fix:
gcloud auth application-default login
"Permission denied" Error #
Ask your Google Cloud admin to run:
gcloud projects add-iam-policy-binding YOUR_PROJECT \
--member=user:YOUR_EMAIL@company.com \
--role=roles/iam.serviceAccountTokenCreator \
--condition=resource.name==serviceAccounts/FCM_SERVICE_ACCOUNT
Package Not Found #
# Clear cache and reinstall
dart pub cache clean
dart pub get
Performance Notes #
- Token Caching: Access tokens are cached and automatically refreshed
- Network: Uses HTTP keep-alive for connection reuse
- Concurrency: Multiple requests can run concurrently
Security #
- No Private Keys: Uses service account impersonation
- Automatic Token Refresh: Handles expiration transparently
- Credential Isolation: Credentials never in code/config
- Audit Trail: Google Cloud logs all access
Migration #
From Private Key Method #
// Old way (not recommended)
// final auth = ServiceAccountAuth.fromFile('key.json');
// New way (recommended)
final auth = await ServiceAccountAuth.fromImpersonation(
serviceAccountEmail: 'fcm-sa@project.iam.gserviceaccount.com',
projectId: 'your-project-id',
);
License #
MIT License - See LICENSE file for details
Support #
- Issues: GitHub Issues
- Pub.dev: Package Page
- Documentation: Full API docs available in code
Contributing #
Contributions are welcome! Please feel free to submit a pull request.
Changelog #
See CHANGELOG.md for version history
Disclaimer #
This package is designed for server-side/backend use. Do NOT embed service account credentials in client applications.
Made with ❤️ for Flutter and Dart developers