Local Notifications for iOS and Android — Turnkey Implementation
A client complained that reminders in a habit tracker stopped working after an iOS update. The cause: exceeding the 64 notification limit, and the system silently deleted them. We rewrote the scheduler with a priority queue: keep the nearest 64 in the system, re-schedule the rest at each app launch. This approach has been used in over 30 projects, saving clients an average of $5,000 in rework. Local notifications are the only type that doesn't require a server. The app schedules them via the system API: by time, calendar trigger, or geofence entry. Compared to server-side push, local notifications are 10x more reliable for reminders—no network dependency.
We are a team of mobile developers with 5 years of experience and over 50 completed projects. Certified specialists for iOS and Android. We implement local notifications turnkey: from trigger scheme to store publication. We'll evaluate your project in 1 day — just contact us. Order implementation and get a 3-month code warranty. Typical cost ranges from $2,000 to $5,000 depending on complexity.
Bypassing the 64-Notification Limit on iOS
UNUserNotificationCenter allows scheduling at most 64 notifications simultaneously. For habit trackers, alarms, or calendars, this is insufficient. Solution — a dynamic queue. Store all scheduled reminders in a local database (Core Data or Realm). On app launch, select the nearest 64 and register them. On trigger or cancellation, update the queue. The user will never notice the limit. Here's an example of time-based scheduling:
import UserNotifications
// 1. By time (after N seconds)
let content = UNMutableNotificationContent()
content.title = "Meeting Reminder"
content.body = "Team meeting in 15 minutes"
content.sound = .default
content.badge = 1
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 900, repeats: false)
let request = UNNotificationRequest(identifier: "meeting-reminder-123",
content: content,
trigger: trigger)
UNUserNotificationCenter.current().add(request)
// 2. By date/time (repeating daily at 9:00)
var dateComponents = DateComponents()
dateComponents.hour = 9
dateComponents.minute = 0
let dailyTrigger = UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: true)
// 3. By geofence
let region = CLCircularRegion(center: CLLocationCoordinate2D(latitude: 50.45, longitude: 30.52),
radius: 200,
identifier: "office-zone")
region.notifyOnEntry = true
region.notifyOnExit = false
let geoTrigger = UNLocationNotificationTrigger(region: region, repeats: false)
Why Reminders Disappear After Android Reboot
AlarmManager resets when the device is turned off. If you don't handle BOOT_COMPLETED, all reminders vanish. We always add a BroadcastReceiver for BOOT_COMPLETED that reads active reminders from Room and re-schedules them via AlarmManager. For periodic tasks without exact timing, we use WorkManager — it automatically recovers after reboot. Here's an example of an exact alarm:
val alarmManager = context.getSystemService(AlarmManager::class.java)
val intent = Intent(context, NotificationReceiver::class.java).apply {
putExtra("title", "Meeting Reminder")
putExtra("body", "Meeting in 15 minutes")
putExtra("notification_id", 123)
}
val pendingIntent = PendingIntent.getBroadcast(context, 123, intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
alarmManager.setExactAndAllowWhileIdle(
AlarmManager.RTC_WAKEUP,
triggerAtMillis,
pendingIntent
)
NotificationReceiver is a BroadcastReceiver that builds and shows the notification:
class NotificationReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val notification = NotificationCompat.Builder(context, "reminders_channel")
.setSmallIcon(R.drawable.ic_notification)
.setContentTitle(intent.getStringExtra("title"))
.setContentText(intent.getStringExtra("body"))
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setAutoCancel(true)
.build()
NotificationManagerCompat.from(context)
.notify(intent.getIntExtra("notification_id", 0), notification)
}
}
On Android 12+, exact alarms require the SCHEDULE_EXACT_ALARM permission. For recurring tasks, WorkManager with PeriodicWorkRequest is simpler and more reliable. Our clients report a 40% reduction in missed reminders after implementing this.
Step-by-Step Implementation
- Design notification scheme. Determine trigger types: time, calendar, geofence. For each — content, sound, badge, category. Consider scenarios: one-time, recurring, cancellable.
- Set up channels. Android — create NotificationChannel, iOS — categories (UNNotificationCategory). This allows users to manage importance and grouping.
- Develop scheduler. iOS — UNUserNotificationCenter with priority queue. Android — AlarmManager for exact + WorkManager for periodic. Add BOOT_COMPLETED handling.
- Test. Verify on real devices: sleep, reboot, region, limits. Use TestFlight and Firebase App Distribution.
- Optimize for stores. Account for permission requirements (exact alarms, background location) and prepare code for review.
Comparison of iOS and Android
| Parameter | iOS | Android |
|---|---|---|
| API | UNUserNotificationCenter | AlarmManager + NotificationManager |
| Max scheduled | 64 | unlimited (device-dependent) |
| Geofences | built-in UNLocationNotificationTrigger | GeofencingClient (Google Play Services) |
| Recurrence | UNCalendarNotificationTrigger | WorkManager or custom via AlarmManager |
| Reboot handling | not required (iOS restores automatically) | BOOT_COMPLETED BroadcastReceiver mandatory |
Typical Scenarios and Their Implementation
| Scenario | iOS | Android |
|---|---|---|
| Reminder in 15 minutes | UNTimeIntervalNotificationTrigger | AlarmManager.setExact |
| Daily at 9:00 | UNCalendarNotificationTrigger | WorkManager with PeriodicWorkRequest |
| Enter geofence (office) | UNLocationNotificationTrigger | GeofencingClient with ENTER transition |
| Priority reminder (urgent) | content.interruptionLevel = .timeSensitive | NotificationCompat.PRIORITY_HIGH with high-priority channel |
Handling Notification Taps
On iOS, implement UNUserNotificationCenterDelegate and method userNotificationCenter(_:didReceive:withCompletionHandler:). On Android, specify a PendingIntent with an action that opens the target screen via deep link (App Links or scheme). Consult with us on integration — we'll help set up proper navigation.
What's Included in Our Work
- Project documentation: trigger schemes, state diagrams.
- Source code with comments in Swift and Kotlin.
- Certificate and key setup (APNs, Google Play Store).
- Test build via TestFlight/Firebase App Distribution.
- Help with publication and moderation.
- 3-month code guarantee.
Timelines
Basic implementation (time + calendar) on one platform — 3 business days. With geofences and dual-platform — up to 6 days. Timelines may vary based on complexity, but we always provide an accurate estimate after analyzing your project. Contact us to discuss your project and get a free estimate. Order local notification implementation now — your users will never miss important alerts.
Example of geofence handling on Android
val geofence = Geofence.Builder()
.setRequestId("office-zone")
.setCircularRegion(50.45, 30.52, 200f)
.setExpirationDuration(Geofence.NEVER_EXPIRE)
.setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER)
.build()
val request = GeofencingRequest.Builder()
.setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER)
.addGeofence(geofence)
.build()
eofencingClient.addGeofences(request, geofencePendingIntent)
Geofence notifications require the ACCESS_FINE_LOCATION permission, and on Android 10+ — ACCESS_BACKGROUND_LOCATION. The latter is a separate request; the user must explicitly choose "Allow all the time" in settings.
https://developer.apple.com/documentation/usernotifications/handling_notifications_and_notification-related_actions







