firebase_notification_helper 0.0.4
firebase_notification_helper: ^0.0.4 copied to clipboard
A lightweight helper for Firebase Messaging, local notifications, and sending FCM push notifications via Legacy API. Best for internal tools and testing environments.
๐ฉ firebase_notification_helper #
A lightweight, easy-to-use Flutter helper package for Firebase Cloud Messaging (FCM), Local Notifications, and Firebase Cloud Function based remote push notification sending.
This package is designed to make notification integration extremely simple โ even for beginners.
It includes FCM token fetching, local notification display, foreground notification handling, background handler support, topic subscription, and remote push sending through Firebase Cloud Functions.
โ Summary of Everything Included in This Package #
- โ๏ธ Firebase Cloud Messaging setup
- โ๏ธ Local notification support
- โ๏ธ FCM token fetch
- โ๏ธ Android notification channel creation
- โ๏ธ Foreground notification listener
- โ๏ธ Background message handler support
- โ๏ธ Notification tap payload callback
- โ๏ธ FCM token refresh callback
- โ๏ธ Topic subscribe and unsubscribe
- โ๏ธ Remote notification sending through Firebase Cloud Functions
- โ๏ธ Send notification to token, multiple tokens, topic, or condition
- โ๏ธ Beautiful full-page Flutter example UI
- โ๏ธ Example app run instructions
- โ๏ธ Security best practices
- โ๏ธ Pub.dev-ready consistent formatting
Remote notification sending requires Firebase Cloud Functions or another trusted server environment.
โญ Features #
- ๐ฅ Fetch FCM Token
- ๐ Show Local Notifications
- ๐จ Send Remote Push Notification using Firebase Cloud Functions
- ๐ก Send Notification to Topic
- ๐ฅ Send Notification to Multiple Tokens
- ๐ฏ Send Notification to Condition
- ๐ Foreground Message Handler Support
- ๐ Background Message Handler Support
- ๐ Notification Tap Callback
- ๐ Token Refresh Callback
- ๐งช Full Example App Included
- ๐ก Safer than client-side server key sending
- ๐ฆ Clean and beginner-friendly API
๐ Important Security Note #
Do not put your Firebase server key or service account key inside a Flutter app.
Remote push notification sending should be done from a trusted environment, such as:
- Firebase Cloud Functions
- Your own backend server
- Firebase Admin SDK
This package supports remote notification sending through Firebase Cloud Functions.
๐ฆ Installation #
Add the package in pubspec.yaml:
dependencies:
firebase_notification_helper: ^0.0.4
Then run:
flutter pub get
๐ฅ Firebase Setup #
Before using this package, connect your Flutter app with Firebase.
Recommended setup:
dart pub global activate flutterfire_cli
flutterfire configure
For Android, make sure google-services.json exists here:
android/app/google-services.json
For the package example app, make sure google-services.json exists here:
example/android/app/google-services.json
๐ค Android Permission Setup #
Add this permission in:
android/app/src/main/AndroidManifest.xml
Add it above the <application> tag:
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
โ๏ธ Upgrade this android part in /android/build.gradle.kts #
If your project needs manual Firebase setup, add Google Services classpath:
buildscript {
repositories {
google()
mavenCentral()
}
dependencies {
classpath("com.google.gms:google-services:4.4.2")
}
}
โ๏ธ Upgrade this android part in /android/app/build.gradle.kts #
Add Google Services plugin:
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
id("com.google.gms.google-services") // REQUIRED
id("dev.flutter.flutter-gradle-plugin")
}
If your project needs local notification desugaring support, add this:
android {
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
isCoreLibraryDesugaringEnabled = true
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_11.toString()
}
}
dependencies {
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.4")
}
๐ Initialization #
Import the package:
import 'package:flutter/material.dart';
import 'package:firebase_notification_helper/firebase_notification_helper.dart';
Then initialize before runApp():
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await FirebaseNotificationHelper.initialize(
config: const NotificationHelperConfig(
debug: true,
channelId: 'high_importance_channel',
channelName: 'High Importance Notifications',
channelDescription: 'Used for important notifications.',
cloudFunctionName: 'sendNotification',
cloudFunctionRegion: 'us-central1',
requestPermissionOnInit: true,
enableForegroundLocalNotification: true,
handleInitialMessage: true,
emitInitialToken: true,
),
onNotificationTap: (payload) {
debugPrint('Notification tapped: $payload');
},
onTokenChanged: (token) {
debugPrint('FCM Token Changed: $token');
},
);
runApp(const MyApp());
}
๐ Get FCM Token #
final token = await FirebaseNotificationHelper.getToken();
debugPrint("FCM Token: $token");
๐ Show Local Notification #
await FirebaseNotificationHelper.showLocalNotification(
title: "Local Test",
body: "This alert is from your device.",
payload: {
"screen": "home",
"type": "local",
},
);
๐ก Subscribe to Topic #
await FirebaseNotificationHelper.subscribeToTopic("all_users");
๐ก Unsubscribe from Topic #
await FirebaseNotificationHelper.unsubscribeFromTopic("all_users");
โ๏ธ Firebase Cloud Function Setup for Remote Notification #
Remote push notification sending requires Firebase Cloud Functions.
Flow:
Flutter App
โ
firebase_notification_helper
โ
Firebase Cloud Function
โ
Firebase Admin SDK
โ
Firebase Cloud Messaging
โ
Receiver Device
Initialize Firebase Functions:
firebase init functions
Choose:
JavaScript
๐ functions/package.json #
Use this code:
{
"name": "functions",
"description": "Cloud Functions for firebase_notification_helper",
"type": "module",
"engines": {
"node": "22"
},
"main": "index.js",
"scripts": {
"serve": "firebase emulators:start --only functions",
"deploy": "firebase deploy --only functions",
"logs": "firebase functions:log"
},
"dependencies": {
"firebase-admin": "^14.0.0",
"firebase-functions": "^7.2.5"
},
"devDependencies": {}
}
๐ functions/index.js #
Use this Cloud Function code:
import { initializeApp } from "firebase-admin/app";
import { getMessaging } from "firebase-admin/messaging";
import { HttpsError, onCall } from "firebase-functions/v2/https";
initializeApp();
const REGION = "us-central1";
/**
* For testing: false
* For production: true
*
* If true, user must be logged in with Firebase Auth.
*/
const REQUIRE_AUTH = false;
export const sendNotification = onCall(
{
region: REGION
},
async (request) => {
try {
if (REQUIRE_AUTH && !request.auth) {
throw new HttpsError(
"unauthenticated",
"You must be logged in to send notification."
);
}
const result = await sendFcmMessage(request.data || {});
return result;
} catch (error) {
if (error instanceof HttpsError) {
throw error;
}
throw new HttpsError(
"internal",
error?.message || "Failed to send notification."
);
}
}
);
async function sendFcmMessage(input) {
const target = input.target || {};
const token = target.token || input.token;
const tokens = target.tokens || input.tokens;
const topic = target.topic || input.topic;
const condition = target.condition || input.condition;
const title = input.title;
const body = input.body;
const data = input.data || {};
const androidChannelId = input.androidChannelId || "high_importance_channel";
if (!title || !body) {
throw new HttpsError(
"invalid-argument",
"title and body are required."
);
}
const safeData = stringifyData(data);
const baseMessage = {
notification: {
title: String(title),
body: String(body)
},
data: safeData,
android: {
priority: "high",
notification: {
channelId: String(androidChannelId),
sound: "default"
}
},
apns: {
payload: {
aps: {
sound: "default"
}
}
}
};
if (token) {
const response = await getMessaging().send({
...baseMessage,
token: String(token)
});
return {
success: true,
type: "token",
message: "Notification sent successfully.",
response
};
}
if (Array.isArray(tokens) && tokens.length > 0) {
const cleanTokens = tokens
.map((item) => String(item).trim())
.filter((item) => item.length > 0);
if (cleanTokens.length === 0) {
throw new HttpsError(
"invalid-argument",
"tokens list is empty."
);
}
if (cleanTokens.length > 500) {
throw new HttpsError(
"invalid-argument",
"Maximum 500 tokens are allowed per request."
);
}
const response = await getMessaging().sendEachForMulticast({
...baseMessage,
tokens: cleanTokens
});
return {
success: response.failureCount === 0,
type: "tokens",
message: "Multicast notification request completed.",
successCount: response.successCount,
failureCount: response.failureCount,
responses: response.responses.map((item, index) => ({
token: cleanTokens[index],
success: item.success,
messageId: item.messageId || null,
error: item.error?.message || null
}))
};
}
if (topic) {
const response = await getMessaging().send({
...baseMessage,
topic: normalizeTopic(String(topic))
});
return {
success: true,
type: "topic",
message: "Topic notification sent successfully.",
response
};
}
if (condition) {
const response = await getMessaging().send({
...baseMessage,
condition: String(condition)
});
return {
success: true,
type: "condition",
message: "Condition notification sent successfully.",
response
};
}
throw new HttpsError(
"invalid-argument",
"Provide token, tokens, topic or condition."
);
}
function stringifyData(data) {
const safeData = {};
Object.entries(data || {}).forEach(([key, value]) => {
if (value === null || value === undefined) {
safeData[key] = "";
} else if (typeof value === "object") {
safeData[key] = JSON.stringify(value);
} else {
safeData[key] = String(value);
}
});
return safeData;
}
function normalizeTopic(topic) {
return topic.trim().replace(/^\/topics\//, "");
}
๐ Deploy Cloud Function #
Run:
firebase deploy --only functions:sendNotification
๐จ Send Remote Push Notification to Token #
final result = await FirebaseNotificationHelper.sendRemoteNotification(
target: FcmTarget.token(receiverFcmToken),
title: "Hello!",
body: "This is sent using firebase_notification_helper.",
data: {
"screen": "chat",
"id": "123",
},
);
debugPrint(result.toString());
๐จ Send Remote Push Notification to Topic #
First subscribe the receiver device:
await FirebaseNotificationHelper.subscribeToTopic("all_users");
Then send:
final result = await FirebaseNotificationHelper.sendRemoteNotification(
target: const FcmTarget.topic("all_users"),
title: "New Update",
body: "A new update is available.",
data: {
"screen": "home",
},
);
debugPrint(result.toString());
๐จ Send Remote Push Notification to Multiple Tokens #
final result = await FirebaseNotificationHelper.sendRemoteNotification(
target: FcmTarget.tokens([
token1,
token2,
token3,
]),
title: "Group Notification",
body: "This notification was sent to multiple devices.",
data: {
"type": "group_message",
},
);
debugPrint(result.rawBody);
๐จ Send Remote Push Notification to Condition #
final result = await FirebaseNotificationHelper.sendRemoteNotification(
target: const FcmTarget.condition(
"'news' in topics || 'updates' in topics",
),
title: "Condition Notification",
body: "This was sent using a topic condition.",
data: {
"screen": "news",
},
);
debugPrint(result.rawBody);
๐งช Usage Example #
Import:
import 'package:flutter/material.dart';
import 'package:firebase_notification_helper/firebase_notification_helper.dart';
Then use this code:
import 'package:flutter/material.dart';
import 'package:firebase_notification_helper/firebase_notification_helper.dart';
class NotificationSenderPage extends StatefulWidget {
const NotificationSenderPage({super.key});
@override
State<NotificationSenderPage> createState() => _NotificationSenderPageState();
}
class _NotificationSenderPageState extends State<NotificationSenderPage> {
final TextEditingController tokenController = TextEditingController();
final TextEditingController topicController = TextEditingController(
text: "all_users",
);
final TextEditingController titleController = TextEditingController(
text: "Test Notification",
);
final TextEditingController bodyController = TextEditingController(
text: "Hello from firebase_notification_helper!",
);
bool sendToTopic = false;
bool loading = false;
String response = "";
@override
void initState() {
super.initState();
_loadToken();
}
Future<void> _loadToken() async {
final token = await FirebaseNotificationHelper.getToken();
if (!mounted) return;
setState(() {
tokenController.text = token ?? "";
});
}
Future<void> _sendNotification() async {
final title = titleController.text.trim();
final body = bodyController.text.trim();
if (title.isEmpty || body.isEmpty) {
setState(() {
response = "Title and body are required.";
});
return;
}
if (sendToTopic && topicController.text.trim().isEmpty) {
setState(() {
response = "Topic is required.";
});
return;
}
if (!sendToTopic && tokenController.text.trim().isEmpty) {
setState(() {
response = "Target token is required.";
});
return;
}
setState(() {
loading = true;
response = "";
});
try {
final result = await FirebaseNotificationHelper.sendRemoteNotification(
target: sendToTopic
? FcmTarget.topic(topicController.text.trim())
: FcmTarget.token(tokenController.text.trim()),
title: title,
body: body,
data: {
"screen": "home",
"source": "example_app",
},
);
if (!mounted) return;
setState(() {
response = result.rawBody;
});
} catch (e) {
if (!mounted) return;
setState(() {
response = e.toString();
});
} finally {
if (!mounted) return;
setState(() {
loading = false;
});
}
}
Future<void> _showLocalNotification() async {
await FirebaseNotificationHelper.showLocalNotification(
title: titleController.text.trim().isEmpty
? "Local Notification"
: titleController.text.trim(),
body: bodyController.text.trim().isEmpty
? "This is a local notification."
: bodyController.text.trim(),
payload: {
"type": "local",
"screen": "home",
},
);
}
Future<void> _subscribeTopic() async {
await FirebaseNotificationHelper.subscribeToTopic(
topicController.text.trim(),
);
setState(() {
response = "Subscribed to topic: ${topicController.text.trim()}";
});
}
@override
void dispose() {
tokenController.dispose();
topicController.dispose();
titleController.dispose();
bodyController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Notification Sender"),
),
floatingActionButton: FloatingActionButton(
onPressed: _showLocalNotification,
child: const Icon(Icons.notifications),
),
body: ListView(
padding: const EdgeInsets.all(16),
children: [
SwitchListTile(
value: sendToTopic,
title: const Text("Send to Topic"),
onChanged: (value) {
setState(() {
sendToTopic = value;
});
},
),
const SizedBox(height: 12),
if (sendToTopic)
TextField(
controller: topicController,
decoration: const InputDecoration(
labelText: "Topic Name",
border: OutlineInputBorder(),
),
)
else
TextField(
controller: tokenController,
maxLines: 4,
decoration: const InputDecoration(
labelText: "Target FCM Token",
border: OutlineInputBorder(),
),
),
const SizedBox(height: 12),
TextField(
controller: titleController,
decoration: const InputDecoration(
labelText: "Notification Title",
border: OutlineInputBorder(),
),
),
const SizedBox(height: 12),
TextField(
controller: bodyController,
maxLines: 2,
decoration: const InputDecoration(
labelText: "Notification Body",
border: OutlineInputBorder(),
),
),
const SizedBox(height: 12),
if (sendToTopic)
OutlinedButton.icon(
onPressed: _subscribeTopic,
icon: const Icon(Icons.add_alert),
label: const Text("Subscribe This Device To Topic"),
),
const SizedBox(height: 12),
ElevatedButton.icon(
onPressed: loading ? null : _sendNotification,
icon: loading
? const SizedBox(
width: 18,
height: 18,
child: CircularProgressIndicator(strokeWidth: 2),
)
: const Icon(Icons.send),
label: Text(loading ? "Sending..." : "Send Notification"),
),
const SizedBox(height: 16),
const Text(
"Response:",
style: TextStyle(fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
SelectableText(
response.isEmpty ? "No response yet." : response,
),
],
),
);
}
}
โถ๏ธ Example App Run Instructions #
Go to example folder:
cd example
Run:
flutter clean
flutter pub get
flutter run
Before running, make sure this file exists:
example/android/app/google-services.json
โ Common Issues #
google-services.json is missing #
Place the file here:
android/app/google-services.json
For example app:
example/android/app/google-services.json
cloud_functions and firebase_core version conflict #
Use compatible versions:
dependencies:
firebase_core: ^3.0.0
firebase_messaging: ^15.0.0
flutter_local_notifications: ^17.0.0
cloud_functions: ^5.6.2
http: ^1.2.0
Notification not showing in foreground #
Initialize with:
enableForegroundLocalNotification: true
Topic notification not received #
Subscribe the receiver device first:
await FirebaseNotificationHelper.subscribeToTopic("all_users");
๐ API Overview #
FirebaseNotificationHelper.initialize();
FirebaseNotificationHelper.getToken();
FirebaseNotificationHelper.deleteToken();
FirebaseNotificationHelper.requestPermission();
FirebaseNotificationHelper.showLocalNotification(
title: "Title",
body: "Body",
);
FirebaseNotificationHelper.subscribeToTopic("all_users");
FirebaseNotificationHelper.unsubscribeFromTopic("all_users");
FirebaseNotificationHelper.sendRemoteNotification(
target: FcmTarget.token(token),
title: "Title",
body: "Body",
);
๐ License #
MIT License
๐จโ๐ป Author #
Developed by Nafim Ahmed.