Flutter NotifySphere (flutter_notify_sphere)
Official Flutter SDK for NotifySphere. Easily monitor and track push notification delivery confirmation, click rates, and subscriber metadata across Android and iOS in real-time.
Features
- 🚀 Accurate Delivery Tracking: Confirms delivery when notifications arrive in the foreground, background isolate, or terminated state (via iOS Notification Service Extension).
- 🖱️ Click & Open Analytics: Tracks user clicks across foreground heads-up notifications, background resumes, and cold starts from terminated state.
- 📱 Device & Subscriber Synchronization: Automatically collects and updates FCM/APNs tokens, device model, OS version, app version, network connectivity type, timezone, and optional geolocation.
- 🖼️ Rich Notifications: Native support for expandable images (Android
BigPictureStyleand iOSUNNotificationAttachment). - 🔔 Custom Notification Sounds: Custom notification channels and sounds supported on both platforms.
- 🔄 Offline Pending Queue: Delivery and click events received while offline or before initial registration are queued securely and flushed automatically once registration completes.
Architecture Flow
flowchart TD
subgraph Client ["Client Application (Flutter & Native iOS)"]
Init["FlutterNotifySphere.initialize()"]
Reg["FlutterNotifySphere.registerUser()"]
FG["Foreground: FirebaseMessaging.onMessage"]
BG["Background: notifyBackgroundHandler"]
Cold["Cold Start: getInitialMessage()"]
Ext["iOS Service Extension: NotifySphereExtension.didReceive()"]
end
subgraph Backend ["NotifySphere Panel (notifysphere.net)"]
API_Reg["POST /client/{appId}/users"]
API_Deliv["POST /client/{appId}/delivery/confirm"]
API_Click["POST /client/{appId}/onpress/handle"]
end
Reg -->|Device Metadata + FCM Token| API_Reg
FG -->|Delivery Confirmation| API_Deliv
BG -->|Background Delivery Confirmation| API_Deliv
Ext -->|Silent Delivery Confirmation| API_Deliv
Cold -->|Click Event Tracking| API_Click
1. NotifySphere Panel Setup (notifysphere.net)
- Sign Up / Log In:
- Go to https://notifysphere.net and access your dashboard.
- Create an Application:
- Navigate to Applications > Create New Application.
- Enter your application name and package/bundle ID.
- Note down your Application ID (
appId) and API Key (apiKey).
- Configure Firebase Cloud Messaging (FCM):
- In the Firebase Console, open your project settings > Service Accounts.
- Click Generate new private key and download the JSON file.
- In the NotifySphere dashboard, open your Application Settings and upload the Firebase Service Account JSON.
- Configure Apple Push Notification service (APNs) (for iOS):
- In Firebase Console > Project Settings > Cloud Messaging, upload your Apple Push Notifications Authentication Key (
.p8) and team ID.
- In Firebase Console > Project Settings > Cloud Messaging, upload your Apple Push Notifications Authentication Key (
2. Installation
Add flutter_notify_sphere along with Firebase dependencies to your pubspec.yaml:
dependencies:
flutter:
sdk: flutter
flutter_notify_sphere: ^1.0.0
firebase_core: ^4.0.0
firebase_messaging: ^16.0.0
Run:
flutter pub get
3. Android Setup
3.1 Firebase Configuration
- Download
google-services.jsonfrom your Firebase project. - Place it into
android/app/google-services.json. - Add the Google Services plugin to your Gradle configuration:
- In
android/build.gradle(orandroid/settings.gradle):plugins { id 'com.google.gms.google-services' version '4.4.2' apply false } - In
android/app/build.gradle:plugins { id 'com.android.application' id 'com.google.gms.google-services' }
- In
3.2 Permissions (AndroidManifest.xml)
In android/app/src/main/AndroidManifest.xml, add the following permissions inside the <manifest> tag:
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Required permissions -->
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<uses-permission android:name="android.permission.VIBRATE"/>
<!-- Optional: Only if useLocationData: true is passed to registerUser() -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<application ...>
<!-- Optional default notification icon & color -->
<meta-data
android:name="com.google.firebase.messaging.default_notification_icon"
android:resource="@mipmap/ic_launcher" />
<meta-data
android:name="com.google.firebase.messaging.default_notification_channel_id"
android:value="notify_sphere_channel" />
...
</application>
</manifest>
3.3 Custom Notification Sound (Android)
- Place your audio file (e.g.
custom_sound.mp3or.wav) into:android/app/src/main/res/raw/custom_sound.mp3 - When creating a notification in the NotifySphere dashboard, enter
custom_sound(without the file extension) in the sound field.
3.4 Rich Images (Android)
Images sent in the notification (imageUrl or image in data payload) are automatically downloaded and displayed as BigPictureStyle notifications.
4. iOS Setup
4.1 Firebase Configuration
- Download
GoogleService-Info.plistfrom your Firebase Console. - Open your project in Xcode (
open ios/Runner.xcworkspace). - Drag and drop
GoogleService-Info.plistinto theRunnerfolder (ensure Copy items if needed is checked).
4.2 Xcode Capabilities
In Xcode, select the Runner target > Signing & Capabilities:
- Click + Capability and add Push Notifications.
- Click + Capability and add Background Modes. Check:
- ✅ Remote notifications
- ✅ Background fetch
- Click + Capability and add App Groups:
- Add an App Group named:
group.<your.bundle.id>(e.g.group.com.example.myapp).
- Add an App Group named:
4.3 iOS Notification Service Extension (Background Delivery & Image Tracking)
Important
iOS does not execute Flutter/Dart code when the app is in the background or terminated upon receiving a notification. To track delivery confirmations and show rich image attachments when the app is closed, you must configure an iOS Notification Service Extension.
Step 1: Create the Extension Target
- In Xcode, choose File > New > Target....
- Select Notification Service Extension and click Next.
- Name the extension (e.g.
ImageNotification), ensure language is Swift, and click Finish. - When prompted to activate scheme, click Cancel.
Step 2: Configure the App Group on the Extension
- Select your new
ImageNotificationtarget > Signing & Capabilities. - Click + Capability > App Groups.
- Check the exact same App Group you created for the Runner target (
group.<your.bundle.id>).
Step 3: Add NotifySphereExtension.swift
Inside your extension folder (e.g. ios/ImageNotification/), create a new Swift file named NotifySphereExtension.swift and paste the following code:
import Foundation
import UserNotifications
import os.log
public class NotifySphereExtension {
/// Called inside the Notification Service Extension to confirm delivery with NotifySphere
public static func didReceive(_ request: UNNotificationRequest, completion: @escaping () -> Void) {
let userInfo = request.content.userInfo
// Extract required notification identifier
guard let notificationId = userInfo["notification_id"] as? String ?? userInfo["gcm.message_id"] as? String else {
completion()
return
}
// Dynamically resolve the parent App Group suite
guard let extensionBundleId = Bundle.main.bundleIdentifier else {
completion()
return
}
let components = extensionBundleId.components(separatedBy: ".")
guard components.count >= 2 else {
completion()
return
}
let parentBundleId = components.dropLast().joined(separator: ".")
let dynamicAppGroup = "group.\(parentBundleId)"
guard let groupDefaults = UserDefaults(suiteName: dynamicAppGroup) else {
completion()
return
}
// Retrieve credentials saved by flutter_notify_sphere
let appId = groupDefaults.string(forKey: "app_id") ?? ""
let apiKey = groupDefaults.string(forKey: "api_key") ?? ""
let subscriptionId = groupDefaults.string(forKey: "subscription_id") ?? ""
guard !appId.isEmpty else {
completion()
return
}
// Construct delivery confirmation endpoint
let urlString = "https://api.notifysphere.in/client/\(appId)/delivery/confirm"
guard let url = URL(string: urlString) else {
completion()
return
}
var urlRequest = URLRequest(url: url)
urlRequest.httpMethod = "POST"
urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")
if !apiKey.isEmpty {
urlRequest.setValue("Bearer \(apiKey)", forHTTPHeaderField: "Authorization")
}
let body: [String: Any] = [
"notification_id": notificationId,
"subscription_id": subscriptionId,
"platform": "ios"
]
urlRequest.httpBody = try? JSONSerialization.data(withJSONObject: body)
let task = URLSession.shared.dataTask(with: urlRequest) { _, _, _ in
completion()
}
task.resume()
}
}
Step 4: Implement NotificationService.swift
In the same extension folder, replace the contents of NotificationService.swift with:
import UIKit
import UserNotifications
import os.log
class NotificationService: UNNotificationServiceExtension {
private var contentHandler: ((UNNotificationContent) -> Void)?
private var bestAttemptContent: UNMutableNotificationContent?
override func didReceive(
_ request: UNNotificationRequest,
withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void
) {
self.contentHandler = contentHandler
bestAttemptContent = request.content.mutableCopy() as? UNMutableNotificationContent
guard let bestAttemptContent = bestAttemptContent else {
contentHandler(request.content)
return
}
let userInfo = request.content.userInfo
// 1. Confirm delivery with NotifySphere
NotifySphereExtension.didReceive(request) {
NSLog("NotifySphere delivery confirmed in extension")
}
// 2. Custom Sound
if let aps = userInfo["aps"] as? [String: Any],
let soundName = aps["sound"] as? String {
bestAttemptContent.sound = UNNotificationSound(named: UNNotificationSoundName(rawValue: soundName))
} else if let soundName = userInfo["sound"] as? String {
bestAttemptContent.sound = UNNotificationSound(named: UNNotificationSoundName(rawValue: soundName))
} else {
bestAttemptContent.sound = .default
}
// 3. Expandable Image Attachment
if let imageUrl = userInfo["imageUrl"] as? String ?? userInfo["image"] as? String,
let url = URL(string: imageUrl) {
downloadImage(from: url) { attachment in
if let attachment = attachment {
bestAttemptContent.attachments = [attachment]
}
contentHandler(bestAttemptContent)
}
} else {
contentHandler(bestAttemptContent)
}
}
override func serviceExtensionTimeWillExpire() {
if let contentHandler = contentHandler,
let bestAttemptContent = bestAttemptContent {
contentHandler(bestAttemptContent)
}
}
private func downloadImage(from url: URL, completion: @escaping (UNNotificationAttachment?) -> Void) {
URLSession.shared.downloadTask(with: url) { location, _, _ in
guard let location = location else {
completion(nil)
return
}
let tempDir = URL(fileURLWithPath: NSTemporaryDirectory())
let fileURL = tempDir.appendingPathComponent(UUID().uuidString + ".jpg")
try? FileManager.default.moveItem(at: location, to: fileURL)
let attachment = try? UNNotificationAttachment(
identifier: UUID().uuidString,
url: fileURL,
options: nil
)
completion(attachment)
}.resume()
}
}
4.4 Custom Notification Sound (iOS)
- Add your audio file (e.g.
alert.caf,alert.wav, oralert.aiff) to Xcode. - In the file inspector, check both
RunnerandImageNotificationtarget memberships. - In NotifySphere dashboard, enter
alert.cafin the sound field.
5. Flutter Dart Integration
5.1 Initialize in main.dart
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/material.dart';
import 'package:flutter_notify_sphere/flutter_notify_sphere.dart';
/// Top-level background message handler
@pragma('vm:entry-point')
Future<void> backgroundMessageHandler(RemoteMessage message) async {
debugPrint('Background message received: ${message.data}');
}
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
// Initialize NotifySphere
await FlutterNotifySphere().initialize(
showForegroundNotification: true,
onForegroundMessage: (RemoteMessage message) {
debugPrint('Foreground notification: ${message.data}');
},
onNotificationClick: (RemoteMessage? message) {
debugPrint('Notification clicked: ${message?.data}');
// Handle navigation or in-app routing here
},
backgroundMessageHandler: backgroundMessageHandler,
);
runApp(const MyApp());
}
5.2 Register User & Device
Call registerUser when your user logs in or on your app's home screen:
await FlutterNotifySphere().registerUser(
appId: "YOUR_NOTIFYSPHERE_APP_ID", // From notifysphere.net
apiKey: "YOUR_NOTIFYSPHERE_API_KEY", // From notifysphere.net
userData: {
"applicationUserId": "user_12345",
"name": "Jane Doe",
"email": "jane@example.com",
// Any custom metadata you want to associate with this subscriber
},
useLocationData: false, // Set to true to include lat/long and reverse-geocoded address
onFcmToken: (String? fcmToken) {
debugPrint("Device FCM Token: $fcmToken");
},
onRegistrationComplete: (String? subscriptionId) {
debugPrint("Registered with NotifySphere! Subscription ID: $subscriptionId");
},
onError: (Object error) {
debugPrint("Registration error: $error");
},
);
5.3 Post Campaign Events
Track in-app conversions, goal events (e.g. booking, purchase, subscription, logout), and campaign attribution via campaign/event:
// The subscription_id is automatically attached from local storage
final success = await FlutterNotifySphere().postEvent(
event: "booking",
nsClick: "https://yourdomain.com/booking/123", // Optional: campaign deep link
data: {
"order_id": "ORD123",
"revenue": 2500,
"quantity": 2,
"tax": 450,
},
onSuccess: (responseData) {
debugPrint("Event tracked successfully: $responseData");
},
onError: (error) {
debugPrint("Failed to post event: $error");
},
);
5.4 Update User Tags
Update or add custom segmentation tags to a subscriber on the NotifySphere server via the users API:
// Only tags are passed from the app; device metadata, token, and subscription_id
// are automatically supplied by the package from local storage.
final success = await FlutterNotifySphere().updateTags(
tags: {
"userType": "anonymous",
},
onSuccess: (subscriptionId) {
debugPrint("Tags updated successfully for subscription: $subscriptionId");
},
onError: (error) {
debugPrint("Failed to update tags: $error");
},
);
6. NotifySphere Payload Reference
Notifications sent from the NotifySphere panel or REST API deliver the following data structure:
| Key | Type | Description |
|---|---|---|
notification_id |
String |
Unique tracking ID generated by NotifySphere for delivery and click tracking. |
title |
String |
The notification title. |
body |
String |
The notification body text. |
imageUrl |
String? |
Optional URL of the image attachment. |
sound |
String? |
Optional custom sound file name (e.g. notification_sound). |
custom_key |
dynamic |
Any additional key-value pairs configured in the NotifySphere campaign. |
7. Verifying Integration
- Registration: Run your application. Inspect debug console logs for:
Registration Complete: <subscription_id> - Delivery Confirmation: Send a test notification from the NotifySphere panel:
- Check the NotifySphere campaign dashboard: the Delivered count will increment in real time.
- Click Tracking: Tap the notification banner:
- The Clicked count will increment in the campaign dashboard.
- iOS Console Logs: On macOS, open Console.app, select your test device, and filter for
NotifySphereExtensionor categoryFlutterNotifySphere.
License
This project is licensed under the MIT License - see the LICENSE file for details.