|

Flutter Firebase Push Notifications Complete Guide

Flutter Firebase Push Notifications
Flutter Firebase Push Notifications

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

  1. Go to Firebase Console
  2. Create a new project or select an existing one
  3. 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

  1. Open ios/Runner.xcworkspace in Xcode
  2. Select your project target
  3. Go to “Signing & Capabilities”
  4. Click “+” and add “Push Notifications”
  5. 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

  1. Go to Firebase Console > Cloud Messaging
  2. Click “Send your first message”
  3. Enter title and message
  4. Select your app
  5. 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

  1. Notifications not received on iOS
    • Ensure you have added Push Notifications capability
    • Check that your APNs certificate is properly configured in Firebase Console
  2. Notifications not working in release mode
    • Make sure you’ve configured the background message handler
    • Check that the app has notification permissions
  3. Token is null
    • Ensure Firebase is properly initialized
    • Check internet connection
    • Verify Firebase configuration files are correct
  4. 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.

Leave a Reply

Your email address will not be published. Required fields are marked *