firebase_fcm_client 0.0.3 copy "firebase_fcm_client: ^0.0.3" to clipboard
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 #

Pub Version License: MIT

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 gcloud CLI needed in production
  • Multiple auth methods automatically detected

Enhanced Security

  • Uses googleapis_auth for 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) #

# 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):

  1. Tries googleapis_auth (works in Cloud Run, GKE, local gcloud)
  2. Tries GOOGLE_APPLICATION_CREDENTIALS env var (Docker, Kubernetes)
  3. Falls back to gcloud CLI (local dev)

Throws:

  • Exception if 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

0
likes
140
points
535
downloads

Documentation

API reference

Publisher

verified publisheraortem.io

Weekly Downloads

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.

License

BSD-3-Clause (license)

Dependencies

ds_standard_features, googleapis_auth, json_annotation

More

Packages that depend on firebase_fcm_client