Skip to content

Instantly share code, notes, and snippets.

@Nidal-Bakir
Last active March 9, 2025 13:31
Show Gist options
  • Save Nidal-Bakir/539f9ce487764b4e4105819bb9962d6f to your computer and use it in GitHub Desktop.
Save Nidal-Bakir/539f9ce487764b4e4105819bb9962d6f to your computer and use it in GitHub Desktop.

Revisions

  1. Nidal-Bakir revised this gist Mar 9, 2025. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion notification_service.dart
    Original file line number Diff line number Diff line change
    @@ -1,6 +1,6 @@
    // MIT License
    //
    // Copyright (c) 2023 Nidal Bakir
    // Copyright (c) 2025 Nidal Bakir
    //
    // Permission is hereby granted, free of charge, to any person obtaining a copy
    // of this software and associated documentation files (the "Software"), to deal
  2. Nidal-Bakir revised this gist Mar 9, 2025. 1 changed file with 23 additions and 0 deletions.
    23 changes: 23 additions & 0 deletions notification_service.dart
    Original file line number Diff line number Diff line change
    @@ -1,3 +1,26 @@
    // MIT License
    //
    // Copyright (c) 2023 Nidal Bakir
    //
    // Permission is hereby granted, free of charge, to any person obtaining a copy
    // of this software and associated documentation files (the "Software"), to deal
    // in the Software without restriction, including without limitation the rights
    // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    // copies of the Software, and to permit persons to whom the Software is
    // furnished to do so, subject to the following conditions:
    //
    // The above copyright notice and this permission notice shall be included in all
    // copies or substantial portions of the Software.
    //
    // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    // SOFTWARE.
    //

    import 'dart:async';
    import 'dart:convert';
    import 'dart:io';
  3. Nidal-Bakir revised this gist Mar 9, 2025. 4 changed files with 80 additions and 76 deletions.
    30 changes: 0 additions & 30 deletions global_function.dart
    Original file line number Diff line number Diff line change
    @@ -1,30 +0,0 @@
    import 'dart:io';


    import 'package:path_provider/path_provider.dart' as path_provider;
    import 'package:path/path.dart' as path;
    import 'package:dio/dio.dart';

    import 'logger/logger.dart';


    Future<File?> gDownloadAndSaveFileToCacheDir(String fileURL) async {
    try {
    final cacheDir = await path_provider.getTemporaryDirectory();
    final filePath = cacheDir.path + path.basename(fileURL);
    await Dio().download(fileURL, filePath);

    final file = File(filePath);
    if (file.existsSync()) {
    return file;
    }

    Logger.e('Error the downloaded file don not exists', error: 'File not exist after download');

    return null;
    } catch (e, s) {
    Logger.e('Error while downloading image for notification', error: e, stackTrace: s);
    }

    return null;
    }
    83 changes: 80 additions & 3 deletions notification_service.dart
    Original file line number Diff line number Diff line change
    @@ -3,15 +3,16 @@ import 'dart:convert';
    import 'dart:io';
    import 'dart:math';

    import 'package:rxdart/rxdart.dart';
    import 'package:path_provider/path_provider.dart' as path_provider;
    import 'package:path/path.dart' as path;
    import 'package:dio/dio.dart';
    import 'package:firebase_messaging/firebase_messaging.dart';
    import 'package:flutter_local_notifications/flutter_local_notifications.dart';
    import 'package:string_validator/string_validator.dart';

    import '../../theme/app_theme.dart';
    import '../../utils/global_function.dart';
    import '../../utils/logger/logger.dart';
    import 'notification_tap_handler.dart';
    import 'unread_notification_count.dart';

    const _channelId = '';
    const _channelDeception = '';
    @@ -229,3 +230,79 @@ class NotificationService {
    return NotificationDetails(android: androidNotificationDetails, iOS: darwinNotificationDetails);
    }
    }


    //-------------------------------------------------------------------------------------------------


    Future<File?> gDownloadAndSaveFileToCacheDir(String fileURL) async {
    try {
    final cacheDir = await path_provider.getTemporaryDirectory();
    final filePath = cacheDir.path + path.basename(fileURL);
    await Dio().download(fileURL, filePath);

    final file = File(filePath);
    if (file.existsSync()) {
    return file;
    }

    Logger.e('Error the downloaded file don not exists', error: 'File not exist after download');

    return null;
    } catch (e, s) {
    Logger.e('Error while downloading image for notification', error: e, stackTrace: s);
    }

    return null;
    }


    //-------------------------------------------------------------------------------------------------


    void handleNotificationTap(Map<String, dynamic> payload) {
    final key = NotificationKeys.extractNotificationKeyFromPayload(payload);
    if (key == null) {
    return;
    }

    // TODO: handel notification tap
    // ....
    }


    //-------------------------------------------------------------------------------------------------


    class UnreadNotificationCount {
    UnreadNotificationCount._();
    static UnreadNotificationCount? _i;
    factory UnreadNotificationCount() {
    return _i ??= UnreadNotificationCount._();
    }

    int _count = 0;

    // No need to close this stream controller as it will life as long as the app is open
    late final _behaviorSubject = BehaviorSubject<int>.seeded(_count);

    Stream<int> get unreadNotificationCountStream {
    return _behaviorSubject.stream;
    }

    void setUnreadNotificationCount(int count) {
    assert(count >= 0);
    _count = count;
    _behaviorSubject.add(_count);
    Logger.i("Set unread notification count. New count: $_count", tag: 'UnreadNotificationCount');
    }

    void incrementUnreadNotificationCount([int incrementBy = 1]) {
    assert(incrementBy >= 1);
    _count += incrementBy;
    _behaviorSubject.add(_count);
    Logger.i("Increment unread notification count. New count: $_count", tag: 'UnreadNotificationCount');
    }
    }

    //-------------------------------------------------------------------------------------------------
    9 changes: 0 additions & 9 deletions notification_tap_handler.dart
    Original file line number Diff line number Diff line change
    @@ -1,9 +0,0 @@
    void handleNotificationTap(Map<String, dynamic> payload) {
    final key = NotificationKeys.extractNotificationKeyFromPayload(payload);
    if (key == null) {
    return;
    }

    // TODO: handel notification tap
    // ....
    }
    34 changes: 0 additions & 34 deletions unread_notification_count.dart
    Original file line number Diff line number Diff line change
    @@ -1,34 +0,0 @@
    import 'package:rxdart/rxdart.dart';

    import '../../utils/logger/logger.dart';

    class UnreadNotificationCount {
    UnreadNotificationCount._();
    static UnreadNotificationCount? _i;
    factory UnreadNotificationCount() {
    return _i ??= UnreadNotificationCount._();
    }

    int _count = 0;

    // No need to close this stream controller as it will life as long as the app is open
    late final _behaviorSubject = BehaviorSubject<int>.seeded(_count);

    Stream<int> get unreadNotificationCountStream {
    return _behaviorSubject.stream;
    }

    void setUnreadNotificationCount(int count) {
    assert(count >= 0);
    _count = count;
    _behaviorSubject.add(_count);
    Logger.i("Set unread notification count. New count: $_count", tag: 'UnreadNotificationCount');
    }

    void incrementUnreadNotificationCount([int incrementBy = 1]) {
    assert(incrementBy >= 1);
    _count += incrementBy;
    _behaviorSubject.add(_count);
    Logger.i("Increment unread notification count. New count: $_count", tag: 'UnreadNotificationCount');
    }
    }
  4. Nidal-Bakir revised this gist Mar 9, 2025. 1 changed file with 231 additions and 0 deletions.
    231 changes: 231 additions & 0 deletions notification_service.dart
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,231 @@
    import 'dart:async';
    import 'dart:convert';
    import 'dart:io';
    import 'dart:math';

    import 'package:firebase_messaging/firebase_messaging.dart';
    import 'package:flutter_local_notifications/flutter_local_notifications.dart';
    import 'package:string_validator/string_validator.dart';

    import '../../theme/app_theme.dart';
    import '../../utils/global_function.dart';
    import '../../utils/logger/logger.dart';
    import 'notification_tap_handler.dart';
    import 'unread_notification_count.dart';

    const _channelId = '';
    const _channelDeception = '';
    const _channelTitle = '';
    const _topicKey = 'general';

    class NotificationService {
    factory NotificationService() {
    return _instance ??= NotificationService._();
    }

    NotificationService._();
    static NotificationService? _instance;

    final _firebaseMessaging = FirebaseMessaging.instance;

    final _flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();

    var _didInitializeNotificationService = false;
    Future<void> initialize() async {
    if (_didInitializeNotificationService) {
    Logger.w(
    'You already initialized the notification service. '
    'Ignoring the reinitialize request!',
    );
    return;
    }

    final settings = await _firebaseMessaging.requestPermission();

    if (settings.authorizationStatus != AuthorizationStatus.authorized) {
    return;
    }

    unawaited(_firebaseMessaging.subscribeToTopic(_topicKey));

    await _initLocalNotificationPackage();

    _startNotificationsListener();

    _didInitializeNotificationService = true;
    }

    Future<void> _initLocalNotificationPackage() async {
    await _flutterLocalNotificationsPlugin
    .resolvePlatformSpecificImplementation<AndroidFlutterLocalNotificationsPlugin>()
    ?.createNotificationChannel(
    const AndroidNotificationChannel(
    _channelId,
    _channelTitle,
    description: _channelDeception,
    ledColor: AppColors.kSecondary,
    playSound: true,
    showBadge: true,
    audioAttributesUsage: AudioAttributesUsage.notification,
    importance: Importance.max,
    enableLights: true,
    enableVibration: false,
    ),
    );

    const initializationSettingsAndroid = AndroidInitializationSettings('@mipmap/ic_stat_notification_icon');

    const iosInitializationSettings = DarwinInitializationSettings(
    defaultPresentAlert: true,
    defaultPresentBadge: true,
    defaultPresentBanner: true,
    defaultPresentList: true,
    defaultPresentSound: true,
    requestAlertPermission: true,
    requestBadgePermission: true,
    requestSoundPermission: true,
    requestCriticalPermission: false,
    requestProvisionalPermission: false,
    );

    const initializationSettings = InitializationSettings(
    android: initializationSettingsAndroid,
    iOS: iosInitializationSettings,
    );

    await _flutterLocalNotificationsPlugin.initialize(
    initializationSettings,
    onDidReceiveNotificationResponse: (details) {
    if (details.payload != null) {
    handleNotificationTap(json.decode(details.payload!) as Map<String, dynamic>);
    }
    },
    );
    }

    bool _isNotificationListenerInitialized = false;

    void _startNotificationsListener() {
    if (_isNotificationListenerInitialized) {
    Logger.w(
    'You already started the notification Listener. '
    'Ignoring the start request!',
    );
    return;
    }
    _isNotificationListenerInitialized = true;

    // show notifications
    FirebaseMessaging.onMessage.listen(_showNotification);
    }

    bool _isTapListenersInitialized = false;

    Future<void> startTapNotificationsListener() async {
    assert(_didInitializeNotificationService, 'Did you forget to initialize the notification service?');

    if (_isTapListenersInitialized) {
    return;
    }
    _isTapListenersInitialized = true;

    final remoteInitialMessage = await FirebaseMessaging.instance.getInitialMessage();
    if (remoteInitialMessage != null) {
    handleNotificationTap(remoteInitialMessage.data);
    }

    FirebaseMessaging.onMessageOpenedApp.listen((remoteMessage) {
    handleNotificationTap(remoteMessage.data);
    });

    final notificationAppLaunchDetails = await _flutterLocalNotificationsPlugin.getNotificationAppLaunchDetails();
    if (notificationAppLaunchDetails != null) {
    final payload = notificationAppLaunchDetails.notificationResponse?.payload;
    if (payload != null) {
    handleNotificationTap(json.decode(payload) as Map<String, dynamic>);
    }
    }
    }

    Future<void> _showNotification(RemoteMessage message) async {
    UnreadNotificationCount().incrementUnreadNotificationCount();

    Logger.i(message.toMap().toString(), tag: 'notification');

    final notificationDetails = await _generateNotificationDetails(message);

    await _flutterLocalNotificationsPlugin.show(
    Random().nextInt(99999),
    message.notification?.title,
    message.notification?.body,
    notificationDetails,
    payload: json.encode(message.data),
    );
    }

    Future<NotificationDetails> _generateNotificationDetails(RemoteMessage message) async {
    String? imageURL;
    Map<String, dynamic>? data;
    File? imageFile;

    try {
    final dataStr = message.data['data'] as String? ?? '';
    if (dataStr.isNotEmpty) {
    data = Map<String, dynamic>.from(jsonDecode(dataStr) as Map? ?? {});
    imageURL = data['image'] as String?;
    }
    } catch (e, s) {
    Logger.e("message", error: e, stackTrace: s, tag: 'generateNotificationDetails');
    }

    if (imageURL?.isURL() ?? false) {
    imageFile = await gDownloadAndSaveFileToCacheDir(imageURL!);
    } else {
    imageFile = null;
    }

    final DarwinNotificationDetails darwinNotificationDetails;
    final StyleInformation androidNotificationStyleInformation;

    if (imageFile != null) {
    darwinNotificationDetails = DarwinNotificationDetails(
    attachments: [DarwinNotificationAttachment(imageFile.path, hideThumbnail: true)],
    );

    final bitmapImage = FilePathAndroidBitmap(imageFile.path);
    androidNotificationStyleInformation = BigPictureStyleInformation(
    bitmapImage,
    largeIcon: bitmapImage,
    hideExpandedLargeIcon: true,
    contentTitle: message.notification?.title ?? '',
    summaryText: message.notification?.body ?? '',
    htmlFormatContentTitle: true,
    htmlFormatTitle: true,
    htmlFormatContent: true,
    htmlFormatSummaryText: true,
    );
    } else {
    darwinNotificationDetails = DarwinNotificationDetails();
    androidNotificationStyleInformation = BigTextStyleInformation(
    message.notification?.body ?? '',
    contentTitle: message.notification?.title ?? '',
    htmlFormatBigText: true,
    htmlFormatContentTitle: true,
    htmlFormatTitle: true,
    htmlFormatContent: true,
    htmlFormatSummaryText: true,
    );
    }

    final androidNotificationDetails = AndroidNotificationDetails(
    _channelId,
    _channelTitle,
    channelDescription: _channelDeception,
    importance: Importance.max,
    styleInformation: androidNotificationStyleInformation,
    priority: Priority.max,
    );

    return NotificationDetails(android: androidNotificationDetails, iOS: darwinNotificationDetails);
    }
    }
  5. Nidal-Bakir revised this gist Mar 9, 2025. 1 changed file with 0 additions and 231 deletions.
    231 changes: 0 additions & 231 deletions notification_service.dart
    Original file line number Diff line number Diff line change
    @@ -1,231 +0,0 @@
    import 'dart:async';
    import 'dart:convert';
    import 'dart:io';
    import 'dart:math';

    import 'package:firebase_messaging/firebase_messaging.dart';
    import 'package:flutter_local_notifications/flutter_local_notifications.dart';
    import 'package:string_validator/string_validator.dart';

    import '../../theme/app_theme.dart';
    import '../../utils/global_function.dart';
    import '../../utils/logger/logger.dart';
    import 'notification_tap_handler.dart';
    import 'unread_notification_count.dart';

    const _channelId = '';
    const _channelDeception = '';
    const _channelTitle = '';
    const _topicKey = 'general';

    class NotificationService {
    factory NotificationService() {
    return _instance ??= NotificationService._();
    }

    NotificationService._();
    static NotificationService? _instance;

    final _firebaseMessaging = FirebaseMessaging.instance;

    final _flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();

    var _didInitializeNotificationService = false;
    Future<void> initialize() async {
    if (_didInitializeNotificationService) {
    Logger.w(
    'You already initialized the notification service. '
    'Ignoring the reinitialize request!',
    );
    return;
    }

    final settings = await _firebaseMessaging.requestPermission();

    if (settings.authorizationStatus != AuthorizationStatus.authorized) {
    return;
    }

    unawaited(_firebaseMessaging.subscribeToTopic(_topicKey));

    await _initLocalNotificationPackage();

    _startNotificationsListener();

    _didInitializeNotificationService = true;
    }

    Future<void> _initLocalNotificationPackage() async {
    await _flutterLocalNotificationsPlugin
    .resolvePlatformSpecificImplementation<AndroidFlutterLocalNotificationsPlugin>()
    ?.createNotificationChannel(
    const AndroidNotificationChannel(
    _channelId,
    _channelTitle,
    description: _channelDeception,
    ledColor: AppColors.kSecondary,
    playSound: true,
    showBadge: true,
    audioAttributesUsage: AudioAttributesUsage.notification,
    importance: Importance.max,
    enableLights: true,
    enableVibration: false,
    ),
    );

    const initializationSettingsAndroid = AndroidInitializationSettings('@mipmap/ic_stat_notification_icon');

    const iosInitializationSettings = DarwinInitializationSettings(
    defaultPresentAlert: true,
    defaultPresentBadge: true,
    defaultPresentBanner: true,
    defaultPresentList: true,
    defaultPresentSound: true,
    requestAlertPermission: true,
    requestBadgePermission: true,
    requestSoundPermission: true,
    requestCriticalPermission: false,
    requestProvisionalPermission: false,
    );

    const initializationSettings = InitializationSettings(
    android: initializationSettingsAndroid,
    iOS: iosInitializationSettings,
    );

    await _flutterLocalNotificationsPlugin.initialize(
    initializationSettings,
    onDidReceiveNotificationResponse: (details) {
    if (details.payload != null) {
    handleNotificationTap(json.decode(details.payload!) as Map<String, dynamic>);
    }
    },
    );
    }

    bool _isNotificationListenerInitialized = false;

    void _startNotificationsListener() {
    if (_isNotificationListenerInitialized) {
    Logger.w(
    'You already started the notification Listener. '
    'Ignoring the start request!',
    );
    return;
    }
    _isNotificationListenerInitialized = true;

    // show notifications
    FirebaseMessaging.onMessage.listen(_showNotification);
    }

    bool _isTapListenersInitialized = false;

    Future<void> startTapNotificationsListener() async {
    assert(_didInitializeNotificationService, 'Did you forget to initialize the notification service?');

    if (_isTapListenersInitialized) {
    return;
    }
    _isTapListenersInitialized = true;

    final remoteInitialMessage = await FirebaseMessaging.instance.getInitialMessage();
    if (remoteInitialMessage != null) {
    handleNotificationTap(remoteInitialMessage.data);
    }

    FirebaseMessaging.onMessageOpenedApp.listen((remoteMessage) {
    handleNotificationTap(remoteMessage.data);
    });

    final notificationAppLaunchDetails = await _flutterLocalNotificationsPlugin.getNotificationAppLaunchDetails();
    if (notificationAppLaunchDetails != null) {
    final payload = notificationAppLaunchDetails.notificationResponse?.payload;
    if (payload != null) {
    handleNotificationTap(json.decode(payload) as Map<String, dynamic>);
    }
    }
    }

    Future<void> _showNotification(RemoteMessage message) async {
    UnreadNotificationCount().incrementUnreadNotificationCount();

    Logger.i(message.toMap().toString(), tag: 'notification');

    final notificationDetails = await _generateNotificationDetails(message);

    await _flutterLocalNotificationsPlugin.show(
    Random().nextInt(99999),
    message.notification?.title,
    message.notification?.body,
    notificationDetails,
    payload: json.encode(message.data),
    );
    }

    Future<NotificationDetails> _generateNotificationDetails(RemoteMessage message) async {
    String? imageURL;
    Map<String, dynamic>? data;
    File? imageFile;

    try {
    final dataStr = message.data['data'] as String? ?? '';
    if (dataStr.isNotEmpty) {
    data = Map<String, dynamic>.from(jsonDecode(dataStr) as Map? ?? {});
    imageURL = data['image'] as String?;
    }
    } catch (e, s) {
    Logger.e("message", error: e, stackTrace: s, tag: 'generateNotificationDetails');
    }

    if (imageURL?.isURL() ?? false) {
    imageFile = await gDownloadAndSaveFileToCacheDir(imageURL!);
    } else {
    imageFile = null;
    }

    final DarwinNotificationDetails darwinNotificationDetails;
    final StyleInformation androidNotificationStyleInformation;

    if (imageFile != null) {
    darwinNotificationDetails = DarwinNotificationDetails(
    attachments: [DarwinNotificationAttachment(imageFile.path, hideThumbnail: true)],
    );

    final bitmapImage = FilePathAndroidBitmap(imageFile.path);
    androidNotificationStyleInformation = BigPictureStyleInformation(
    bitmapImage,
    largeIcon: bitmapImage,
    hideExpandedLargeIcon: true,
    contentTitle: message.notification?.title ?? '',
    summaryText: message.notification?.body ?? '',
    htmlFormatContentTitle: true,
    htmlFormatTitle: true,
    htmlFormatContent: true,
    htmlFormatSummaryText: true,
    );
    } else {
    darwinNotificationDetails = DarwinNotificationDetails();
    androidNotificationStyleInformation = BigTextStyleInformation(
    message.notification?.body ?? '',
    contentTitle: message.notification?.title ?? '',
    htmlFormatBigText: true,
    htmlFormatContentTitle: true,
    htmlFormatTitle: true,
    htmlFormatContent: true,
    htmlFormatSummaryText: true,
    );
    }

    final androidNotificationDetails = AndroidNotificationDetails(
    _channelId,
    _channelTitle,
    channelDescription: _channelDeception,
    importance: Importance.max,
    styleInformation: androidNotificationStyleInformation,
    priority: Priority.max,
    );

    return NotificationDetails(android: androidNotificationDetails, iOS: darwinNotificationDetails);
    }
    }
  6. Nidal-Bakir revised this gist Mar 9, 2025. No changes.
  7. Nidal-Bakir revised this gist Mar 9, 2025. 3 changed files with 73 additions and 0 deletions.
    30 changes: 30 additions & 0 deletions global_function.dart
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,30 @@
    import 'dart:io';


    import 'package:path_provider/path_provider.dart' as path_provider;
    import 'package:path/path.dart' as path;
    import 'package:dio/dio.dart';

    import 'logger/logger.dart';


    Future<File?> gDownloadAndSaveFileToCacheDir(String fileURL) async {
    try {
    final cacheDir = await path_provider.getTemporaryDirectory();
    final filePath = cacheDir.path + path.basename(fileURL);
    await Dio().download(fileURL, filePath);

    final file = File(filePath);
    if (file.existsSync()) {
    return file;
    }

    Logger.e('Error the downloaded file don not exists', error: 'File not exist after download');

    return null;
    } catch (e, s) {
    Logger.e('Error while downloading image for notification', error: e, stackTrace: s);
    }

    return null;
    }
    9 changes: 9 additions & 0 deletions notification_tap_handler.dart
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,9 @@
    void handleNotificationTap(Map<String, dynamic> payload) {
    final key = NotificationKeys.extractNotificationKeyFromPayload(payload);
    if (key == null) {
    return;
    }

    // TODO: handel notification tap
    // ....
    }
    34 changes: 34 additions & 0 deletions unread_notification_count.dart
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,34 @@
    import 'package:rxdart/rxdart.dart';

    import '../../utils/logger/logger.dart';

    class UnreadNotificationCount {
    UnreadNotificationCount._();
    static UnreadNotificationCount? _i;
    factory UnreadNotificationCount() {
    return _i ??= UnreadNotificationCount._();
    }

    int _count = 0;

    // No need to close this stream controller as it will life as long as the app is open
    late final _behaviorSubject = BehaviorSubject<int>.seeded(_count);

    Stream<int> get unreadNotificationCountStream {
    return _behaviorSubject.stream;
    }

    void setUnreadNotificationCount(int count) {
    assert(count >= 0);
    _count = count;
    _behaviorSubject.add(_count);
    Logger.i("Set unread notification count. New count: $_count", tag: 'UnreadNotificationCount');
    }

    void incrementUnreadNotificationCount([int incrementBy = 1]) {
    assert(incrementBy >= 1);
    _count += incrementBy;
    _behaviorSubject.add(_count);
    Logger.i("Increment unread notification count. New count: $_count", tag: 'UnreadNotificationCount');
    }
    }
  8. Nidal-Bakir revised this gist Mar 9, 2025. 1 changed file with 116 additions and 52 deletions.
    168 changes: 116 additions & 52 deletions notification_service.dart
    Original file line number Diff line number Diff line change
    @@ -1,15 +1,21 @@
    import 'dart:async';
    import 'dart:convert';
    import 'dart:io';
    import 'dart:math';

    import 'package:firebase_messaging/firebase_messaging.dart';
    import 'package:flutter_local_notifications/flutter_local_notifications.dart';
    import 'package:string_validator/string_validator.dart';

    import '../utils/logger/logger.dart';
    import '../../theme/app_theme.dart';
    import '../../utils/global_function.dart';
    import '../../utils/logger/logger.dart';
    import 'notification_tap_handler.dart';
    import 'unread_notification_count.dart';

    const _channelId = '<your_channel_id>';
    const _channelDeception = '<your_channel_desc>';
    const _channelTitle = '<your_channel_title>';
    const _channelId = '';
    const _channelDeception = '';
    const _channelTitle = '';
    const _topicKey = 'general';

    class NotificationService {
    @@ -27,8 +33,10 @@ class NotificationService {
    var _didInitializeNotificationService = false;
    Future<void> initialize() async {
    if (_didInitializeNotificationService) {
    Logger.w('You already initialized the notification service. '
    'Ignoring the reinitialize request!');
    Logger.w(
    'You already initialized the notification service. '
    'Ignoring the reinitialize request!',
    );
    return;
    }

    @@ -49,35 +57,47 @@ class NotificationService {

    Future<void> _initLocalNotificationPackage() async {
    await _flutterLocalNotificationsPlugin
    .resolvePlatformSpecificImplementation<
    AndroidFlutterLocalNotificationsPlugin>()
    .resolvePlatformSpecificImplementation<AndroidFlutterLocalNotificationsPlugin>()
    ?.createNotificationChannel(
    const AndroidNotificationChannel(
    _channelId,
    _channelTitle,
    description: _channelDeception,
    ledColor: AppColors.kSecondary,
    playSound: true,
    showBadge: true,
    audioAttributesUsage: AudioAttributesUsage.notification,
    importance: Importance.max,
    enableLights: true,
    enableVibration: false,
    ),
    );

    const initializationSettingsAndroid =
    AndroidInitializationSettings('@mipmap/ic_stat_notification_icon');

    const darwinInitializationSettings = DarwinInitializationSettings();
    const initializationSettingsAndroid = AndroidInitializationSettings('@mipmap/ic_stat_notification_icon');

    const iosInitializationSettings = DarwinInitializationSettings(
    defaultPresentAlert: true,
    defaultPresentBadge: true,
    defaultPresentBanner: true,
    defaultPresentList: true,
    defaultPresentSound: true,
    requestAlertPermission: true,
    requestBadgePermission: true,
    requestSoundPermission: true,
    requestCriticalPermission: false,
    requestProvisionalPermission: false,
    );

    const initializationSettings = InitializationSettings(
    android: initializationSettingsAndroid,
    iOS: darwinInitializationSettings,
    iOS: iosInitializationSettings,
    );

    await _flutterLocalNotificationsPlugin.initialize(
    initializationSettings,
    onDidReceiveNotificationResponse: (details) {
    if (details.payload != null) {
    _handleNotificationTap(
    json.decode(details.payload!) as Map<String, dynamic>,
    );
    handleNotificationTap(json.decode(details.payload!) as Map<String, dynamic>);
    }
    },
    );
    @@ -87,8 +107,10 @@ class NotificationService {

    void _startNotificationsListener() {
    if (_isNotificationListenerInitialized) {
    Logger.w('You already started the notification Listener. '
    'Ignoring the start request!');
    Logger.w(
    'You already started the notification Listener. '
    'Ignoring the start request!',
    );
    return;
    }
    _isNotificationListenerInitialized = true;
    @@ -100,68 +122,110 @@ class NotificationService {
    bool _isTapListenersInitialized = false;

    Future<void> startTapNotificationsListener() async {
    assert(
    _didInitializeNotificationService,
    'Did you forget to initialize the notification service?',
    );
    assert(_didInitializeNotificationService, 'Did you forget to initialize the notification service?');

    if (_isTapListenersInitialized) {
    return;
    }
    _isTapListenersInitialized = true;

    // handle notification tap

    final remoteInitialMessage =
    await FirebaseMessaging.instance.getInitialMessage();
    final remoteInitialMessage = await FirebaseMessaging.instance.getInitialMessage();
    if (remoteInitialMessage != null) {
    _handleNotificationTap(remoteInitialMessage.data);
    handleNotificationTap(remoteInitialMessage.data);
    }

    FirebaseMessaging.onMessageOpenedApp.listen((remoteMessage) {
    _handleNotificationTap(remoteMessage.data);
    handleNotificationTap(remoteMessage.data);
    });

    final notificationAppLaunchDetails = await _flutterLocalNotificationsPlugin
    .getNotificationAppLaunchDetails();

    final notificationAppLaunchDetails = await _flutterLocalNotificationsPlugin.getNotificationAppLaunchDetails();
    if (notificationAppLaunchDetails != null) {
    final payload =
    notificationAppLaunchDetails.notificationResponse?.payload;
    final payload = notificationAppLaunchDetails.notificationResponse?.payload;
    if (payload != null) {
    _handleNotificationTap(json.decode(payload) as Map<String, dynamic>);
    handleNotificationTap(json.decode(payload) as Map<String, dynamic>);
    }
    }
    }

    Future<void> _showNotification(RemoteMessage message) async {
    final bigTextStyleInformation = BigTextStyleInformation(
    message.notification?.body.toString() ?? '',
    contentTitle: message.notification?.title.toString(),
    );
    UnreadNotificationCount().incrementUnreadNotificationCount();

    final androidPlatformChannelSpecifics = AndroidNotificationDetails(
    _channelId,
    _channelTitle,
    channelDescription: _channelDeception,
    importance: Importance.max,
    styleInformation: bigTextStyleInformation,
    priority: Priority.max,
    );
    Logger.i(message.toMap().toString(), tag: 'notification');

    final platformChannelSpecifics =
    NotificationDetails(android: androidPlatformChannelSpecifics);
    final notificationDetails = await _generateNotificationDetails(message);

    await _flutterLocalNotificationsPlugin.show(
    Random().nextInt(99999),
    message.notification?.title,
    message.notification?.body,
    platformChannelSpecifics,
    notificationDetails,
    payload: json.encode(message.data),
    );
    }
    }

    void _handleNotificationTap(Map<String, dynamic> payload) {
    // handel notification tap
    Future<NotificationDetails> _generateNotificationDetails(RemoteMessage message) async {
    String? imageURL;
    Map<String, dynamic>? data;
    File? imageFile;

    try {
    final dataStr = message.data['data'] as String? ?? '';
    if (dataStr.isNotEmpty) {
    data = Map<String, dynamic>.from(jsonDecode(dataStr) as Map? ?? {});
    imageURL = data['image'] as String?;
    }
    } catch (e, s) {
    Logger.e("message", error: e, stackTrace: s, tag: 'generateNotificationDetails');
    }

    if (imageURL?.isURL() ?? false) {
    imageFile = await gDownloadAndSaveFileToCacheDir(imageURL!);
    } else {
    imageFile = null;
    }

    final DarwinNotificationDetails darwinNotificationDetails;
    final StyleInformation androidNotificationStyleInformation;

    if (imageFile != null) {
    darwinNotificationDetails = DarwinNotificationDetails(
    attachments: [DarwinNotificationAttachment(imageFile.path, hideThumbnail: true)],
    );

    final bitmapImage = FilePathAndroidBitmap(imageFile.path);
    androidNotificationStyleInformation = BigPictureStyleInformation(
    bitmapImage,
    largeIcon: bitmapImage,
    hideExpandedLargeIcon: true,
    contentTitle: message.notification?.title ?? '',
    summaryText: message.notification?.body ?? '',
    htmlFormatContentTitle: true,
    htmlFormatTitle: true,
    htmlFormatContent: true,
    htmlFormatSummaryText: true,
    );
    } else {
    darwinNotificationDetails = DarwinNotificationDetails();
    androidNotificationStyleInformation = BigTextStyleInformation(
    message.notification?.body ?? '',
    contentTitle: message.notification?.title ?? '',
    htmlFormatBigText: true,
    htmlFormatContentTitle: true,
    htmlFormatTitle: true,
    htmlFormatContent: true,
    htmlFormatSummaryText: true,
    );
    }

    final androidNotificationDetails = AndroidNotificationDetails(
    _channelId,
    _channelTitle,
    channelDescription: _channelDeception,
    importance: Importance.max,
    styleInformation: androidNotificationStyleInformation,
    priority: Priority.max,
    );

    return NotificationDetails(android: androidNotificationDetails, iOS: darwinNotificationDetails);
    }
    }
  9. Nidal-Bakir created this gist Apr 10, 2024.
    167 changes: 167 additions & 0 deletions notification_service.dart
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,167 @@
    import 'dart:async';
    import 'dart:convert';
    import 'dart:math';

    import 'package:firebase_messaging/firebase_messaging.dart';
    import 'package:flutter_local_notifications/flutter_local_notifications.dart';

    import '../utils/logger/logger.dart';

    const _channelId = '<your_channel_id>';
    const _channelDeception = '<your_channel_desc>';
    const _channelTitle = '<your_channel_title>';
    const _topicKey = 'general';

    class NotificationService {
    factory NotificationService() {
    return _instance ??= NotificationService._();
    }

    NotificationService._();
    static NotificationService? _instance;

    final _firebaseMessaging = FirebaseMessaging.instance;

    final _flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();

    var _didInitializeNotificationService = false;
    Future<void> initialize() async {
    if (_didInitializeNotificationService) {
    Logger.w('You already initialized the notification service. '
    'Ignoring the reinitialize request!');
    return;
    }

    final settings = await _firebaseMessaging.requestPermission();

    if (settings.authorizationStatus != AuthorizationStatus.authorized) {
    return;
    }

    unawaited(_firebaseMessaging.subscribeToTopic(_topicKey));

    await _initLocalNotificationPackage();

    _startNotificationsListener();

    _didInitializeNotificationService = true;
    }

    Future<void> _initLocalNotificationPackage() async {
    await _flutterLocalNotificationsPlugin
    .resolvePlatformSpecificImplementation<
    AndroidFlutterLocalNotificationsPlugin>()
    ?.createNotificationChannel(
    const AndroidNotificationChannel(
    _channelId,
    _channelTitle,
    description: _channelDeception,
    importance: Importance.max,
    enableLights: true,
    ),
    );

    const initializationSettingsAndroid =
    AndroidInitializationSettings('@mipmap/ic_stat_notification_icon');

    const darwinInitializationSettings = DarwinInitializationSettings();

    const initializationSettings = InitializationSettings(
    android: initializationSettingsAndroid,
    iOS: darwinInitializationSettings,
    );

    await _flutterLocalNotificationsPlugin.initialize(
    initializationSettings,
    onDidReceiveNotificationResponse: (details) {
    if (details.payload != null) {
    _handleNotificationTap(
    json.decode(details.payload!) as Map<String, dynamic>,
    );
    }
    },
    );
    }

    bool _isNotificationListenerInitialized = false;

    void _startNotificationsListener() {
    if (_isNotificationListenerInitialized) {
    Logger.w('You already started the notification Listener. '
    'Ignoring the start request!');
    return;
    }
    _isNotificationListenerInitialized = true;

    // show notifications
    FirebaseMessaging.onMessage.listen(_showNotification);
    }

    bool _isTapListenersInitialized = false;

    Future<void> startTapNotificationsListener() async {
    assert(
    _didInitializeNotificationService,
    'Did you forget to initialize the notification service?',
    );

    if (_isTapListenersInitialized) {
    return;
    }
    _isTapListenersInitialized = true;

    // handle notification tap

    final remoteInitialMessage =
    await FirebaseMessaging.instance.getInitialMessage();
    if (remoteInitialMessage != null) {
    _handleNotificationTap(remoteInitialMessage.data);
    }

    FirebaseMessaging.onMessageOpenedApp.listen((remoteMessage) {
    _handleNotificationTap(remoteMessage.data);
    });

    final notificationAppLaunchDetails = await _flutterLocalNotificationsPlugin
    .getNotificationAppLaunchDetails();

    if (notificationAppLaunchDetails != null) {
    final payload =
    notificationAppLaunchDetails.notificationResponse?.payload;
    if (payload != null) {
    _handleNotificationTap(json.decode(payload) as Map<String, dynamic>);
    }
    }
    }

    Future<void> _showNotification(RemoteMessage message) async {
    final bigTextStyleInformation = BigTextStyleInformation(
    message.notification?.body.toString() ?? '',
    contentTitle: message.notification?.title.toString(),
    );

    final androidPlatformChannelSpecifics = AndroidNotificationDetails(
    _channelId,
    _channelTitle,
    channelDescription: _channelDeception,
    importance: Importance.max,
    styleInformation: bigTextStyleInformation,
    priority: Priority.max,
    );

    final platformChannelSpecifics =
    NotificationDetails(android: androidPlatformChannelSpecifics);

    await _flutterLocalNotificationsPlugin.show(
    Random().nextInt(99999),
    message.notification?.title,
    message.notification?.body,
    platformChannelSpecifics,
    payload: json.encode(message.data),
    );
    }
    }

    void _handleNotificationTap(Map<String, dynamic> payload) {
    // handel notification tap
    }