Flutter Awesome Notification πŸ””

A comprehensive, production-ready notification plugin for Flutter apps with Firebase Cloud Messaging (FCM) and local notifications. Handles foreground, background and terminated notifications and navigation on notification tap.

✨ Features

  • βœ… Foreground Notification Handling: Intelligent foreground notification management
  • βœ… Intelligent Filtering: Action step, chat room, and custom notification filtering
  • βœ… Navigation Integration: Custom callbacks for navigation handling
  • βœ… Topic Subscriptions: Easy FCM topic management
  • βœ… Local Notifications: Immediate and scheduled local notifications
  • βœ… Highly Configurable: Builder pattern with sensible defaults
  • βœ… Minimal Code: Easy setup with very little boilerplate
  • βœ… FCM Token Management: Automatic token handling and refresh
  • βœ… Custom Logging: Integrate with your preferred logging solution
  • βœ… Type-Safe: Full TypeScript-style type safety

πŸš€ Quick Start

Installation

Add to your pubspec.yaml:

dependencies:
  flutter_awesome_notification: ^0.0.2

Basic Setup

import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter_awesome_notification/flutter_awesome_notification.dart';
import 'firebase_options.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  // Initialize the notification plugin BEFORE Firebase
  await FlutterAwesomeNotification.initialize(
    config: FlutterFlutterAwesomeNotificationConfig(
      firebaseOptions: DefaultFirebaseOptions.currentPlatform,
      mainChannelId: 'my_app_notifications',
      mainChannelName: 'My App Notifications',
      onNotificationTap: (data) {
        print('Notification tapped: $data');
      },
      onNavigate: (pageName, id, data) {
        print('Navigate to: $pageName with id: $id');
      },
    ),
  );

  // Initialize Firebase
  await Firebase.initializeApp(
    options: DefaultFirebaseOptions.currentPlatform,
  );

  runApp(MyApp());
}

That's it! You now have full notification support with just a few lines of code.

πŸ“– Configuration

Complete Configuration Example

await FlutterAwesomeNotification.initialize(
  config: FlutterFlutterAwesomeNotificationConfig(
    // REQUIRED
    firebaseOptions: DefaultFirebaseOptions.currentPlatform,

    // Channel Configuration
    mainChannelId: 'my_app_channel',
    mainChannelName: 'My App Notifications',
    mainChannelDescription: 'General notifications',
    notificationIcon: '@mipmap/ic_launcher',

    // Callbacks
    onNotificationTap: (data) {
      // Handle notification tap
      print('Tapped: $data');
    },
    onNavigate: (pageName, id, data) {
      // Custom navigation
      // Example: GoRouter.of(context).push('/$pageName/$id');
    },
    getCurrentUserId: () {
      // Return current user ID for filtering
      return userCubit.getUserModel()?.id;
    },
    customFilter: (messageData) async {
      // Custom filtering logic
      // Return true to show, false to hide
      return true;
    },
    
    // Logging Options
    
    // Option 1: External logger (recommended - unified logging)
    // Compatible with flutter_awesome_logger and other logging solutions
    externalLogger: logger, // Your logger instance with d(), i(), w(), e() methods
    
    // Option 2: Logger callback (legacy support)
    // logger: (message, {error}) {
    //   myLogger.log(message, error: error);
    // },

    // Filtering Options
    enableActionStepFiltering: true,
    enableChatRoomFiltering: true,
    isActiveChatRoom: (chatRoomId) {
      // Check if user is currently in this chat room
      return appCubit.isActiveChatRoom(chatRoomId);
    },
    chatPageRoute: 'chat-page',

    // Notification Types
    notificationTypeToPage: {
      'action_step': 'challenge-details',
      'chat_message': 'chat-room',
      'event_reminder': 'event-details',
    },

    // Advanced
    enableLogging: true,
    requestPermissionOnInit: true,
    showAlertInForeground: true,
    showBadgeInForeground: true,
    playSoundInForeground: true,
    defaultNotificationTitle: 'New Update',
    defaultNotificationBody: 'You have a new update',
    environment: 'production',
  ),
);

Minimal Configuration

For a basic setup, only Firebase options are required:

await FlutterAwesomeNotification.initialize(
  config: FlutterFlutterAwesomeNotificationConfig(
    firebaseOptions: DefaultFirebaseOptions.currentPlatform,
  ),
);

🎯 Usage

Access the Service

final notificationService = FlutterAwesomeNotification.instance;

Topic Subscriptions

// Subscribe to a topic
await notificationService.subscribeToTopic('announcements');

// Unsubscribe from a topic
await notificationService.unsubscribeFromTopic('announcements');

Get FCM Token

final token = await notificationService.getDeviceToken();
print('FCM Token: $token');

Show Local Notification

await notificationService.showLocalNotification(
  id: 123,
  title: 'Hello!',
  body: 'This is a local notification',
  data: {'key': 'value'},
);

Schedule Notification

await notificationService.scheduleNotification(
  id: 124,
  title: 'Reminder',
  body: 'Don\'t forget to check this!',
  scheduledDate: DateTime.now().add(Duration(hours: 2)),
  data: {'reminder_type': 'task'},
);

Cancel Notifications

// Cancel specific notification
await notificationService.cancelNotification(123);

// Cancel all notifications
await notificationService.cancelAllNotifications();

Check Notification Permissions

final enabled = await notificationService.areNotificationsEnabled();
if (!enabled) {
  await notificationService.requestPermissions();
}

Unified Logging with External Logger

The plugin supports external logger instances for unified logging across your app:

// 1. Create or use your existing logger instance
class MyLogger {
  void d(String message) => print('πŸ” DEBUG: $message');
  void i(String message) => print('ℹ️ INFO: $message');
  void w(String message) => print('⚠️ WARNING: $message');
  void e(String message, {dynamic error, StackTrace? stackTrace}) {
    print('❌ ERROR: $message');
    if (error != null) print('Error: $error');
  }
}

final logger = MyLogger();

// 2. Pass it to the plugin during initialization
await FlutterAwesomeNotification.initialize(
  config: FlutterAwesomeNotificationConfig(
    firebaseOptions: DefaultFirebaseOptions.currentPlatform,
    enableLogging: true,
    externalLogger: logger, // 🎯 Your logger instance
  ),
);

Benefits:

  • βœ… Unified logging across all plugins (deeplink, notification, etc.)
  • βœ… Compatible with flutter_awesome_logger and other logging solutions
  • βœ… Consistent log format and filtering
  • βœ… No need for custom callbacks

Supported Log Levels:

  • d() - Debug messages (initialization, state changes)
  • i() - Info messages (successful operations)
  • w() - Warning messages (non-critical issues)
  • e() - Error messages (failures, exceptions)

πŸ” How It Works

App State Behavior

Foreground (App Open & Visible)

  1. FCM Message Received β†’ FirebaseMessaging.onMessage stream
  2. Custom Filtering Applied β†’ Action steps, chat rooms, user filtering
  3. Local Notification Shown β†’ Via flutter_local_notifications plugin
  4. Tap Navigation β†’ onNavigate callback with pageName and id

Background (App Minimized)

  1. FCM Message Received β†’ System notification (if notification field present)
  2. No Custom Filtering β†’ Plugin doesn't run in background
  3. User Taps Notification β†’ FirebaseMessaging.onMessageOpenedApp triggers
  4. Navigation on App Open β†’ Same onNavigate callback as foreground

Terminated (App Closed)

  1. FCM Message Received β†’ System notification (if notification field present)
  2. No Custom Filtering β†’ App not running
  3. User Taps Notification β†’ Cold app launch with initial message
  4. Navigation on Launch β†’ FirebaseMessaging.getInitialMessage() β†’ onNavigate

Key Differences by App State

Feature Foreground Background Terminated
Custom Filtering βœ… Full ❌ None ❌ None
Notification Display βœ… Plugin βœ… System βœ… System
Navigation βœ… Immediate βœ… On tap βœ… On launch
Plugin Processing βœ… Active ❌ Dormant ❌ Dormant

FCM Payload Requirements

For Background/Terminated delivery:

{
  "notification": {
    "title": "New Message",
    "body": "You have a new message"
  },
  "data": {
    "pageName": "chat-room",
    "id": "room123",
    "type": "message"
  }
}

⚠️ Data-only payloads won't show in background/terminated:

// ❌ Won't show in background/terminated
{
  "data": {
    "pageName": "chat-room",
    "id": "room123"
  }
}

Filtering System

The plugin provides multiple layers of filtering:

  1. Action Step Filtering: Prevents users from seeing their own action notifications
  2. Chat Room Filtering: Hides notifications when user is in the chat room
  3. Custom Filtering: Your own logic via callback
  4. Type Filtering: Filter by notification type

πŸ”§ Migration from Existing Service

If you're using the notification_service/ from challenge_app, here's how to migrate:

Before (Old Code)

// In main.dart
NotificationService.registerBackgroundMessageHandler();
await Firebase.initializeApp();

// In your app
await getIt<NotificationService>().initialize();

After (Plugin)

// In main.dart
await FlutterAwesomeNotification.initialize(
  config: FlutterAwesomeNotificationConfig(
    firebaseOptions: DefaultFirebaseOptions.currentPlatform,
    onNotificationTap: (data) => AutoNavigation.handleNotificationTap(data),
    getCurrentUserId: () => getIt<UserCubit>().getUserModel()?.id,
    isActiveChatRoom: (id) => getIt<MyAppCubit>().isActiveChatRoom(id),
    chatPageRoute: RouteNames.chatPage,
    logger: (msg, {error}) => logger.d(msg, error: error),
  ),
);
await Firebase.initializeApp();

// That's it! No need for separate initialization

Example: my_bottom_nav_bar.dart

Future<void> _initializeServices() async {
  logger.i('MyBottomNavBar: Initializing services');
  
  try {
    final notificationService = FlutterAwesomeNotification.instance;

    // Optional: Subscribe to topics
    // await notificationService.subscribeToTopic('challenges');
    
    logger.i('MyBottomNavBar: Services initialized');
  } catch (e) {
    logger.e('MyBottomNavBar: Error initializing services', error: e);
  }
}

πŸ“± Server-Side Configuration

FCM Message Format

For proper filtering, send data-only messages:

{
  "data": {
    "type": "action_step_completion",
    "excludeUserId": "user123",
    "challengeId": "challenge456",
    "pageName": "challenge-details",
    "id": "challenge456",
    "title": "Challenge Update",
    "body": "Someone completed a step!"
  },
  "token": "fcm_device_token"
}

πŸ› Troubleshooting

Notifications Not Showing

Foreground Issues:

  1. Check if permissions are granted:
    final enabled = await notificationService.areNotificationsEnabled();
    
  2. Ensure plugin is initialized before Firebase initialization
  3. Check if custom filters are blocking notifications

Background/Terminated Issues:

  1. Critical: FCM payload must include notification field:
    {
      "notification": {"title": "Title", "body": "Body"}, // REQUIRED
      "data": {"pageName": "route"}
    }
    
  2. Data-only payloads won't show in background/terminated states
  3. Custom filtering doesn't work in background/terminated
  1. Verify onNavigate callback is set
  2. Ensure pageName is in notification data
  3. Check navigation implementation in callback

πŸ“Š Comparison

Feature flutter_awesome_notification Manual Setup
Setup Complexity ⭐️ Simple ⭐️⭐️⭐️⭐️ Complex
Lines of Code ~10 lines ~500+ lines
Filtering System βœ… Built-in ❌ Manual
Topic Management βœ… Built-in ❌ Manual
Documentation βœ… Complete ❌ Variable
Maintenance βœ… Plugin updates ❌ Manual updates

πŸ“„ License

MIT License - see LICENSE file for details

🀝 Contributing

Contributions are welcome! Please open an issue or submit a PR.

πŸ“ž Support

For issues, questions, or feature requests, please open an issue on GitHub.

Libraries

flutter_awesome_notification
Flutter Awesome Notification Plugin