Search code examples
flutterflutter-local-notification

Flutter local notifications daily scheduled notification


I've been trying to get Flutter local notifications to work since a week but can't get it to work. Basically the issue is whenever i create a daily notification, it works only for the first time and then it doesn't show notifications every next day. Suppose if i set daily scheduled notification at 12:20 PM, it will show notification at 12:20 PM for first time, then the next day it won't show. And when i see the list of pending notifications i can see the notification still present.

here's all my notification code

class NotificationService {
  // Singleton pattern
  static final NotificationService _notificationService =
      NotificationService._internal();
  factory NotificationService() {
    return _notificationService;
  }

  NotificationService._internal();

  static const channelId = "1";

  final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
      FlutterLocalNotificationsPlugin();

  static const AndroidNotificationDetails _androidNotificationDetails =
      AndroidNotificationDetails(
    channelId,
    "thecodexhub",
    channelDescription:
        "This channel is responsible for all the local notifications",
    playSound: true,
    priority: Priority.high,
    importance: Importance.high,
  );

  static const IOSNotificationDetails _iOSNotificationDetails =
      IOSNotificationDetails();

  final NotificationDetails notificationDetails = const NotificationDetails(
    android: _androidNotificationDetails,
    iOS: _iOSNotificationDetails,
  );

  Future<void> init() async {
    const AndroidInitializationSettings androidInitializationSettings =
        AndroidInitializationSettings('@mipmap/ic_launcher');

    const IOSInitializationSettings iOSInitializationSettings =
        IOSInitializationSettings(
      defaultPresentAlert: false,
      defaultPresentBadge: false,
      defaultPresentSound: false,
    );

    const InitializationSettings initializationSettings =
        InitializationSettings(
      android: androidInitializationSettings,
      iOS: iOSInitializationSettings,
    );

    // *** Initialize timezone here ***
    tz.initializeTimeZones();

    await flutterLocalNotificationsPlugin.initialize(
      initializationSettings,
      onSelectNotification: onSelectNotification,
    );
  }

  Future<void> requestIOSPermissions() async {
    await flutterLocalNotificationsPlugin
        .resolvePlatformSpecificImplementation<
            IOSFlutterLocalNotificationsPlugin>()
        ?.requestPermissions(
          alert: true,
          badge: true,
          sound: true,
        );
  }

  Future<void> showNotification(
      int id, String title, String body, String payload) async {
    await flutterLocalNotificationsPlugin.show(
      id,
      title,
      body,
      notificationDetails,
      payload: payload,
    );
  }

  Future<void> scheduleNotification(int id, String title, String body,
      DateTime eventDate, TimeOfDay eventTime, String payload,
      [DateTimeComponents? dateTimeComponents]) async {
    final scheduledTime = eventDate.add(Duration(
      hours: eventTime.hour,
      minutes: eventTime.minute,
    ));
    await flutterLocalNotificationsPlugin.zonedSchedule(
      id,
      title,
      body,
      tz.TZDateTime.from(scheduledTime, tz.local),
      notificationDetails,
      uiLocalNotificationDateInterpretation:
          UILocalNotificationDateInterpretation.absoluteTime,
      androidAllowWhileIdle: true,
      payload: payload,
      matchDateTimeComponents: dateTimeComponents,
    );
  }

  Future<void> cancelNotification(int id) async {
    await flutterLocalNotificationsPlugin.cancel(id);
  }

  Future<void> cancelAllNotifications() async {
    await flutterLocalNotificationsPlugin.cancelAll();
  }

  Future getNotifications() async {
    final List<PendingNotificationRequest> pendingNotificationRequests =
        await FlutterLocalNotificationsPlugin().pendingNotificationRequests();
    return pendingNotificationRequests;
  }
}

Future<void> onSelectNotification(String? payload) async {
  // await navigatorKey.currentState
  //     ?.push(MaterialPageRoute(builder: (_) => DetailsPage(payload: payload)));
}

and here's how i'm calling it.

 await notificationService.scheduleNotification(
      1,
      _textEditingController.text,
      "Reminder for your scheduled event at ${eventTime!.format(context)}",
      eventDate!,
      eventTime!,
      jsonEncode({
        "title": _textEditingController.text,
        "eventDate": DateFormat("EEEE, d MMM y").format(eventDate!),
        "eventTime": eventTime!.format(context),
      }),
      getDateTimeComponents(),
    );
  }

here is getDateTimeComponents if it matters

  DateTimeComponents? getDateTimeComponents() {
    if (segmentedControlGroupValue == 1) {
      return DateTimeComponents.time;
    } else if (segmentedControlGroupValue == 2) {
      return DateTimeComponents.dayOfWeekAndTime;
    }
  }

it's been week since i'm trying to fix this issue. Thank you for reading.


Solution

  • For anyone looking for answer, here is the solution.

    Main

    void main() async {
      WidgetsFlutterBinding.ensureInitialized();
      await initNotifications();
      runApp(const MyApp());
    }
    
    Future<void> initNotifications() async {
      NotificationService notificationService = NotificationService();
      await notificationService.init();
      if (Platform.isIOS) {
        await notificationService.requestIOSPermissions();
      } else {
        await notificationService.requestAndroidPermission();
      }
    }
    

    Notifications

    import 'package:flutter/cupertino.dart';
    import 'package:flutter/material.dart';
    import 'package:flutter_local_notifications/flutter_local_notifications.dart';
    import 'package:flutter_timezone/flutter_timezone.dart';
    import 'package:timezone/data/latest.dart' as tz;
    import 'package:timezone/timezone.dart' as tz;
    
    @pragma('vm:entry-point')
    void backgroundNotificationHandler(NotificationResponse? details) async {}
    
    Future<void> onNotificationTapped(NotificationResponse? payload) async {
    }
    
    class NotificationService {
      static final NotificationService _notificationService =
          NotificationService._internal();
      factory NotificationService() {
        return _notificationService;
      }
    
      Future<NotificationResponse?> getInitialNotification() async {
        final launchDetails =
            await flutterLocalNotificationsPlugin.getNotificationAppLaunchDetails();
        if (launchDetails?.didNotificationLaunchApp ?? false) {
          return NotificationResponse(
              notificationResponseType:
                  NotificationResponseType.selectedNotification,
              payload: launchDetails!.notificationResponse!.payload);
        }
        return null;
      }
    
      NotificationService._internal();
    
      static const channelId = "1";
    
      final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
          FlutterLocalNotificationsPlugin();
    
      static const AndroidNotificationDetails _androidNotificationDetails =
          AndroidNotificationDetails(
        channelId,
        "randomString",
        channelDescription:
            "This channel is responsible for all the local notifications",
        playSound: true,
        priority: Priority.high,
        importance: Importance.high,
      );
    
      static const DarwinNotificationDetails _darwinNotificationDetails =
          DarwinNotificationDetails();
    
      final NotificationDetails notificationDetails = const NotificationDetails(
        android: _androidNotificationDetails,
        iOS: _darwinNotificationDetails,
      );
    
      void onDidReceiveLocalNotification(
          int id, String? title, String? body, String? payload) async {
        showDialog(
          context: dialogKey.currentContext!,
          builder: (BuildContext context) => CupertinoAlertDialog(
            title: Text(title ?? ''),
            content: Text(body ?? ''),
            actions: [
              CupertinoDialogAction(
                isDefaultAction: true,
                child: const Text('Ok'),
                onPressed: () async {
                  Navigator.of(context, rootNavigator: true).pop();
                },
              )
            ],
          ),
        );
      }
    
      Future<void> init() async {
        const AndroidInitializationSettings androidInitializationSettings =
            AndroidInitializationSettings('@mipmap/ic_launcher');
    
        final DarwinInitializationSettings initializationSettingsDarwin =
            DarwinInitializationSettings(
                onDidReceiveLocalNotification: onDidReceiveLocalNotification);
    
        final InitializationSettings initializationSettings =
            InitializationSettings(
          android: androidInitializationSettings,
          iOS: initializationSettingsDarwin,
        );
    
        // *** Initialize timezone here ***
        tz.initializeTimeZones();
        final String currentTimeZone = await FlutterTimezone.getLocalTimezone();
        tz.setLocalLocation(tz.getLocation(currentTimeZone));
    
        await flutterLocalNotificationsPlugin.initialize(
          initializationSettings,
          onDidReceiveBackgroundNotificationResponse: backgroundNotificationHandler,
          onDidReceiveNotificationResponse: onNotificationTapped,
        );
      }
    
      Future<void> requestAndroidPermission() async {
        await flutterLocalNotificationsPlugin
            .resolvePlatformSpecificImplementation<
                AndroidFlutterLocalNotificationsPlugin>()!
            .requestPermission();
      }
    
      Future<void> requestIOSPermissions() async {
        await flutterLocalNotificationsPlugin
            .resolvePlatformSpecificImplementation<
                IOSFlutterLocalNotificationsPlugin>()
            ?.requestPermissions(
              alert: true,
              badge: true,
              sound: true,
            );
      }
    
      Future<void> showNotification(
          int id, String title, String body, String payload) async {
        await flutterLocalNotificationsPlugin.show(
          id,
          title,
          body,
          notificationDetails,
          payload: payload,
        );
      }
    
      Future<void> scheduleNotification(
          int id,
          String title,
          String body,
          DateTime eventDate,
          TimeOfDay eventTime,
          String payload,
          String time,
          int? hours,
          [DateTimeComponents? dateTimeComponents]) async {
        eventDate = DateTime(eventDate.year, eventDate.month, eventDate.day);
    
        final scheduledTime = eventDate.add(Duration(
          hours: eventTime.hour,
          minutes: eventTime.minute,
        ));
    
        tz.TZDateTime nextInstanceOfTenAM() {
          final tz.TZDateTime now = tz.TZDateTime.now(tz.local);
          tz.TZDateTime scheduledDate = tz.TZDateTime.from(scheduledTime, tz.local);
    
          if (time == 'daily') {
            if (scheduledDate.isBefore(now)) {
              scheduledDate = scheduledDate.add(const Duration(days: 1));
            }
          } else if (time == 'hourly') {
            if (scheduledDate.isBefore(now)) {
              scheduledDate = scheduledDate.add(Duration(hours: hours!));
            }
          }
          return scheduledDate;
        }
    
        await flutterLocalNotificationsPlugin.zonedSchedule(
          id,
          title,
          body,
          nextInstanceOfTenAM(),
          notificationDetails,
          uiLocalNotificationDateInterpretation:
              UILocalNotificationDateInterpretation.absoluteTime,
          androidScheduleMode: AndroidScheduleMode.exactAllowWhileIdle,
          payload: payload,
          matchDateTimeComponents: dateTimeComponents,
        );
      }
    
      Future<void> cancelNotification(int id) async {
        await flutterLocalNotificationsPlugin.cancel(id);
      }
    
      Future<void> cancelAllNotifications() async {
        await flutterLocalNotificationsPlugin.cancelAll();
      }
    
      Future<List<PendingNotificationRequest>> getNotifications() async {
        final List<PendingNotificationRequest> pendingNotificationRequests =
            await FlutterLocalNotificationsPlugin().pendingNotificationRequests();
        return pendingNotificationRequests;
      }
    }