# How to Add Customizable Reminders to Your App (Copy-Paste Prompts Included)

- Tool: App Features
- Last updated: July 2026

## TL;DR

Customizable reminders need four pieces: a creation form with recurrence options, a local notification scheduler (flutter_local_notifications), a quiet hours gate in the data model, and snooze logic wired to the notification response callback. With FlutterFlow you can ship a working reminder system in 4–6 hours for $0/month — the scheduling library is free and entirely on-device. Server costs only appear if you add web push for a PWA path via Supabase pg_cron.

## What Customizable Reminders Actually Are

Customizable reminders let each user set their own schedule for the things that matter to them — a habit at 7 AM every weekday, a medication at noon and 9 PM, a weekly review every Sunday. Unlike push notifications (which the server sends), local reminders fire entirely on the device through the OS notification center, which means they work with the app fully closed and require no server infrastructure. The product decisions are: how rich to make the recurrence options (one-time vs daily vs custom days), whether to support quiet hours, how the notification tap deep-links back into the app, and what the snooze UX feels like.

## Anatomy of the Feature

Seven components across three layers. FlutterFlow generates the UI layer and connects to the scheduling library; the recurrence engine and quiet hours logic are where first builds break.

- **Reminder Creation Form** (ui): Time picker (Flutter's showTimePicker) combined with a label input (max 50 chars), a recurrence selector (one-time / daily / custom days-of-week), and a snooze duration preference. Writes the new reminder to the reminders table in Supabase on save.
- **Recurrence Engine** (data): Stores RRULE strings (RFC 5545 format) or cron expressions per reminder row in the rrule column. The flutter_local_notifications plugin calculates the next trigger time from the recurrence pattern at scheduling time.
- **Local Notification Scheduler** (backend): The flutter_local_notifications package schedules AlarmManager intents on Android and UNUserNotificationCenter triggers on iOS. Uses AndroidScheduleMode.exactAllowWhileIdle for precise timing even when the device is in Doze mode. Fires notification payloads when the app is backgrounded or terminated.
- **Notification Payload Handler** (backend): The onDidReceiveNotificationResponse callback receives the notification tap event. It decodes the JSON payload (reminder_id, label, action) and routes the user to the correct reminder detail screen, logging a 'completed' or 'dismissed' action in reminder_logs.
- **Snooze Logic** (backend): When the user taps Snooze from the notification, a one-time notification is scheduled N minutes out (where N is the user's stored snooze_minutes preference: 5, 10, or 30). A snooze event is inserted into reminder_logs with action='snoozed' and the new fire time.
- **Quiet Hours Gate** (data): Each user row stores quiet_start and quiet_end as TIME values in UTC. Before scheduling a notification, the scheduler checks whether the calculated next_fire_at falls within the quiet window; if so, it reschedules to quiet_end on the same day (or next day if past midnight).
- **Reminders List Screen** (ui): A ListView.builder showing all reminders for the current user, ordered by next_fire_at. Each row shows the label, recurrence description, next fire time, and a Switch widget. Toggle off calls cancelNotification() and sets is_active = false; toggle on calls scheduleNotification() and sets is_active = true.

## Data model

Two tables cover scheduling and history. Run this in the Supabase SQL editor — the RLS policies ensure users only see and modify their own reminders.

```sql
create table public.reminders (
  id uuid primary key default gen_random_uuid(),
  user_id uuid references auth.users(id) on delete cascade not null,
  label text not null check (char_length(label) <= 50),
  rrule text,
  next_fire_at timestamptz,
  is_active bool not null default true,
  snooze_minutes int not null default 10,
  quiet_start time,
  quiet_end time,
  created_at timestamptz not null default now()
);

create table public.reminder_logs (
  id uuid primary key default gen_random_uuid(),
  reminder_id uuid references public.reminders(id) on delete cascade not null,
  fired_at timestamptz not null default now(),
  action text not null check (action in ('fired','snoozed','completed','dismissed'))
);

alter table public.reminders enable row level security;
alter table public.reminder_logs enable row level security;

create policy "Users manage own reminders"
  on public.reminders for all
  using (auth.uid() = user_id)
  with check (auth.uid() = user_id);

create policy "Users view own reminder logs"
  on public.reminder_logs for select
  using (
    exists (
      select 1 from public.reminders r
      where r.id = reminder_id and r.user_id = auth.uid()
    )
  );

create policy "Service role inserts reminder logs"
  on public.reminder_logs for insert
  with check (true);

create index reminders_user_next_idx
  on public.reminders (user_id, next_fire_at)
  where is_active = true;
```

The partial index on active reminders keeps the scheduler query fast even for users with hundreds of historical reminders. The ON DELETE CASCADE on reminder_logs means deleting a reminder automatically cleans up its log history.

## Build paths

### Flutterflow — fit 5/10, 4–6 hours

Best path for mobile reminders: FlutterFlow has a dedicated flutter_local_notifications plugin and Custom Actions handle the iOS/Android permission flows. Build the UI visually, drop into Custom Code for the scheduler.

1. In FlutterFlow, add the flutter_local_notifications package under Settings → Pub Dependencies, then add the Supabase package for your reminders table
2. Build the Reminder Creation Form page visually: add a TimePicker widget, a TextField for the label (max 50 chars), a RadioGroup for recurrence (One-time / Daily / Custom days), and a DropDown for snooze minutes (5/10/30)
3. Create a Custom Action named scheduleReminder that calls flutter_local_notifications to schedule an AlarmManager-style exact trigger using AndroidScheduleMode.exactAllowWhileIdle, passing label and reminder_id as JSON payload; request notification permission inside this action using requestExactAlarmsPermission() before scheduling
4. Build the Reminders List page using a Supabase Query for the reminders table filtered by auth.uid(); add a Switch widget on each row wired to a Custom Action that calls cancelNotification() on toggle-off and scheduleReminder() on toggle-on
5. Add a Custom Action named handleNotificationTap registered in initState that reads onDidReceiveNotificationResponse and navigates to the reminder detail page using the reminder_id from the JSON payload
6. Test on a real physical device — the FlutterFlow web preview cannot schedule or receive local notifications

Limitations:

- Android 13+ requires requestExactAlarmsPermission() called in a Custom Action — FlutterFlow doesn't add the SCHEDULE_EXACT_ALARM manifest entry automatically
- Continuous quiet hours across midnight (e.g. 10 PM – 8 AM) requires extra date arithmetic in the Custom Function — FlutterFlow's Date helpers don't handle the day-rollover edge case out of the box
- iOS background scheduling survives force-close only up to the pending notification queue limit; users who force-close repeatedly may miss reminders until they reopen the app

### Lovable — fit 2/10, 6–8 hours

Lovable builds React/Vite web apps. Web notifications cannot schedule future delivery or fire with the browser closed — only a server-side cron approach (Supabase pg_cron + web-push) makes this work as a PWA. Suitable only if you are building a PWA, not a native app.

1. Connect Lovable Cloud so Supabase and Edge Functions are provisioned automatically
2. Paste the prompt below; Lovable will scaffold the reminder form, the reminders table, and a Supabase Edge Function for push delivery
3. In Lovable's Cloud tab → Secrets, add your VAPID_PUBLIC_KEY and VAPID_PRIVATE_KEY (generate free VAPID keys at web-push-codelab.glitch.me)
4. Enable pg_cron on the Supabase Pro plan (Settings → Database → pg_cron), then prompt Lovable to add a cron job that calls the Edge Function every 5 minutes to check for due reminders
5. Publish the app to your custom domain and test on mobile — push permission and delivery cannot be tested in the Lovable preview iframe
6. Guide users to 'Add to Home Screen' on iOS Safari since iOS web push only works for installed PWAs (iOS 16.4+)

Starter prompt:

```
Build a customizable reminders feature for a web PWA using Supabase and the Web Push API. Schema: reminders table with columns id uuid primary key, user_id uuid references auth.users, label text (max 50 chars), rrule text, next_fire_at timestamptz, is_active bool default true, snooze_minutes int default 10, quiet_start time, quiet_end time, created_at timestamptz. Also create reminder_logs table with id, reminder_id, fired_at, action text check in (fired, snoozed, completed, dismissed). Enable RLS — authenticated users can only access their own rows.

UI: Reminder creation form with label input (validate max 50 chars), time picker, recurrence selector (one-time / daily / weekdays / custom days of week), snooze duration dropdown (5/10/30 min), quiet hours time range picker. Reminders list screen showing all active reminders with enable/disable toggle.

Backend: Supabase Edge Function named send-reminder-push that reads VAPID keys from Secrets and sends web push payloads using the web-push npm package. Include push_subscriptions table (id, user_id, endpoint, p256dh, auth). Add a permission request flow that only asks for push permission on button click (not on page load) and explains why notifications are needed. Handle the edge case where the user denies permission with an in-app message explaining they can re-enable in browser settings. Deduplicate: do not re-fire the same reminder within the same scheduled window.
```

Limitations:

- Web Push cannot fire when the browser tab is closed on desktop; on mobile it only works if the site is installed as a PWA (Add to Home Screen)
- Mobile Safari on iOS requires iOS 16.4+ and Add to Home Screen — push will silently not work for standard browser tabs
- No equivalent to flutter_local_notifications exact alarm scheduling; pg_cron runs every 5 minutes at best, so reminders may fire up to 5 minutes late
- Supabase pg_cron requires the Pro plan ($25/mo); the free tier has no server-side scheduling

### Custom — fit 4/10, 1–2 weeks

Custom development is the right call when reminders must sync across devices, integrate with Google Calendar or Apple Calendar, or serve an enterprise team where managers set reminders for staff.

1. Flutter app with flutter_local_notifications for on-device scheduling combined with a Supabase backend for cross-device sync — when a reminder is created on one device, an Edge Function sends a push notification to all other devices owned by the same user to reschedule
2. RRULE parsing via the rrule Dart package for complex recurrence rules (BYDAY, UNTIL, COUNT) with a human-readable description generator for the list screen
3. Google Calendar API or Apple EventKit integration for two-way sync — reminders created in-app appear in the user's system calendar and vice versa
4. Backend retry logic for missed reminders: if the device was off when a reminder fired, reschedule on next app open based on the next_fire_at timestamp and the recurrence rule

Limitations:

- Cross-device sync requires careful conflict resolution — two devices scheduling the same reminder creates duplicate OS notifications
- Google Calendar and Apple Calendar APIs have different OAuth flows and event models; building two-way sync for both adds significant scope

## Gotchas

- **Reminders fire on Android emulator but not on physical Android 13+ devices** — Android 13 (API 33) introduced a stricter exact alarm permission — SCHEDULE_EXACT_ALARM — that apps targeting API 33+ must request at runtime. The flutter_local_notifications package requests USE_EXACT_ALARM by default, which is sufficient for API 31–32 but not API 33+. On a physical Pixel 7 or Samsung Galaxy S23, the scheduled notification is registered but silently never fires. Fix: In your Custom Action before calling scheduleNotification(), call await flutterLocalNotificationsPlugin.resolvePlatformSpecificImplementation<AndroidFlutterLocalNotificationsPlugin>()?.requestExactAlarmsPermission(). Also verify AndroidScheduleMode.exactAllowWhileIdle is set on the scheduling call, not AndroidScheduleMode.exact.
- **iOS reminders stop after the user force-closes the app twice** — iOS monitors battery consumption per app. If a user force-closes (swipes away) an app multiple times in succession, iOS applies background execution limits that prevent future scheduled notifications from waking the app's background isolate. UNUserNotificationCenter's pending notifications survive in the OS queue, but apps that rely on the background isolate to reschedule after each fire lose that rescheduling step. Fix: Schedule all recurrence instances up front in UNUserNotificationCenter (iOS allows up to 64 pending notifications per app) rather than relying on a background callback to reschedule each occurrence. On app open, check for missed reminders using next_fire_at from Supabase and reschedule the upcoming queue. Document this iOS limitation with an in-app tooltip.
- **Reminder label shows null in the notification instead of the user's text** — FlutterFlow Custom Actions pass notification body as a dynamic variable resolved at scheduling time. When flutter_local_notifications fires the notification from the background isolate, any variable that was bound to app state is no longer available — the isolate has no access to the running app's memory, so the label resolves to null. Fix: Encode all payload data — label, reminder_id, snooze_minutes — as a JSON string in the NotificationDetails payload field when scheduling. In onDidReceiveNotificationResponse, parse the JSON string: final data = jsonDecode(response.payload!); This is the correct pattern for passing data through the notification boundary.
- **Quiet hours don't block notifications consistently after a timezone change** — Quiet hours are checked at scheduling time against the device's current local clock. If the user travels to a different timezone after scheduling, the stored quiet_start/quiet_end values (saved as local time strings) no longer match the new local time. A reminder that was blocked at 11 PM local time fires at 11 PM UTC instead when the user is in a +10 timezone. Fix: Store quiet_start and quiet_end in UTC in the database. On the device, convert the user's preferred local quiet window to UTC before saving. Re-evaluate and reschedule all active reminders whenever the device reports a timezone change (listen to the system timezone change broadcast on Android, or re-evaluate on app foreground on iOS).

## Best practices

- Request notification permission only after the user taps 'Add Reminder' for the first time — explain the benefit before triggering the system dialog to maximize grant rate
- Show an in-app explanatory screen before the system permission dialog: 'We need notification permission to remind you even when the app is closed' with an illustration
- Schedule all upcoming recurrence instances (up to 64 on iOS) at creation time rather than relying on a background reschedule callback — this is the only reliable pattern on iOS
- Validate label length (50 chars max) client-side and trim silently — notification payload size limits can cause delivery failures for very long titles
- Encode all notification payload data as a single JSON string in the payload field; never rely on dynamic app state variables in the background isolate
- Re-evaluate and reschedule pending reminders on every app foreground resume — catches missed reminders after OS restarts, updates, or timezone changes
- Provide a 'Test reminder' button in settings that schedules a notification 10 seconds out — lets users verify permissions are working without waiting for a real reminder

## Frequently asked questions

### Do reminders work when the phone is on silent?

Yes — local notifications are delivered to the notification center regardless of the ringer switch. They appear in the lock screen and notification drawer. Whether they play a sound depends on the device's 'Do Not Disturb' settings and the notification channel priority you configure in flutter_local_notifications. Setting importance to AndroidImportance.high bypasses some DND modes on Android.

### How do I make reminders repeat every weekday?

Store BYDAY=MO,TU,WE,TH,FR as the RRULE value in the database. On the scheduling side, flutter_local_notifications doesn't parse RRULE natively — calculate the next weekday occurrence in a Custom Function and reschedule after each fire. Alternatively, schedule Monday through Friday as five separate repeating weekly notifications sharing the same reminder_id, and cancel all five when the user disables the reminder.

### Why did my reminder stop working after I updated the app?

App updates on Android cancel pending AlarmManager alarms — this is an OS behavior. On app launch after an update, query the reminders table for all active reminders where next_fire_at is in the future and reschedule them. Add this logic in your app's initState or a startup service so it runs automatically after every update.

### Can users set reminders from a web browser too?

Web browsers can receive push notifications via the Web Push API, but they cannot schedule future local notifications the way a native app can — the browser has to be running (or the site installed as a PWA) for the service worker to receive them. For a true 'fires even when closed' experience on mobile, a native Flutter app using flutter_local_notifications is the right platform. The PWA path via Lovable is a reasonable compromise for non-critical web reminders.

### How do I test reminders without waiting for the scheduled time?

Add a 'Test Reminder' button in your debug menu that schedules a notification 10–15 seconds in the future with the same payload logic as a real reminder. This lets you verify the permission grant, payload decoding, deep-link routing, and snooze behavior without manipulating the device clock. Always test on a real physical device — iOS and Android simulators have limited notification support.

### What happens if the user denies notification permission?

The notification is never delivered, but the reminder row is still saved in your database. Show a persistent in-app banner on the reminders list screen when permission is denied: 'Notifications are turned off — your reminders won't arrive. Tap here to open Settings and enable them.' Use Flutter's permission_handler package to detect the current status and link directly to the app's system settings page.

### Can I add custom sounds and vibration patterns to reminders?

Yes. On Android, create a custom NotificationChannel with a sound URI pointing to a .mp3 in the res/raw folder and a vibration pattern array. On iOS, set UNNotificationSound.named() with a bundled .aiff or .caf file. In flutter_local_notifications, configure these in AndroidNotificationDetails and DarwinNotificationDetails. Note: custom sounds must be included in the app bundle — you cannot load them from a URL at notification time.

### How many reminders can one user have?

iOS limits each app to 64 pending scheduled notifications in UNUserNotificationCenter. If a user has 10 repeating reminders and each is pre-scheduled for the next 6 occurrences, you hit the limit quickly. The practical solution is to schedule only the next 2–3 occurrences per reminder and reschedule on each fire event. Android has no such hard limit, but keeping pending alarm counts reasonable improves battery life.

---

Source: https://www.rapidevelopers.com/app-features/customizable-reminders
© RapidDev — https://www.rapidevelopers.com/app-features/customizable-reminders
