Skip to main content
RapidDev - Software Development Agency
App Featuresnotifications-alerts18 min read

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

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.

4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members

Feature spec

Intermediate

Category

notifications-alerts

Build with AI

4–8 hours with FlutterFlow or Lovable

Custom build

1–2 weeks custom dev

Running cost

$0/mo (on-device) · $25/mo if using Supabase pg_cron for web push

Works on

Mobile

Everything it takes to ship Customizable Reminders — parts, prompts, and real costs.

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.

What users consider table stakes in 2026

  • Users can set one-time or repeating reminders — at minimum daily and weekly, ideally custom days of the week
  • Notification arrives even when the app is fully closed or the device is in Do Not Disturb (depends on OS priority setting)
  • Reminder label and scheduled time are editable after creation without deleting and recreating
  • Snooze option appears on notification tap with user-configurable duration (5, 10, or 30 minutes)
  • Quiet hours setting blocks reminders during a chosen time range (e.g. 10 PM – 8 AM) per user preference
  • List view of all scheduled reminders with a per-reminder enable/disable toggle so users pause without deleting

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.

Layers:UIDataBackend

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.

Note: Validate label length client-side before save — the notification payload truncates long strings on some Android skins.

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.

Note: RRULE gives you maximum flexibility (BYDAY, COUNT, UNTIL). For simple apps, storing a day-of-week bitmask integer is lighter and easier for the AI to generate correctly.

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.

Note: Android 13+ (API 33+) requires SCHEDULE_EXACT_ALARM permission requested at runtime — the package requests USE_EXACT_ALARM by default, which is insufficient for apps targeting API 33+.

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.

Note: Pass all payload data as a JSON-encoded string in the payload field — dynamic variables resolve to null in the background isolate if passed separately.

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.

Note: Snooze scheduling must happen in a background isolate on Android so it works even if the user doesn't open the app after tapping snooze.

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).

Note: Always store quiet hours in UTC and convert to local time at display — comparing stored local times after the user travels causes off-by-one hour errors.

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.

Note: Re-query the reminders table after any toggle to keep the list in sync with what is actually scheduled in flutter_local_notifications.

The 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.

schema.sql
1create table public.reminders (
2 id uuid primary key default gen_random_uuid(),
3 user_id uuid references auth.users(id) on delete cascade not null,
4 label text not null check (char_length(label) <= 50),
5 rrule text,
6 next_fire_at timestamptz,
7 is_active bool not null default true,
8 snooze_minutes int not null default 10,
9 quiet_start time,
10 quiet_end time,
11 created_at timestamptz not null default now()
12);
13
14create table public.reminder_logs (
15 id uuid primary key default gen_random_uuid(),
16 reminder_id uuid references public.reminders(id) on delete cascade not null,
17 fired_at timestamptz not null default now(),
18 action text not null check (action in ('fired','snoozed','completed','dismissed'))
19);
20
21alter table public.reminders enable row level security;
22alter table public.reminder_logs enable row level security;
23
24create policy "Users manage own reminders"
25 on public.reminders for all
26 using (auth.uid() = user_id)
27 with check (auth.uid() = user_id);
28
29create policy "Users view own reminder logs"
30 on public.reminder_logs for select
31 using (
32 exists (
33 select 1 from public.reminders r
34 where r.id = reminder_id and r.user_id = auth.uid()
35 )
36 );
37
38create policy "Service role inserts reminder logs"
39 on public.reminder_logs for insert
40 with check (true);
41
42create index reminders_user_next_idx
43 on public.reminders (user_id, next_fire_at)
44 where is_active = true;

Heads up: 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 it — pick your path

Each tool fits this feature differently. Switch tabs to compare fit, time, the exact steps, and a copy-paste prompt.

Native iOS + AndroidFit for this feature:

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.

Step by step

  1. 1In FlutterFlow, add the flutter_local_notifications package under Settings → Pub Dependencies, then add the Supabase package for your reminders table
  2. 2Build 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. 3Create 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. 4Build 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. 5Add 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. 6Test on a real physical device — the FlutterFlow web preview cannot schedule or receive local notifications

Where this path bites

  • 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

Third-party services you'll need

On-device reminder scheduling (the FlutterFlow path) is entirely free. Costs appear only if you take the web/PWA path requiring server-side push delivery.

ServiceWhat it doesFree tierPaid from
flutter_local_notificationsOn-device scheduler for AlarmManager (Android) and UNUserNotificationCenter (iOS); fires reminders with app closedOpen-source, zero costFree
Supabase pg_cronServer-side cron scheduler for web/PWA push path — runs SQL functions on a schedule to trigger reminder push deliveryNot available on Supabase free tierIncluded in Supabase Pro $25/mo (approx)
web-push npm packageOpen-source library for sending Web Push API payloads from a Supabase Edge Function; VAPID keys are self-generated at no costOpen-source, zero costFree — delivery via FCM (Firebase Cloud Messaging) is also free

Swipe the table sideways to see pricing.

What it costs to run

Drag through the tiers to see how your monthly bill scales with users — no surprises later.

Estimated monthly running cost

$0/mo

flutter_local_notifications is entirely on-device — no server needed. Supabase free tier covers the reminders table and reminder_logs storage easily at this scale.

Estimates use verified 2026 pricing and assume typical usage per user. Your real bill depends on activity, storage, and third-party plans.

What breaks when AI tools build this

The failures people actually hit on their first build — the symptom, why it happens, and the exact fix.

Reminders fire on Android emulator but not on physical Android 13+ devices

Symptom: 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

Symptom: 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

Symptom: 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

Symptom: 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

1

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

2

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

3

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

4

Validate label length (50 chars max) client-side and trim silently — notification payload size limits can cause delivery failures for very long titles

5

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

6

Re-evaluate and reschedule pending reminders on every app foreground resume — catches missed reminders after OS restarts, updates, or timezone changes

7

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

When You Need Custom Development

FlutterFlow with flutter_local_notifications covers the vast majority of reminder use cases. These scenarios outgrow that pattern:

  • Reminders need to sync across devices — a habit set on an iPhone should fire on the user's iPad too, requiring a server-driven push delivery layer beyond local notifications
  • Integration with Google Calendar or Apple Calendar is required for two-way sync, which involves separate OAuth flows and event management APIs not covered by flutter_local_notifications
  • Enterprise use case where managers set reminders for team members and need override controls, or where all reminder interactions require a compliance audit log
  • Healthcare or regulated context requiring proof-of-delivery logging: that a medication reminder fired at exactly 08:00:03 on a specific date with a specific device attestation

RapidDev builds these features for production

Real auth, database, and payments — fixed price, delivered in weeks. You own 100% of the code.

Get a fixed-price quote

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.

RapidDev

Need this feature production-ready?

RapidDev builds customizable reminders into real apps — auth, database, payments — at $13K–$25K.

Book a free consultation

30-min call. No commitment.

Want this built for you?

We ship production apps at a fixed price — $13K–$25K, 6–10 weeks, source code yours. You've seen what it takes; we do it every week.

Get a fixed-price quote

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.