MaikuB / flutter_local_notifications

A Flutter plugin for displaying local notifications on Android, iOS, macOS and Linux
2.47k stars 1.4k forks source link

onDidReceiveNotificationResponse DID NOT TRIGGER WHEN APP IS IN FOREGROUD #2390

Open Abdulah-butt opened 2 months ago

Abdulah-butt commented 2 months ago
import 'dart:async';
import 'dart:convert';
import 'dart:io';

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:flutter_timezone/flutter_timezone.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:sos_cameroon/app/common/shared_preference_manager.dart';

import 'package:sos_cameroon/src/features/notifications/data/repositories/local_notification_service.dart';
import 'package:timezone/data/latest_all.dart' as tz;
import 'package:timezone/timezone.dart' as tz;

import '../../models/schedule_notification_model.dart';

@pragma('vm:entry-point')
void notificationTapBackground(NotificationResponse notificationResponse) {
  // ignore: avoid_print
  print('notification(${notificationResponse.id}) action tapped: '
      '${notificationResponse.actionId} with'
      ' payload: ${notificationResponse.payload}');
  if (notificationResponse.input?.isNotEmpty ?? false) {
    // ignore: avoid_print
    print(
        'notification action tapped with input: ${notificationResponse.input}');
  }
}

class FlutterLocalNotification implements LocalNotificationService {
  final SharedPreferences _sharedPreferences =
      SharedPreferencesManager.instance;
  final StreamController<String?> _selectNotificationStream =
      StreamController<String?>.broadcast();

  final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
      FlutterLocalNotificationsPlugin();

  final AndroidNotificationDetails _channel = const AndroidNotificationDetails(
      'default_notification_channel_id', // id
      'High Importance Notifications', // title
      // description
      importance: Importance.high,
      enableLights: true,
      playSound: true);

  @override
  Future<void> initialize() async {
    await _configureLocalTimeZone();
    await _configureNotifications();
  }

  Future<bool> _isAlreadyScheduled() async {
    return _sharedPreferences.getBool("local_notification_schedule") ?? false;
  }

  Future<bool> _localNotificationScheduled() async {
    return _sharedPreferences.setBool("local_notification_schedule", true);
  }

  Future<void> _configureNotifications() async {
    const AndroidInitializationSettings initializationSettingsAndroid =
        AndroidInitializationSettings('@mipmap/ic_launcher');
    final DarwinInitializationSettings initializationSettingsDarwin =
        DarwinInitializationSettings(
      requestAlertPermission: true,
      requestBadgePermission: true,
      requestSoundPermission: true,
      onDidReceiveLocalNotification:
          (int id, String? title, String? body, String? payload) async {},
    );
    final InitializationSettings initializationSettings =
        InitializationSettings(
      android: initializationSettingsAndroid,
      iOS: initializationSettingsDarwin,
      macOS: initializationSettingsDarwin,
    );

    await flutterLocalNotificationsPlugin.initialize(
      initializationSettings,
      onDidReceiveNotificationResponse:onNotificationTap,
      onDidReceiveBackgroundNotificationResponse: notificationTapBackground,
    );

  }

// on tap on any notification
  static void onNotificationTap(NotificationResponse notificationResponse) {
    // _selectNotificationStream.add(notificationResponse.payload!);
    debugPrint("NOTIFICATION IS TAPPED +++++++++++");
  }

  @override
  Future<String?> notificationPayloadFromWhichAppOpened() async {
    final NotificationAppLaunchDetails? notificationAppLaunchDetails =
        !kIsWeb && Platform.isLinux
            ? null
            : await flutterLocalNotificationsPlugin
                .getNotificationAppLaunchDetails();
    if (notificationAppLaunchDetails?.didNotificationLaunchApp ?? false) {
      String? selectedNotificationPayload =
          notificationAppLaunchDetails!.notificationResponse?.payload;
      return selectedNotificationPayload;
    }
    return null;
  }

  @override
  Future<void> requestPermissions() async {
    await _requestPermissions();
  }

  @override
  Future<void> scheduleNotification(
      {required int id,
      required String title,
      required String description,
      String? payload,
      required Duration durationFromCurrentTime}) async {
    debugPrint(
        "NOTIFICATION SCHEDULED AT+++++++ $durationFromCurrentTime ++++ ${tz.TZDateTime.now(tz.local).add(durationFromCurrentTime)}");
    await flutterLocalNotificationsPlugin.zonedSchedule(
        id,
        title,
        description,
        payload: payload,
        tz.TZDateTime.now(tz.local).add(durationFromCurrentTime),
        NotificationDetails(android: _channel),
        androidScheduleMode: AndroidScheduleMode.exactAllowWhileIdle,
        uiLocalNotificationDateInterpretation:
            UILocalNotificationDateInterpretation.absoluteTime);
  }

  Future<void> _configureLocalTimeZone() async {
    if (kIsWeb || Platform.isLinux) {
      return;
    }
    tz.initializeTimeZones();
    final String timeZoneName = await FlutterTimezone.getLocalTimezone();
    tz.setLocalLocation(tz.getLocation(timeZoneName));
  }

  Future<void> _requestPermissions() async {
    if (Platform.isIOS || Platform.isMacOS) {
      await flutterLocalNotificationsPlugin
          .resolvePlatformSpecificImplementation<
              IOSFlutterLocalNotificationsPlugin>()
          ?.requestPermissions(
            alert: true,
            badge: true,
            sound: true,
          );
      await flutterLocalNotificationsPlugin
          .resolvePlatformSpecificImplementation<
              MacOSFlutterLocalNotificationsPlugin>()
          ?.requestPermissions(
            alert: true,
            badge: true,
            sound: true,
          );
    } else if (Platform.isAndroid) {
      final AndroidFlutterLocalNotificationsPlugin? androidImplementation =
          flutterLocalNotificationsPlugin.resolvePlatformSpecificImplementation<
              AndroidFlutterLocalNotificationsPlugin>();
      final bool? grantedNotificationPermission =
          await androidImplementation?.requestNotificationsPermission();
      // If the app requires scheduling notifications with exact timings
      await AndroidFlutterLocalNotificationsPlugin()
          .requestExactAlarmsPermission();
    }
  }

  @override
  Future<void> scheduleAllNotificationsFromJson() async {
    // 1. FIRST CHECK IF NOTIFICATION ALREADY SCHEDULES OR NOT

    // if(await _isAlreadyScheduled()){
    //   debugPrint("NOTIFICATIONS ARE ALREADY SCHEDULED ++++++");
    //   return;
    // }

    List<ScheduleNotificationModel> scheduleNotifications =
        await _loadNotifications();
    for (int index = 0; index < scheduleNotifications.length; index++) {
      ScheduleNotificationModel notification = scheduleNotifications[index];
      LocaleData localeData = notification.locales!['en']!;
      scheduleNotification(
        id: index,
        title: localeData.title ?? "N/A",
        description: localeData.description ?? "N/A",
        payload: json.encode(notification.toJson()),
        durationFromCurrentTime: Duration(
          days: ((notification.day ?? 1) - 1),
          hours: notification.hour ?? 0,
          minutes: notification.minute ?? 0,
          seconds: notification.second ?? 0,
        ),
      );
    }
    _localNotificationScheduled();
  }

  /// Read json according to current locale
  Future<List<ScheduleNotificationModel>> _loadNotifications() async {
    // final  currentLocale = Localizations.localeOf(context);
    // debugPrint("CURRENT LOCALE : ${currentLocale.languageCode}");
    final jsonString =
        await rootBundle.loadString('assets/local_notifications/mock.json');
    final jsonData = json.decode(jsonString) as List<dynamic>;
    return jsonData
        .map((json) => ScheduleNotificationModel.fromJson(json))
        .toList();
  }

  @override
  Stream<String?> selectNotificationStream() {
    return _selectNotificationStream.stream;
  }
}
Roshan-pcy commented 2 months ago

i have face same issue did get any solution?

AmirmohammadNemati commented 2 months ago

i have this problem too

ced1check commented 2 months ago

Same problem here, running on Android, any solutions, are we missing some specific setup? Will try iOS pretty soon.

ced1check commented 2 months ago

Worked-around using response here: https://github.com/MaikuB/flutter_local_notifications/issues/2315#issuecomment-2136799521

MaikuB commented 1 month ago

For those that say that they are facing this issue, check to ensure you have done all the configuration mentioned in the readme. If you have done so and still face an issue, please provide a link to a repository hosting a minimal app that can reproduce the problem. This could be one you've created yourself or another way is to fork the repository and update the example app

Edit: please also mention the platform you encounter the issue on. If you provide a the repo, also include mention that Flutter version. Expectation is I should be able to clone the repository and reproduce the issue easily. Providing code snippets doesn't help as it's missing the native configuration

itsatishay commented 1 month ago

@MaikuB On tap not working for me as well, in IOS.

return await _flutterLocalNotificationsPlugin.initialize( // ignore: prefer_const_constructors InitializationSettings( android: const AndroidInitializationSettings('@mipmap/ic_launcher'), iOS: const DarwinInitializationSettings(onDidReceiveLocalNotification: handleForegroundPayload)), onDidReceiveNotificationResponse: handlePayload, );

Both the handleForegroundPayload or handlePayload never gets invoked when a user taps on the notification when app is in foreground.

Is there any specific IOS configuration that we need to do to handle on tap in IOS for foreground?
Do we need to create/add any notificationCategories during initialization to handle this?  
MaikuB commented 3 weeks ago

@itsatishay the configuration needed is in the readme. You'll need to provide a link to repo hosting a minimal app that can reproduce the issue. I've asked in other threads that the community provides a link to a minimal repo hosting an app to reproduce the problem but haven't gotten a (favourable) response. This is the best way to identify if there is a legitimate issue or that developers haven't completed the expected configuration

marcellocamara commented 2 weeks ago

@Abdulah-butt could you provide a repository link of your example code for MaikuB investigate ?