Overview
Trackmart uses Firebase Cloud Messaging (FCM) combined with Flutter Local Notifications to keep users informed about order status, driver location updates, and delivery notifications in real-time.
Push notifications require Firebase setup to be complete. Ensure you’ve followed the Firebase Setup guide before proceeding.
Why Push Notifications?
Notifications are critical for Trackmart’s user experience:
Order Updates Notify buyers when drivers accept or complete orders
Driver Alerts Alert drivers about new delivery requests
Real-time Updates Keep users informed of delivery status changes
Chat Messages Notify users of new messages from drivers or buyers
Dependencies
Trackmart uses two packages for notifications:
dependencies :
firebase_messaging : 5.1.2
flutter_local_notifications : 0.8.0
firebase_messaging : Handles push notifications from Firebase Cloud Messaging
flutter_local_notifications : Displays notifications when app is in foreground
Step 1: Enable Firebase Cloud Messaging
Navigate to Cloud Messaging
Go to Project Settings > Cloud Messaging tab.
Note your Server Key
Copy the Server key - you’ll need this for sending notifications from your backend.
Enable Cloud Messaging API
Ensure the Cloud Messaging API is enabled in the Google Cloud Console.
Update AndroidManifest.xml
Add to android/app/src/main/AndroidManifest.xml: android/app/src/main/AndroidManifest.xml
< manifest >
< application >
<!-- FCM notification icon -->
< meta-data
android:name = "com.google.firebase.messaging.default_notification_icon"
android:resource = "@drawable/notification_icon" />
<!-- FCM notification color -->
< meta-data
android:name = "com.google.firebase.messaging.default_notification_color"
android:resource = "@color/colorAccent" />
<!-- FCM notification channel -->
< meta-data
android:name = "com.google.firebase.messaging.default_notification_channel_id"
android:value = "trackmart_channel" />
</ application >
<!-- Notification permissions -->
< uses-permission android:name = "android.permission.INTERNET" />
< uses-permission android:name = "android.permission.VIBRATE" />
< uses-permission android:name = "android.permission.WAKE_LOCK" />
</ manifest >
Create notification icon
Add a notification icon to android/app/src/main/res/drawable/:
Create notification_icon.png (white icon on transparent background)
Recommended size: 24x24dp for different screen densities
Define notification color
Add to android/app/src/main/res/values/colors.xml: <? xml version = "1.0" encoding = "utf-8" ?>
< resources >
< color name = "colorAccent" > #005B9A </ color >
</ resources >
Android 8.0 (API 26+) requires notification channels. Trackmart uses trackmart_channel as the default channel ID.
Enable Push Notifications capability
Open ios/Runner.xcworkspace in Xcode:
Select Runner project
Go to Signing & Capabilities tab
Click + Capability
Add Push Notifications
Enable Background Modes
In the same Signing & Capabilities tab:
Add Background Modes capability
Enable:
☑️ Remote notifications
☑️ Background fetch
Configure APNs Authentication
In the Firebase Console:
Go to Project Settings > Cloud Messaging
Under iOS app configuration , upload your APNs authentication key or certificate
Follow the instructions to generate an APNs key from Apple Developer Portal
Update Info.plist
Add to ios/Runner/Info.plist if not already present: < key > UIBackgroundModes </ key >
< array >
< string > fetch </ string >
< string > remote-notification </ string >
</ array >
Step 4: Initialize Firebase Messaging
Implement Firebase Messaging in your app’s main initialization:
import 'package:firebase_messaging/firebase_messaging.dart' ;
import 'package:flutter_local_notifications/flutter_local_notifications.dart' ;
class MyApp extends StatefulWidget {
@override
_MyAppState createState () => _MyAppState ();
}
class _MyAppState extends State < MyApp > {
final FirebaseMessaging _firebaseMessaging = FirebaseMessaging ();
final FlutterLocalNotificationsPlugin _localNotifications =
FlutterLocalNotificationsPlugin ();
@override
void initState () {
super . initState ();
_configureNotifications ();
}
void _configureNotifications () async {
// Initialize local notifications
const androidSettings = AndroidInitializationSettings ( '@drawable/notification_icon' );
const iosSettings = IOSInitializationSettings (
requestSoundPermission : true ,
requestBadgePermission : true ,
requestAlertPermission : true ,
);
const initSettings = InitializationSettings (
android : androidSettings,
iOS : iosSettings,
);
await _localNotifications. initialize (
initSettings,
onSelectNotification : _onNotificationTapped,
);
// Configure Firebase Messaging
_firebaseMessaging. configure (
onMessage : ( Map < String , dynamic > message) async {
print ( "onMessage: $ message " );
_showLocalNotification (message);
},
onLaunch : ( Map < String , dynamic > message) async {
print ( "onLaunch: $ message " );
_handleNotificationTap (message);
},
onResume : ( Map < String , dynamic > message) async {
print ( "onResume: $ message " );
_handleNotificationTap (message);
},
);
// Request iOS permissions
_firebaseMessaging. requestNotificationPermissions (
const IosNotificationSettings (
sound : true ,
badge : true ,
alert : true ,
),
);
// Get FCM token
String token = await _firebaseMessaging. getToken ();
print ( "FCM Token: $ token " );
// Save token to Firebase for this user
_saveTokenToDatabase (token);
}
}
Step 5: Handle Notifications
Implement notification handling based on app state:
Foreground
Background
Terminated
When app is open and active: void _showLocalNotification ( Map < String , dynamic > message) async {
// Extract notification data
final notification = message[ 'notification' ];
final data = message[ 'data' ];
// Android notification details
const androidDetails = AndroidNotificationDetails (
'trackmart_channel' ,
'Trackmart Notifications' ,
'Notifications for order updates and messages' ,
importance : Importance .high,
priority : Priority .high,
icon : '@drawable/notification_icon' ,
);
// iOS notification details
const iosDetails = IOSNotificationDetails ();
// Combined platform details
const platformDetails = NotificationDetails (
android : androidDetails,
iOS : iosDetails,
);
// Show notification
await _localNotifications. show (
notification.hashCode,
notification[ 'title' ],
notification[ 'body' ],
platformDetails,
payload : json. encode (data),
);
}
When app is in background: // Background message handler (must be top-level function)
Future < dynamic > backgroundMessageHandler ( Map < String , dynamic > message) async {
print ( "Background message: $ message " );
// Process notification data
if (message. containsKey ( 'data' )) {
final data = message[ 'data' ];
// Handle background data processing
}
return Future < void >. value ();
}
// Register handler in main.dart
void main () {
FirebaseMessaging . onBackgroundMessage (backgroundMessageHandler);
runApp ( MyApp ());
}
When app was closed: void _handleNotificationTap ( Map < String , dynamic > message) {
final data = message[ 'data' ];
// Navigate based on notification type
if (data[ 'type' ] == 'order_update' ) {
Navigator . pushNamed (
context,
'/order_details' ,
arguments : { 'orderId' : data[ 'orderId' ]},
);
} else if (data[ 'type' ] == 'new_message' ) {
Navigator . pushNamed (
context,
'/chat' ,
arguments : { 'chatId' : data[ 'chatId' ]},
);
}
}
Step 6: Save FCM Token
Store the device FCM token in Firebase for targeting notifications:
void _saveTokenToDatabase ( String token) async {
// Get current user ID
final userId = await SharedPreferences . getInstance ()
. then ((prefs) => prefs. getString ( 'id' ));
if (userId != null ) {
// Save to Realtime Database
await FirebaseDatabase .instance
. reference ()
. child ( 'users' )
. child (userId)
. update ({
'fcmToken' : token,
'platform' : Platform .isIOS ? 'ios' : 'android' ,
'tokenUpdatedAt' : ServerValue .timestamp,
});
// Also save to Firestore for querying
await Firestore .instance
. collection ( 'users' )
. document (userId)
. updateData ({
'fcmToken' : token,
'platform' : Platform .isIOS ? 'ios' : 'android' ,
});
}
}
Tokens should be refreshed when they change. Listen to onTokenRefresh to update the database.
Step 7: Send Notifications
Notifications can be sent from:
Firebase Console
Backend Server
Cloud Functions
Manual notifications for testing:
Open Cloud Messaging
Go to Firebase Console > Cloud Messaging > Send your first message
Compose notification
Notification title : “Order Accepted”
Notification text : “Your driver is on the way!”
Target users
Choose:
User segment : All users
Topic : Specific topic subscribers
Single device : Test with your FCM token
Send notification
Click Review and Publish to send.
Programmatic notifications using FCM HTTP API: server.js (Node.js example)
const admin = require ( 'firebase-admin' );
// Initialize Firebase Admin SDK
admin . initializeApp ({
credential: admin . credential . cert ( serviceAccount ),
});
// Send notification to specific user
async function sendOrderNotification ( userId , orderId , driverName ) {
// Get user's FCM token from database
const userDoc = await admin . firestore ()
. collection ( 'users' )
. doc ( userId )
. get ();
const fcmToken = userDoc . data (). fcmToken ;
// Prepare notification
const message = {
notification: {
title: 'Order Accepted' ,
body: ` ${ driverName } has accepted your order` ,
},
data: {
type: 'order_update' ,
orderId: orderId ,
status: 'accepted' ,
},
token: fcmToken ,
};
// Send notification
const response = await admin . messaging (). send ( message );
console . log ( 'Successfully sent:' , response );
}
Automatic notifications using Firebase Cloud Functions: const functions = require ( 'firebase-functions' );
const admin = require ( 'firebase-admin' );
admin . initializeApp ();
// Trigger when order status changes
exports . sendOrderNotification = functions . database
. ref ( '/buyers/{buyerId}/requests/{orderId}' )
. onUpdate ( async ( change , context ) => {
const before = change . before . val ();
const after = change . after . val ();
// Only send if status changed
if ( before . status !== after . status ) {
const buyerId = context . params . buyerId ;
// Get buyer's FCM token
const userSnapshot = await admin . database ()
. ref ( `users/ ${ buyerId } /fcmToken` )
. once ( 'value' );
const fcmToken = userSnapshot . val ();
if ( fcmToken ) {
const message = {
notification: {
title: 'Order Update' ,
body: `Your order is now ${ after . status } ` ,
},
data: {
type: 'order_update' ,
orderId: context . params . orderId ,
status: after . status ,
},
token: fcmToken ,
};
await admin . messaging (). send ( message );
}
}
});
Notification Types
Trackmart supports various notification types:
// Order accepted
{
"notification" : {
"title" : "Order Accepted" ,
"body" : "Your driver is on the way!"
},
"data" : {
"type" : "order_update" ,
"orderId" : "O:1234567890" ,
"status" : "accepted" ,
"driverId" : "driver123"
}
}
// Order in transit
{
"notification" : {
"title" : "Order In Transit" ,
"body" : "Your order is being delivered"
},
"data" : {
"type" : "order_update" ,
"orderId" : "O:1234567890" ,
"status" : "in_transit"
}
}
// Order delivered
{
"notification" : {
"title" : "Order Delivered" ,
"body" : "Your order has been delivered successfully"
},
"data" : {
"type" : "order_update" ,
"orderId" : "O:1234567890" ,
"status" : "delivered"
}
}
// New delivery request
{
"notification" : {
"title" : "New Delivery Request" ,
"body" : "You have a new delivery request nearby"
},
"data" : {
"type" : "new_request" ,
"orderId" : "O:1234567890" ,
"buyerId" : "buyer123" ,
"distance" : "2.5km"
}
}
// New message
{
"notification" : {
"title" : "Message from John" ,
"body" : "When will you arrive?"
},
"data" : {
"type" : "new_message" ,
"chatId" : "chat123" ,
"senderId" : "user456" ,
"senderName" : "John"
}
}
Testing Notifications
Get FCM token
Run the app and copy the FCM token printed in the console:
Send test notification
Use the Firebase Console or a tool like Postman: curl -X POST https://fcm.googleapis.com/fcm/send \
-H "Authorization: key=YOUR_SERVER_KEY" \
-H "Content-Type: application/json" \
-d '{
"to": "YOUR_FCM_TOKEN",
"notification": {
"title": "Test Notification",
"body": "This is a test from Trackmart"
},
"data": {
"type": "test"
}
}'
Verify delivery
Check that:
Notification appears when app is in foreground
System tray shows notification when app is backgrounded
Tapping notification opens the app correctly
Notification Channels (Android 8+)
Create notification channels for better user control:
Future < void > createNotificationChannels () async {
final android = _localNotifications.resolvePlatformSpecificImplementation <
AndroidFlutterLocalNotificationsPlugin > ();
if (android != null ) {
// Orders channel
await android. createNotificationChannel (
const AndroidNotificationChannel (
'trackmart_orders' ,
'Order Updates' ,
'Notifications about your order status' ,
importance : Importance .high,
),
);
// Messages channel
await android. createNotificationChannel (
const AndroidNotificationChannel (
'trackmart_messages' ,
'Messages' ,
'New messages from drivers and buyers' ,
importance : Importance .high,
),
);
// General channel
await android. createNotificationChannel (
const AndroidNotificationChannel (
'trackmart_general' ,
'General' ,
'General app notifications' ,
importance : Importance .defaultImportance,
),
);
}
}
Troubleshooting
Notifications not received
Verify FCM is enabled in Firebase Console
Check that the app has notification permissions
Ensure FCM token is saved correctly in database
Verify APNs certificate/key is uploaded (iOS)
Check device internet connectivity
iOS notifications not working
Ensure Push Notifications capability is enabled in Xcode
Verify APNs authentication key is uploaded to Firebase
Check that requestNotificationPermissions() is called
Test on a physical device (simulator has limited support)
Verify bundle ID matches in Firebase and Xcode
Foreground notifications not showing
Ensure flutter_local_notifications is configured
Check that _showLocalNotification() is called in onMessage
Verify notification channel is created (Android)
Check notification permissions in app settings
Notification tap not working
Ensure onSelectNotification callback is set
Verify payload data is properly JSON encoded
Check navigation routes are registered
Test with different app states (foreground, background, terminated)
Best Practices
Token Management
Refresh tokens when they change
Store tokens with user profiles
Handle token errors gracefully
Clean up tokens on logout
User Experience
Keep notification text concise
Use meaningful titles
Include action buttons when relevant
Don’t spam users with notifications
Data Payload
Include navigation data in payload
Keep data payload small (under 4KB)
Use data-only messages for background processing
Include timestamps for time-sensitive data
Testing
Test all app states (foreground, background, terminated)
Verify on both iOS and Android
Test notification permissions flow
Monitor delivery rates in Firebase Console
Next Steps
Firebase Setup Review Firebase configuration for notifications
Mapbox Integration Add location-based notification triggers