Skip to content

mahmoodhamdi/Flutter-Local-Notifications-Integration-Guide

Repository files navigation

Flutter Local Notifications Integration Guide

A comprehensive guide and demo application for integrating local notifications in Flutter apps. This project serves as a practical reference for developers looking to implement notifications on both Android and iOS platforms.

Flutter Dart License PRs Welcome

Features

Implemented

  • Basic instant notifications
  • Scheduled notifications with date/time picker
  • Repeating/periodic notifications
  • Custom notification sounds
  • Cancel all notifications
  • Handle notification tap responses
  • Stream-based notification event handling
  • iOS full support with DarwinInitializationSettings
  • Runtime permission requests (Android 13+)
  • View pending/scheduled notifications list
  • Cancel specific notification by ID
  • Big picture notifications (file path & drawable)
  • Progress bar notifications
  • Notification action buttons (Reply, Mark as Read, Dismiss)
  • Grouped notifications with inbox style
  • Enhanced notification details page
  • Notification history with search and filter
  • Deep linking from notifications
  • Media style notifications (for music/audio apps)

Roadmap

All planned features have been implemented!

Testing

This project includes comprehensive widget tests. Run tests with:

flutter test

Current test coverage includes:

  • NotificationButton widget tests
  • NotificationPage widget tests
  • PendingNotificationsPage widget tests
  • HeaderCard widget tests
  • Main app integration tests

Requirements

Platform Minimum Version
Flutter SDK 3.6.0+
Dart SDK 3.6.0+
Android API 21 (Android 5.0)
iOS 13.0+

Quick Start

1. Add Dependencies

dependencies:
  flutter_local_notifications: ^19.5.0
  timezone: ^0.10.0
flutter pub get

2. Android Setup

AndroidManifest.xml

Add permissions above the <application> tag:

<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM"/>
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<uses-permission android:name="android.permission.USE_EXACT_ALARM"/>

Add receivers inside <application> tag:

<receiver android:exported="false"
    android:name="com.dexterous.flutterlocalnotifications.ScheduledNotificationReceiver" />
<receiver android:exported="false"
    android:name="com.dexterous.flutterlocalnotifications.ScheduledNotificationBootReceiver">
    <intent-filter>
        <action android:name="android.intent.action.BOOT_COMPLETED"/>
        <action android:name="android.intent.action.MY_PACKAGE_REPLACED"/>
        <action android:name="android.intent.action.QUICKBOOT_POWERON"/>
        <action android:name="com.htc.intent.action.QUICKBOOT_POWERON"/>
    </intent-filter>
</receiver>

build.gradle (app level)

Enable desugaring for scheduled notifications:

android {
    compileSdk = 35

    compileOptions {
        coreLibraryDesugaringEnabled true
        sourceCompatibility = JavaVersion.VERSION_17
        targetCompatibility = JavaVersion.VERSION_17
    }

    defaultConfig {
        minSdk = 21
        targetSdk = 35
        multiDexEnabled true
    }
}

dependencies {
    coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.4'
}

3. Initialize Notifications

import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:timezone/data/latest_all.dart' as tz;

class NotificationHelper {
  static final FlutterLocalNotificationsPlugin _notification =
      FlutterLocalNotificationsPlugin();

  static Future<void> init() async {
    // Android settings
    const androidSettings = AndroidInitializationSettings("@mipmap/ic_launcher");

    // iOS settings (recommended)
    const iosSettings = DarwinInitializationSettings(
      requestAlertPermission: true,
      requestBadgePermission: true,
      requestSoundPermission: true,
    );

    const initSettings = InitializationSettings(
      android: androidSettings,
      iOS: iosSettings,
    );

    await _notification.initialize(
      initSettings,
      onDidReceiveNotificationResponse: onNotificationTap,
      onDidReceiveBackgroundNotificationResponse: onNotificationTap,
    );

    tz.initializeTimeZones();
  }
}

Usage Examples

Basic Notification

await NotificationHelper.showBasicNotification(
  id: 1,
  title: "Hello!",
  body: "This is a basic notification",
  payload: "custom_data",
);

Scheduled Notification

await NotificationHelper.showScheduleNotification(
  id: 2,
  title: "Reminder",
  body: "Don't forget your task!",
  delay: Duration(hours: 1),
);

Repeating Notification

await NotificationHelper.showRepeatingNotification(
  id: 3,
  title: "Daily Reminder",
  body: "Time to check in!",
  repeatInterval: RepeatInterval.daily,
);

Handle Notification Taps

@override
void initState() {
  super.initState();
  _subscription = NotificationHelper.notificationResponseController.stream
      .listen((response) {
        // Navigate or handle the tap
        Navigator.push(context, MaterialPageRoute(
          builder: (context) => NotificationDetailsPage(payload: response.payload),
        ));
      });
}

@override
void dispose() {
  _subscription?.cancel(); // Important: prevent memory leaks!
  super.dispose();
}

Custom Sound

Place your sound file in android/app/src/main/res/raw/ (e.g., custom_sound.mp3)

NotificationHelper.showBasicNotification(
  id: 4,
  title: "Custom Sound",
  body: "This notification has a custom sound!",
  sound: RawResourceAndroidNotificationSound('custom_sound'), // No extension!
);

Big Picture Notification

// From file path
await NotificationHelper.showBigPictureNotification(
  id: 5,
  title: "New Photo",
  body: "Check out this amazing picture!",
  bigPicturePath: "/path/to/image.jpg",
  summaryText: "Photo from vacation",
);

// From drawable resource
await NotificationHelper.showBigPictureFromDrawable(
  id: 6,
  title: "App Update",
  body: "New features available!",
  drawableName: "update_banner",
);

Progress Notification

// Show progress (useful for downloads, uploads, etc.)
for (int i = 0; i <= 100; i += 10) {
  await NotificationHelper.showProgressNotification(
    id: 7,
    title: "Downloading...",
    body: "$i% complete",
    progress: i,
    maxProgress: 100,
  );
  await Future.delayed(Duration(milliseconds: 500));
}

// Indeterminate progress (unknown duration)
await NotificationHelper.showProgressNotification(
  id: 8,
  title: "Processing...",
  body: "Please wait",
  progress: 0,
  maxProgress: 100,
  indeterminate: true,
);

View Pending Notifications

// Get list of scheduled notifications
final pendingNotifications = await NotificationHelper.getPendingNotifications();
for (final notification in pendingNotifications) {
  print('ID: ${notification.id}, Title: ${notification.title}');
}

// Cancel specific notification
await NotificationHelper.cancelNotification(notificationId);

Notification with Action Buttons

// Show notification with Reply, Mark as Read, and Dismiss buttons
await NotificationHelper.showNotificationWithActions(
  id: 9,
  title: "New Message",
  body: "You have a new message from Ahmed",
  payload: "message_123",
  showReplyAction: true,
  showMarkReadAction: true,
);

// Handle action responses in your listener
NotificationHelper.notificationResponseController.stream.listen((response) {
  if (response.actionId == NotificationHelper.actionReply) {
    final replyText = response.input;
    print('User replied: $replyText');
  } else if (response.actionId == NotificationHelper.actionMarkRead) {
    print('Marked as read');
  }
});

Grouped Notifications

// Show multiple notifications grouped together
await NotificationHelper.showNotificationGroup(
  groupKey: 'messages_group',
  summaryTitle: 'New Messages',
  notifications: [
    {'title': 'Ahmed', 'body': 'Hey, how are you?'},
    {'title': 'Sara', 'body': 'Meeting at 3 PM'},
    {'title': 'Ali', 'body': 'Check this out!'},
  ],
);

// Or show individual grouped notifications
await NotificationHelper.showGroupedNotification(
  groupKey: 'emails_group',
  title: 'New Email',
  body: 'You have a new email from support',
  id: 100,
  isSummary: false,
);

Notification History

// Get notification history
final history = await NotificationStorageHelper.getNotificationHistory();

// Search notifications
final results = await NotificationStorageHelper.searchNotifications('meeting');

// Get unread count
final unreadCount = await NotificationStorageHelper.getUnreadCount();

// Mark as read
await NotificationStorageHelper.markAsRead(notificationId);

// Clear all history
await NotificationStorageHelper.clearHistory();

Deep Linking

// Create payloads for deep linking
final pagePayload = DeepLinkHelper.createPagePayload('history'); // Opens history page
final messagePayload = DeepLinkHelper.createMessagePayload('123'); // Opens message
final actionPayload = DeepLinkHelper.createActionPayload('reply', 'Hello');

// Show notification with deep link
await NotificationHelper.showBasicNotification(
  id: 10,
  title: "View History",
  body: "Tap to see your notification history",
  payload: pagePayload, // Will navigate to history page
);

// Handle deep links in your listener
DeepLinkHelper.handleDeepLink(
  context: context,
  response: notificationResponse,
);

Media Style Notification

// Simple media notification (for music apps)
await NotificationHelper.showSimpleMediaNotification(
  id: 999,
  songTitle: "Beautiful Day",
  artist: "Artist Name",
  album: "Album Name",
  isPlaying: true,
);

// Full media notification with album art
await NotificationHelper.showMediaNotification(
  id: 999,
  title: "Now Playing",
  body: "Album Name",
  artist: "Artist Name",
  albumArt: "/path/to/album/art.jpg",
  isPlaying: true,
);

Project Structure

lib/
├── main.dart                      # App entry point
├── helpers/
│   ├── deep_link_helper.dart      # Deep linking from notifications
│   ├── notification_helper.dart   # Core notification logic
│   ├── notification_storage_helper.dart # History storage
│   ├── permission_helper.dart     # Permission management
│   └── show_snack_bar_helper.dart # UI helper
├── models/
│   └── notification_record.dart   # Notification history model
├── pages/
│   ├── home_page.dart             # Main UI with buttons
│   ├── notification_history_page.dart # View notification history
│   ├── notification_page.dart     # Notification details
│   └── pending_notifications_page.dart # View scheduled notifications
├── widgets/
│   ├── notification_button.dart   # Reusable button with icon
│   └── header_card.dart           # Header card widget
└── theme/
    └── theme.dart                 # App theming

Notification Channels

Channel ID Purpose
basic_notification Instant notifications
repeating_notification Periodic notifications
schedule_notification Time-scheduled notifications
big_picture_notification Notifications with images
progress_notification Progress bar notifications
action_notification Notifications with action buttons
grouped_notification Grouped/inbox style notifications
media_notification Media playback controls

Common Issues & Solutions

Notifications not showing on Android 13+

Request POST_NOTIFICATIONS permission at runtime:

final plugin = FlutterLocalNotificationsPlugin();
await plugin.resolvePlatformSpecificImplementation<
    AndroidFlutterLocalNotificationsPlugin>()?.requestNotificationsPermission();

Scheduled notifications not working

  1. Ensure desugaring is enabled in build.gradle
  2. Call tz.initializeTimeZones() before scheduling
  3. Check that SCHEDULE_EXACT_ALARM permission is granted

Custom sound not playing

  1. File must be in res/raw/ folder
  2. Use lowercase filename with underscores
  3. Don't include file extension in code

Contributing

Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/AmazingFeature)
  3. Commit your changes (git commit -m 'Add some AmazingFeature')
  4. Push to the branch (git push origin feature/AmazingFeature)
  5. Open a Pull Request

Resources

License

This project is licensed under the MIT License - see the LICENSE file for details.

Author

Mahmoud Hamdy


If you found this guide helpful, please give it a star on GitHub!


مَن قالَ: لا إلَهَ إلَّا اللَّهُ، وحْدَهُ لا شَرِيكَ له، له المُلْكُ وله الحَمْدُ، وهو علَى كُلِّ شَيءٍ قَدِيرٌ، في يَومٍ مِئَةَ مَرَّةٍ؛ كانَتْ له عَدْلَ عَشْرِ رِقابٍ، وكُتِبَتْ له مِئَةُ حَسَنَةٍ، ومُحِيَتْ عنْه مِئَةُ سَيِّئَةٍ، وكانَتْ له حِرْزًا مِنَ الشَّيْطانِ يَومَهُ ذلكَ حتَّى يُمْسِيَ، ولَمْ يَأْتِ أحَدٌ بأَفْضَلَ ممَّا جاءَ به، إلَّا أحَدٌ عَمِلَ أكْثَرَ مِن ذلكَ.

— صحيح البخاري

About

A comprehensive guide for integrating local notifications in Flutter applications, including setup instructions, code samples, and best practices.

Topics

Resources

License

Stars

Watchers

Forks

Contributors 2

  •  
  •