最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

dart - Duplicate Notifications with Appwrite and FCM on Flutter - Stack Overflow

programmeradmin4浏览0评论

Description

I'm using an Appwrite function triggered by a cron job to schedule daily notifications via FCM. While notifications are sent at the correct time, I'm occasionally receiving duplicate notifications on the same device.

What I've Checked

  • Scheduling Logic: The scheduling function updates the existing notification when called multiple times.
  • Message IDs: Each message ID is unique for the combination of target (device) and date.
  • Appwrite Console: Every device has exactly one notification target, and each target is included in one notification schedule.
  • App-side Code: My app isn't processing background notifications in a way that could cause duplicates.

Example Screenshot

Code

Scheduler Appwrite Function

Future<ResponseModel> setRemindersForUser(
    context, User user, Messaging messaging) async {
  context.log('processing user ${user.name}');
  // Retrieve user's timezone offset
  final timezoneOffset = user.prefs.data['timezone'];

  if (timezoneOffset != null) {
    context.log('retrieved timezone offset: $timezoneOffset');
    // Parse timezone offset
    context.log('Original timezone offset: $timezoneOffset');
    final isOffsetNegative = timezoneOffset.startsWith('-');
    final pureOffsetDuration =
        timezoneOffset.replaceAll('-', '').split('.').first;
    context.log('Pure offset duration: $pureOffsetDuration');
    final offsetHour = int.parse(pureOffsetDuration.split(':')[0]);
    final offsetMin = int.parse(pureOffsetDuration.split(':')[1]);
    final offsetSec = int.parse(pureOffsetDuration.split(':')[2]);
    final offsetDuration = Duration(
      hours: offsetHour,
      minutes: offsetMin,
      seconds: offsetSec,
    );
    context.log('Offset duration: $offsetDuration');

    // Calculate next 9 PM in user's local time
    final now = DateTime.now().toUtc();
    context.log('Current UTC time: $now');
    final userTime = isOffsetNegative
        ? now.subtract(offsetDuration)
        : now.add(offsetDuration);
    context.log('Current user time: $userTime');
    DateTime next9PM =
        DateTime(userTime.year, userTime.month, userTime.day, 21);
    if (userTime.isAfter(next9PM)) {
      next9PM = next9PM.add(Duration(days: 1));
    }
    context.log('Next 9 PM user time: $next9PM');

    // Convert next9PM to UTC
    final next9PMUtc = isOffsetNegative
        ? next9PM.add(offsetDuration)
        : next9PM.subtract(offsetDuration);

    final messageId = _generateMessageId(user, next9PMUtc);
    late Function(
        {required String messageId,
        required String title,
        required String body,
        List<String>? topics,
        List<String>? users,
        List<String>? targets,
        Map? data,
        String? action,
        String? image,
        String? icon,
        String? sound,
        String? color,
        String? tag,
        bool? draft,
        String? scheduledAt}) scheduler;
    try {
      context.log('check existing message');
      // update if message already scheduled
      await messaging.getMessage(messageId: messageId);
      // no error thrown means message exists
      scheduler = messaging.updatePush;
      context.log('found existing message');
    } catch (e) {
      // create if message not scheduled
      context.log('no existing message found');
      scheduler = messaging.createPush;
      context.log('should create new message');
      context.log(e.toString());
    }
    // Schedule push notification
    context.log('scheduling push notification');
    final userPushTargets = user.targets
        .where((element) => element.providerType == "push")
        .toList();
    context.log('user push targets: ${jsonEncode(userPushTargets.map(
          (e) => e.toMap(),
        ).toList())}');
    try {
      final content = dynamicNotifications.random;
      final result = await scheduler(
        messageId: messageId,
        title: content.$1,
        body: content.$2,
        scheduledAt: next9PMUtc.toIso8601String(),
        targets: userPushTargets
            .map(
              (e) => e.$id,
            )
            .toList(),
      );
      context
          .log('scheduled push notification!: $messageId - ${result.toMap()}');
      return ResponseModel.success(null);
    } catch (e) {
      context.log(e.toString());
      return ResponseModel.failed(message: e.toString());
    }
  }
  return ResponseModel.failed(message: 'Timezone offset not found');
}

Flutter App Notification Handler Code

static Future<bool> initialize() async {
  try {
    final result = await FirebaseMessaging.instance.requestPermission();
    if (Platform.isAndroid) {
      await localNotifications
          .resolvePlatformSpecificImplementation<
              AndroidFlutterLocalNotificationsPlugin>()
          ?.createNotificationChannel(reminderChannel);
    }
    await localNotifications.initialize(
      const InitializationSettings(
        android: AndroidInitializationSettings('@mipmap/ic_launcher'),
        iOS: DarwinInitializationSettings(),
      ),
    );
    _addOnReceiveMessageListener();
    _addTokenRefreshListener();
    return result.authorizationStatus == AuthorizationStatus.authorized;
  } catch (e) {
    log(e.toString());
  }
  return false;
}

static void _addOnReceiveMessageListener() {
  FirebaseMessaging.onMessage.listen((message) {
    if (message.notification != null) {
      _showLocalNotification(
          title: message.notification!.title ?? '',
          message: message.notification!.body ?? '',
          data: jsonEncode(message.data));
    }
  });
}

static void _showLocalNotification(
    {required String title, required String message, String? data}) {
  FlutterLocalNotificationsPlugin()
      .show(generateTimeBasedId(), title, message,
          _details,
          payload: data)
      .then((_) {});
}

static void _addTokenRefreshListener() {
   FirebaseMessaging.instance.onTokenRefresh.listen((token) async {
      // updating fcm token on appwrite
   });
}

I'm looking for insights or suggestions on what might be causing these duplicate notifications.

Description

I'm using an Appwrite function triggered by a cron job to schedule daily notifications via FCM. While notifications are sent at the correct time, I'm occasionally receiving duplicate notifications on the same device.

What I've Checked

  • Scheduling Logic: The scheduling function updates the existing notification when called multiple times.
  • Message IDs: Each message ID is unique for the combination of target (device) and date.
  • Appwrite Console: Every device has exactly one notification target, and each target is included in one notification schedule.
  • App-side Code: My app isn't processing background notifications in a way that could cause duplicates.

Example Screenshot

Code

Scheduler Appwrite Function

Future<ResponseModel> setRemindersForUser(
    context, User user, Messaging messaging) async {
  context.log('processing user ${user.name}');
  // Retrieve user's timezone offset
  final timezoneOffset = user.prefs.data['timezone'];

  if (timezoneOffset != null) {
    context.log('retrieved timezone offset: $timezoneOffset');
    // Parse timezone offset
    context.log('Original timezone offset: $timezoneOffset');
    final isOffsetNegative = timezoneOffset.startsWith('-');
    final pureOffsetDuration =
        timezoneOffset.replaceAll('-', '').split('.').first;
    context.log('Pure offset duration: $pureOffsetDuration');
    final offsetHour = int.parse(pureOffsetDuration.split(':')[0]);
    final offsetMin = int.parse(pureOffsetDuration.split(':')[1]);
    final offsetSec = int.parse(pureOffsetDuration.split(':')[2]);
    final offsetDuration = Duration(
      hours: offsetHour,
      minutes: offsetMin,
      seconds: offsetSec,
    );
    context.log('Offset duration: $offsetDuration');

    // Calculate next 9 PM in user's local time
    final now = DateTime.now().toUtc();
    context.log('Current UTC time: $now');
    final userTime = isOffsetNegative
        ? now.subtract(offsetDuration)
        : now.add(offsetDuration);
    context.log('Current user time: $userTime');
    DateTime next9PM =
        DateTime(userTime.year, userTime.month, userTime.day, 21);
    if (userTime.isAfter(next9PM)) {
      next9PM = next9PM.add(Duration(days: 1));
    }
    context.log('Next 9 PM user time: $next9PM');

    // Convert next9PM to UTC
    final next9PMUtc = isOffsetNegative
        ? next9PM.add(offsetDuration)
        : next9PM.subtract(offsetDuration);

    final messageId = _generateMessageId(user, next9PMUtc);
    late Function(
        {required String messageId,
        required String title,
        required String body,
        List<String>? topics,
        List<String>? users,
        List<String>? targets,
        Map? data,
        String? action,
        String? image,
        String? icon,
        String? sound,
        String? color,
        String? tag,
        bool? draft,
        String? scheduledAt}) scheduler;
    try {
      context.log('check existing message');
      // update if message already scheduled
      await messaging.getMessage(messageId: messageId);
      // no error thrown means message exists
      scheduler = messaging.updatePush;
      context.log('found existing message');
    } catch (e) {
      // create if message not scheduled
      context.log('no existing message found');
      scheduler = messaging.createPush;
      context.log('should create new message');
      context.log(e.toString());
    }
    // Schedule push notification
    context.log('scheduling push notification');
    final userPushTargets = user.targets
        .where((element) => element.providerType == "push")
        .toList();
    context.log('user push targets: ${jsonEncode(userPushTargets.map(
          (e) => e.toMap(),
        ).toList())}');
    try {
      final content = dynamicNotifications.random;
      final result = await scheduler(
        messageId: messageId,
        title: content.$1,
        body: content.$2,
        scheduledAt: next9PMUtc.toIso8601String(),
        targets: userPushTargets
            .map(
              (e) => e.$id,
            )
            .toList(),
      );
      context
          .log('scheduled push notification!: $messageId - ${result.toMap()}');
      return ResponseModel.success(null);
    } catch (e) {
      context.log(e.toString());
      return ResponseModel.failed(message: e.toString());
    }
  }
  return ResponseModel.failed(message: 'Timezone offset not found');
}

Flutter App Notification Handler Code

static Future<bool> initialize() async {
  try {
    final result = await FirebaseMessaging.instance.requestPermission();
    if (Platform.isAndroid) {
      await localNotifications
          .resolvePlatformSpecificImplementation<
              AndroidFlutterLocalNotificationsPlugin>()
          ?.createNotificationChannel(reminderChannel);
    }
    await localNotifications.initialize(
      const InitializationSettings(
        android: AndroidInitializationSettings('@mipmap/ic_launcher'),
        iOS: DarwinInitializationSettings(),
      ),
    );
    _addOnReceiveMessageListener();
    _addTokenRefreshListener();
    return result.authorizationStatus == AuthorizationStatus.authorized;
  } catch (e) {
    log(e.toString());
  }
  return false;
}

static void _addOnReceiveMessageListener() {
  FirebaseMessaging.onMessage.listen((message) {
    if (message.notification != null) {
      _showLocalNotification(
          title: message.notification!.title ?? '',
          message: message.notification!.body ?? '',
          data: jsonEncode(message.data));
    }
  });
}

static void _showLocalNotification(
    {required String title, required String message, String? data}) {
  FlutterLocalNotificationsPlugin()
      .show(generateTimeBasedId(), title, message,
          _details,
          payload: data)
      .then((_) {});
}

static void _addTokenRefreshListener() {
   FirebaseMessaging.instance.onTokenRefresh.listen((token) async {
      // updating fcm token on appwrite
   });
}

I'm looking for insights or suggestions on what might be causing these duplicate notifications.

Share asked Mar 10 at 8:14 Morteza MohammadiMorteza Mohammadi 3421 gold badge10 silver badges21 bronze badges 5
  • can you share the code which is creating and pushing the notification. I think it is scheduler – Munsif Ali Commented Mar 12 at 8:36
  • @MunsifAli The scheduling code is already in the question. check out the 'Scheduler Appwrite Function'. The push process is handled by AppWrite so this is not on my side. – Morteza Mohammadi Commented Mar 12 at 8:46
  • is Messaging your define type or this is from AppWrite? – Munsif Ali Commented Mar 12 at 9:13
  • and also provide which version of package you are using? – Munsif Ali Commented Mar 12 at 9:29
  • @MunsifAli it's from AppWrite final messaging = Messaging(client);. I'm using dart_appwrite: ^13.0.0 – Morteza Mohammadi Commented Mar 12 at 10:41
Add a comment  | 

1 Answer 1

Reset to default 2 +50

When App is in the background and Notification is sent the the firebase is automatically handling it. and when you again call the show notification method two notifications are displayed. to handle this situation you can either conditionally call the show notification method you can do some changes in the push(while creating notification) in this case don't send title and body directly instead send it in the data by doing so you're sending a "data" message from Firebase Messaging instead of sending a "notification" message. checkout the difference https://firebase.google/docs/cloud-messaging/concept-options#notifications_and_data_messages

schedule notification like this:

 final result = await scheduler(
        messageId: messageId,
        //remove these
       // title: content.$1, body: content.$2, 
        data: {
           "title": content.$1, 
           "body": content.$2, 
        },
        scheduledAt: next9PMUtc.toIso8601String(),
        targets: userPushTargets
            .map(
              (e) => e.$id,
            )
            .toList(),
      );

and when displaying the notification get the title and the body from the data field.

Difference between notification message and data message.

发布评论

评论列表(0)

  1. 暂无评论