Capacitor Local Notifications don't fire on Android, and randomly fire on iOS

Hey all,

I have a React/Ionic app that uses capacitor local notifications v4.0.0. I have followed the instructions on the documentation to the best of my ability and included the

<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />

In my androidManifest.xml

For whatever reason, my android monthly notifications never fire, despite passing all my jest tests. My iOS notifications fire every few days. Both should fire off a local notification on the 27th of every month. I am absolutely confused, and unfortunately this is a mission critical part of my application. What am I doing wrong?

Here is my code:

import { useEffect } from 'react';
import { LocalNotifications } from '@capacitor/local-notifications';
import { Storage } from '@capacitor/storage';

import getNextNotificationDates from '../util/getNextNotificationDates';

const useLocalNotifications = () => {
  const STORAGE_KEY = '2023-04-23';

  const scheduleYearlyNotifications = async () => {
    const pending = await LocalNotifications.getPending();
    const currentDate = new Date().toISOString();

    if (
      !pending.notifications.some((notification) =>
        [1, 2, 3].includes(notification.id)
      )
    ) {
      const [nextJuly1st, nextSept23rd, nextSept30th] =
        getNextNotificationDates(currentDate);

      LocalNotifications.schedule({
        notifications: [
          {
            title: `Claim Your Tax Refund Now! 💰`,
            body: 'You can now file for your fuel refund! Press the file return button to get started.',
            id: 1,
            schedule: { at: nextJuly1st, allowWhileIdle: true },
          },
          {
            title: 'One Week Left To File! ⏳',
            body: 'You have one week left to file for your fuel refund!',
            id: 2,
            schedule: { at: nextSept23rd, allowWhileIdle: true },
          },
          {
            title: 'One Day Left To File! 🚨⏰',
            body: 'Today is the last day to file for your fuel refund!',
            id: 3,
            schedule: { at: nextSept30th, allowWhileIdle: true },
          },
        ],
      });
    }
  };

  const scheduleMonthlyNotification = async () => {
    const pending = await LocalNotifications.getPending();

    if (!pending.notifications.some((notification) => notification.id === 4)) {
      LocalNotifications.schedule({
        notifications: [
          {
            title: 'Monthly Receipt Reminder ⛽️',
            body: `Don't forget to upload your fuel receipts!`,
            id: 4,
            schedule: {
              on: {
                day: 27,
                hour: 12,
                minute: 0,
                second: 0,
              },
              allowWhileIdle: true,
            },
          },
        ],
      });
    }
  };

  useEffect(() => {
    (async () => {
      try {
        const permissions = await LocalNotifications.requestPermissions();

        if (permissions.display === 'granted') {
          const pending = await LocalNotifications.getPending();
          console.log('pending', pending);
          const { value } = await Storage.get({ key: 'notificationsSetUp' });
          console.log('value', value);
          if (!value || value !== STORAGE_KEY) {
            await scheduleYearlyNotifications();
            await scheduleMonthlyNotification();
            await Storage.set({
              key: 'notificationsSetUp',
              value: STORAGE_KEY,
            });
          }

          console.log('pending', await LocalNotifications.getPending());

          LocalNotifications.addListener(
            'localNotificationReceived',
            async (notification) => {
              switch (notification.id) {
                case 1:
                case 2:
                case 3:
                  // Remove the current yearly notifications
                  await LocalNotifications.cancel({
                    notifications: [{ id: 1 }, { id: 2 }, { id: 3 }],
                  });

                  // Reschedule the yearly notifications
                  await scheduleYearlyNotifications();
                  break;

                case 4:
                  // Remove the current yearly notifications
                  await LocalNotifications.cancel({
                    notifications: [{ id: 4 }],
                  });

                  // Reschedule the monthly notifications
                  await scheduleMonthlyNotification();
                  break;

                default:
                  console.log(
                    'Unknown notification ID received:',
                    notification.id
                  );
              }
            }
          );
        } else {
          console.log('User denied local notifications');
        }
      } catch (e) {
        console.log('e', e);
      }
    })();
  }, []);

  return null;
};

export default useLocalNotifications;

Quickly looking at things, it’s had to tell without seeing a full working demo.

Can you isolate it in a git repo and we can test that?

EDIT: Also adding that @capacitor/storage was deprecated and renamed to @capacitor/preferences last year, so not sure if that could be causing issues.

Interesting catch on the deprecated capacitor package. Let me update that later in the day and see if that clears up any issues. If not, I will try and report back with a working sample