Search code examples
flutterdartpush-notificationschedule

Flutter scheduled push notifications


I need to schedule PUSH notification from one device to another. I have the code which sends push notification in time, how can I improve it to have scheduled noti?

class PushNotificationService {
  static const serviceAccountJson = {
    <here is json>
  };

  static Future<String> getAccessToken() async {
    List<String> scopes = [
      "https://www.googleapis.com/auth/userinfo.email",
      "https://www.googleapis.com/auth/firebase.database",
      "https://www.googleapis.com/auth/firebase.messaging"
    ];

    final client = await auth.clientViaServiceAccount(
      auth.ServiceAccountCredentials.fromJson(serviceAccountJson),
      scopes,
    );

    final credentials = await auth.obtainAccessCredentialsViaServiceAccount(
      auth.ServiceAccountCredentials.fromJson(serviceAccountJson),
      scopes,
      client,
    );

    client.close();

    return credentials.accessToken.data;
  }

  static Future<void> sendPushNotification(
      String deviceToken, String title, String body ,String tripID) async {
    final String serverKey = await getAccessToken();
    String endpointFirebaseCloudMessaging =
        'https://fcm.googleapis.com/v1/projects/rd-tasks-mobi/messages:send';

    final Map<String, dynamic> message = {
      'message': {
        'token': deviceToken,
        'notification': {
          'title': title,
          'body': body,
        },
        'data': {
          'tripID': tripID,
        },
      }
    };
    final http.Response response = await http.post(
      Uri.parse(endpointFirebaseCloudMessaging),
      headers: <String, String>{
        'Authorization': 'Bearer $serverKey',
        'Content-Type': 'application/json',
      },
      body: jsonEncode(message),
    );
    if (response.statusCode == 200) {
      print('Success');
    } else {
      print('Error: ${response.body}');
    }
  }
}

Firebase Cloud Messaging uses https from June 20 2024, and it's too less info about this type of connecting


Solution

  • I found the decision by using AWESOME_NOTIFICATION. You need to connect sending push notification using Firebase Cloud Messaging API (V1) by https, to send push to another device using device token (I saved it in my database).

    It will look like this:

    class PushNotificationService {
      static const serviceAccountJson = {
        'here is secure json file, which you need to download from firebase(if you use this db)'
      };
    
      static Future<String> getAccessToken() async {
        List<String> scopes = [
          "https://www.googleapis.com/auth/userinfo.email",
          "https://www.googleapis.com/auth/firebase.database",
          "https://www.googleapis.com/auth/firebase.messaging"
        ];
    
        final client = await auth.clientViaServiceAccount(
          auth.ServiceAccountCredentials.fromJson(serviceAccountJson),
          scopes,
        );
    
        final credentials = await auth.obtainAccessCredentialsViaServiceAccount(
          auth.ServiceAccountCredentials.fromJson(serviceAccountJson),
          scopes,
          client,
        );
    
        client.close();
    
        return credentials.accessToken.data;
      }
    
      static Future<void> sendPushNotification(String deviceToken, String title,
          String body, String tripID, DateTime? date) async {
        final String serverKey = await getAccessToken();
        String endpointFirebaseCloudMessaging =
            'https://fcm.googleapis.com/v1/projects/PROJECT-NAME/messages:send';
    
        final Map<String, dynamic> message = {
          'message': {
            'token': deviceToken,
            'notification': {
              'title': title,
              'body': body,
            },
            'data': {
              'tripID': tripID,
              'date': date.toString(),//it's for scheduling
            },
          }
        };
        final http.Response response = await http.post(
          Uri.parse(endpointFirebaseCloudMessaging),
          headers: <String, String>{
            'Authorization': 'Bearer $serverKey',
            'Content-Type': 'application/json',
          },
          body: jsonEncode(message),
        );
      }
    }
    

    I transfer the scheduling data like metadata and processit on the receiver side. I created a notification service which process the push it catches

    import 'package:awesome_notifications/awesome_notifications.dart';
    
    class NotificationService {
      static Future<void> initializeNotification() async {
        await AwesomeNotifications().initialize(
          null,
          [
            NotificationChannel(
              channelGroupKey: 'basic_channel',
              channelKey: 'basic_channel',
              channelName: 'Basic Notification',
              channelDescription: 'Notification channel for basic test',
              defaultColor: AppColors.grey,
              ledColor: Colors.white,
              importance: NotificationImportance.Max,
              channelShowBadge: true,
              onlyAlertOnce: true,
              playSound: true,
              criticalAlerts: true,
            )
          ],
          channelGroups: [
            NotificationChannelGroup(
                channelGroupKey: 'basic_channel_group',
                channelGroupName: 'Group 1'),
          ],
          debug: true,
        );
    
        await AwesomeNotifications()
            .isNotificationAllowed()
            .then((isAllowed) async {
          if (!isAllowed) {
            await AwesomeNotifications().requestPermissionToSendNotifications();
          }
        });
    
        await AwesomeNotifications().setListeners(
            onActionReceivedMethod: onActionReceivedMethod,
            onNotificationCreatedMethod: onNotificationCreatedMethod,
            onNotificationDisplayedMethod: onNotificationDisplayedMethod,
            onDismissActionReceivedMethod: onDismissActionReceivedMethod);
      }
    
      static Future<void> onNotificationCreatedMethod(
          ReceivedNotification receivedNotification) async {
        debugPrint("onNotificationCreatedMethod");
      }
    
      static Future<void> onNotificationDisplayedMethod(
          ReceivedNotification receivedNotification) async {
        debugPrint("onNotificationDisplayedMethod");
      }
    
      static Future<void> onDismissActionReceivedMethod(
          ReceivedAction receivedAction) async {
        debugPrint("onDismissActionReceivedMethod");
      }
    
      static Future<void> onActionReceivedMethod(
          ReceivedAction receivedAction) async {
        debugPrint("onActionReceivedMethod");
        final payload = receivedAction.payload ?? {};
       
      }
    
      static Future<void> cancelNotificationByTripID(String tripID) async {
        List<NotificationModel> notifications =
            await AwesomeNotifications().listScheduledNotifications();
    
        for (var notification in notifications) {
          if (notification.content!.payload!['tripID'] == tripID) {
            await AwesomeNotifications().cancel(notification.content!.id!);
          }
        }
      }
    

    //this metod is for generating unique id. I need it to have a possibility to delete some notifications in future static int generateUniqueID() { final now = DateTime.now(); final String idString = '${now.year}${_twoDigits(now.month)}${_twoDigits(now.day)}' '${_twoDigits(now.hour)}${_twoDigits(now.minute)}${_twoDigits(now.second)}' '${now.millisecond.toString().padLeft(3, '0')}'; return idString.hashCode & 0x7FFFFFFF; }

      static String _twoDigits(int n) {
        return n.toString().padLeft(2, '0');
      }
    
      static Future<void> showNotification({
        required final String title,
        required final String body,
        final String? summary,
        final Map<String, String>? payload,
        final ActionType actionType = ActionType.Default,
        final NotificationLayout notificationLayout = NotificationLayout.Default,
        final NotificationCategory? category,
        final String? bigPicture,
        final List<NotificationActionButton>? actionButtons,
        final bool scheduled = false,
        final DateTime? scheduledDateTime,
      }) async {
        assert(!scheduled || (scheduled && scheduledDateTime != null));
    
        final int uniqueId = generateUniqueID();
        await AwesomeNotifications().createNotification(
          content: NotificationContent(
            id: uniqueId,
            channelKey: 'basic_channel',
            title: title,
            body: body,
            actionType: actionType,
            notificationLayout: notificationLayout,
            summary: summary,
            category: category,
            payload: payload,
            bigPicture: bigPicture,
          ),
          actionButtons: actionButtons,
          schedule: scheduled
              ? NotificationCalendar.fromDate(date: scheduledDateTime!)
              : null,
        );
      }
    }
    

    showNotification process push and show it to user even the noti is scheduled, but we need to correctly pass the data to the function you need to add this in your main.dart

    FirebaseMessaging.onMessage.listen((RemoteMessage message) {
        if (message.notification != null) {
          if (message.data['date'] != 'null') {
            DateTime dateTime = DateTime.parse(message.data['date']);
            NotificationService.showNotification(
              title: message.notification!.title ?? 'No title',
              body: message.notification!.body ?? 'No body',
              scheduled: true,
              scheduledDateTime: dateTime,
              payload: {'tripID': '${message.data['tripID']}'},
            );
          } else {
            NotificationService.showNotification(
              title: message.notification!.title ?? 'No title',
              body: message.notification!.body ?? 'No body',
              payload: {'tripID': '${message.data['tripID']}'},
            );
          }
        }
      });
    

    and this code for background handling

    Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
      await Firebase.initializeApp();
      if (message.notification != null) {
        if (message.data['date'] != 'null') {
          DateTime dateTime = DateTime.parse(message.data['date']);
          NotificationService.showNotification(
              title: message.notification!.title ?? 'No title',
              body: message.notification!.body ?? 'No body',
              scheduled: true,
              scheduledDateTime: dateTime);
        } else {
          NotificationService.showNotification(
            title: message.notification!.title ?? 'No title',
            body: message.notification!.body ?? 'No body',
          );
        }
      }
    }