
Prerequisites
- Flutter SDK installed
- Firebase CLI installed (
npm install -g firebase-tools
) - A Firebase project created in the Firebase Console
Step 1: Firebase Project Setup
- Go to Firebase Console
- Create a new project or select an existing one
- Enable Cloud Messaging in the Firebase Console:
- Go to Project Settings > Cloud Messaging
- Note down your Server Key and Sender ID
Step 2: Flutter Project Configuration
Add Dependencies
Add these packages to your pubspec.yaml
:
dependencies:
firebase_core: ^2.24.2
firebase_messaging: ^14.7.10
flutter_local_notifications: ^16.3.2
Run flutter pub get
after adding dependencies.
Initialize Firebase
Install FlutterFire CLI:
dart pub global activate flutterfire_cli
Configure Firebase for your Flutter project:
flutterfire configure
This will create firebase_options.dart
and configure both Android and iOS.
Step 3: Android Configuration
Update android/app/build.gradle
android {
compileSdkVersion 33 // or higher
defaultConfig {
minSdkVersion 19
targetSdkVersion 33
}
}
Add Permissions in android/app/src/main/AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Add these permissions -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<application>
<!-- Add this service for background messages -->
<service
android:name="io.flutter.plugins.firebase.messaging.FlutterFirebaseMessagingBackgroundService"
android:exported="false" />
<!-- Add this for notification icon -->
<meta-data
android:name="com.google.firebase.messaging.default_notification_icon"
android:resource="@drawable/ic_launcher" />
<!-- Add this for notification color -->
<meta-data
android:name="com.google.firebase.messaging.default_notification_color"
android:resource="@color/colorAccent" />
</application>
</manifest>
Step 4: iOS Configuration
Update ios/Runner/Info.plist
No additional configuration needed for basic setup.
Enable Push Notifications Capability
- Open
ios/Runner.xcworkspace
in Xcode - Select your project target
- Go to “Signing & Capabilities”
- Click “+” and add “Push Notifications”
- Add “Background Modes” and check “Background fetch” and “Remote notifications”
Step 5: Flutter Implementation
Main Application Setup
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/material.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'firebase_options.dart';
// Background message handler
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
await Firebase.initializeApp();
print('Handling a background message: ${message.messageId}');
}
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// Initialize Firebase
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
// Set background message handler
FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Firebase Push Notifications',
home: NotificationScreen(),
);
}
}
Notification Service Implementation
class NotificationService {
static final FlutterLocalNotificationsPlugin _localNotifications =
FlutterLocalNotificationsPlugin();
static final FirebaseMessaging _messaging = FirebaseMessaging.instance;
// Initialize notifications
static Future<void> initialize() async {
// Request permissions
NotificationSettings settings = await _messaging.requestPermission(
alert: true,
badge: true,
sound: true,
carPlay: false,
criticalAlert: false,
provisional: false,
announcement: false,
);
print('User granted permission: ${settings.authorizationStatus}');
// Initialize local notifications
const AndroidInitializationSettings initializationSettingsAndroid =
AndroidInitializationSettings('@mipmap/ic_launcher');
const DarwinInitializationSettings initializationSettingsIOS =
DarwinInitializationSettings(
requestSoundPermission: true,
requestBadgePermission: true,
requestAlertPermission: true,
);
const InitializationSettings initializationSettings =
InitializationSettings(
android: initializationSettingsAndroid,
iOS: initializationSettingsIOS,
);
await _localNotifications.initialize(
initializationSettings,
onDidReceiveNotificationResponse: onNotificationTapped,
);
// Get FCM token
String? token = await _messaging.getToken();
print('FCM Token: $token');
// Handle foreground messages
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
print('Received a message while in foreground!');
print('Message data: ${message.data}');
if (message.notification != null) {
print('Message also contained a notification: ${message.notification}');
_showLocalNotification(message);
}
});
// Handle notification taps when app is terminated
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
print('A new onMessageOpenedApp event was published!');
_handleNotificationTap(message);
});
// Check for initial message if app was opened from terminated state
RemoteMessage? initialMessage = await _messaging.getInitialMessage();
if (initialMessage != null) {
_handleNotificationTap(initialMessage);
}
}
// Show local notification
static Future<void> _showLocalNotification(RemoteMessage message) async {
const AndroidNotificationDetails androidPlatformChannelSpecifics =
AndroidNotificationDetails(
'high_importance_channel',
'High Importance Notifications',
channelDescription: 'This channel is used for important notifications.',
importance: Importance.max,
priority: Priority.high,
showWhen: false,
);
const DarwinNotificationDetails iOSPlatformChannelSpecifics =
DarwinNotificationDetails();
const NotificationDetails platformChannelSpecifics = NotificationDetails(
android: androidPlatformChannelSpecifics,
iOS: iOSPlatformChannelSpecifics,
);
await _localNotifications.show(
0,
message.notification?.title,
message.notification?.body,
platformChannelSpecifics,
payload: message.data.toString(),
);
}
// Handle notification tap
static void onNotificationTapped(NotificationResponse notificationResponse) {
print('Notification tapped: ${notificationResponse.payload}');
// Handle navigation or actions here
}
static void _handleNotificationTap(RemoteMessage message) {
print('Notification tapped with data: ${message.data}');
// Handle navigation based on message data
}
// Subscribe to topic
static Future<void> subscribeToTopic(String topic) async {
await _messaging.subscribeToTopic(topic);
print('Subscribed to topic: $topic');
}
// Unsubscribe from topic
static Future<void> unsubscribeFromTopic(String topic) async {
await _messaging.unsubscribeFromTopic(topic);
print('Unsubscribed from topic: $topic');
}
}
Sample Screen Implementation
class NotificationScreen extends StatefulWidget {
@override
_NotificationScreenState createState() => _NotificationScreenState();
}
class _NotificationScreenState extends State<NotificationScreen> {
String? _fcmToken;
@override
void initState() {
super.initState();
_initializeNotifications();
}
Future<void> _initializeNotifications() async {
await NotificationService.initialize();
// Get and display FCM token
_fcmToken = await FirebaseMessaging.instance.getToken();
setState(() {});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Firebase Push Notifications'),
),
body: Padding(
padding: EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'FCM Token:',
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16),
),
SizedBox(height: 8),
Container(
padding: EdgeInsets.all(8),
decoration: BoxDecoration(
border: Border.all(color: Colors.grey),
borderRadius: BorderRadius.circular(4),
),
child: SelectableText(
_fcmToken ?? 'Loading...',
style: TextStyle(fontSize: 12),
),
),
SizedBox(height: 20),
ElevatedButton(
onPressed: () => NotificationService.subscribeToTopic('general'),
child: Text('Subscribe to General Topic'),
),
ElevatedButton(
onPressed: () => NotificationService.unsubscribeFromTopic('general'),
child: Text('Unsubscribe from General Topic'),
),
],
),
),
);
}
}
Step 6: Testing Push Notifications
Method 1: Firebase Console
- Go to Firebase Console > Cloud Messaging
- Click “Send your first message”
- Enter title and message
- Select your app
- Send test message
Method 2: Using FCM HTTP API
curl -X POST https://fcm.googleapis.com/fcm/send \
-H "Authorization: key=YOUR_SERVER_KEY" \
-H "Content-Type: application/json" \
-d '{
"to": "FCM_TOKEN_HERE",
"notification": {
"title": "Test Notification",
"body": "This is a test message"
},
"data": {
"custom_key": "custom_value"
}
}'
Method 3: Topic-based messaging
curl -X POST https://fcm.googleapis.com/fcm/send \
-H "Authorization: key=YOUR_SERVER_KEY" \
-H "Content-Type: application/json" \
-d '{
"to": "/topics/general",
"notification": {
"title": "Topic Notification",
"body": "This message was sent to topic subscribers"
}
}'
Troubleshooting
Common Issues
- Notifications not received on iOS
- Ensure you have added Push Notifications capability
- Check that your APNs certificate is properly configured in Firebase Console
- Notifications not working in release mode
- Make sure you’ve configured the background message handler
- Check that the app has notification permissions
- Token is null
- Ensure Firebase is properly initialized
- Check internet connection
- Verify Firebase configuration files are correct
- Background notifications not working
- Ensure background message handler is set up correctly
- Check that the app has background refresh permissions
Testing Checklist
- [ ] App receives notifications when in foreground
- [ ] App receives notifications when in background
- [ ] App receives notifications when terminated
- [ ] Notification taps open the app correctly
- [ ] FCM token is generated successfully
- [ ] Topic subscription/unsubscription works
- [ ] Custom data is passed correctly
Additional Features
Custom Notification Channels (Android)
// Create custom notification channel
await _localNotifications
.resolvePlatformSpecificImplementation<AndroidFlutterLocalNotificationsPlugin>()
?.createNotificationChannel(
AndroidNotificationChannel(
'custom_channel',
'Custom Notifications',
description: 'Custom notification channel',
importance: Importance.high,
),
);
Scheduled Local Notifications
// Schedule a notification
await _localNotifications.zonedSchedule(
0,
'Scheduled Notification',
'This notification was scheduled',
tz.TZDateTime.now(tz.local).add(Duration(seconds: 10)),
platformChannelSpecifics,
uiLocalNotificationDateInterpretation:
UILocalNotificationDateInterpretation.absoluteTime,
);
This setup provides a complete foundation for Firebase push notifications in Flutter, including foreground, background, and terminated state handling.