Implementing Timer and Stopwatch in Mobile App
Stopwatch with 0.01 second accuracy and countdown timer—task less simple than appears. Main problem isn't UI, but app backgrounding, phone locking, and showing correct time on return.
Accuracy and Background Behavior
Timer in iOS (aka ScheduledTimer) unfit for precise counting—fires in run loop and can delay under main thread load. Correct approach: save startDate = Date() on start, each tick compute elapsed = Date().timeIntervalSince(startDate). Update UI via CADisplayLink for smoothness (60/120 fps) or Timer with 0.01–0.1s interval for normal needs.
On background via NotificationCenter catch UIApplication.didEnterBackgroundNotification, fix backgroundDate. On willEnterForegroundNotification compute delta and adjust state. For timer with notification—UNUserNotificationCenter.scheduleLocalNotification on start; on return cancel via removePendingNotificationRequests.
On Android—System.currentTimeMillis() or SystemClock.elapsedRealtime() for start (second preferable—independent of system time changes). Handler.postDelayed() for UI updates. On background via onPause() save start time in ViewModel, on onResume() recalculate. For background timer work—ForegroundService with status bar notification.
In Flutter—Stopwatch class from Dart:core as basis for stopwatch (accurate, no drift). Timer.periodic for UI. On background—flutter_foreground_task or platform channel.
States and UI
Stopwatch: stopped, running, paused. Timer: idle, running, paused, finished. Each state—specific button set and display.
Time display: HH:MM:SS.cc—format from elapsed via integer division, not DateFormatter (extra allocations per tick). In SwiftUI—Text with monospacedDigit() so digits don't "jump" on value changes. In Compose—FontVariation.Settings or monospace font family.
Lap function for stopwatch: store array [(lapNumber: Int, lapTime: TimeInterval, totalTime: TimeInterval)], display in List / LazyColumn. Auto-scroll to last element on add.
Local Notifications on Timer End
iOS: UNMutableNotificationContent + UNTimeIntervalNotificationTrigger with timeInterval equal to remaining time. Request permission via UNUserNotificationCenter.requestAuthorization. If app in foreground—UNUserNotificationCenterDelegate.userNotificationCenter(_:willPresent:) for banner show.
Android: AlarmManager.setExactAndAllowWhileIdle() for exact trigger with Doze mode. BroadcastReceiver receives intent, launches notification via NotificationManager. From API 31+ requires SCHEDULE_EXACT_ALARM permission with explanation.
Timeline: basic timer + stopwatch with background support—2 days. With rings, session history, custom sounds and home screen widget—3 days.







