← ~/work

Case study2026Solo project

Knock

Local-first calendar alarm app on Compose Multiplatform

Role
Solo project
Stack
Kotlin Multiplatform · Compose Multiplatform · Room (KMP) · Ktor
Status
In progress
Reading
2 min
Knock · calendar event → alarm that won't be missedmandate: never weaken alarm reliability
sourceAndroid CalendarContract
sourceGoogle Calendar · AppAuth OAuth
sourceICS subscription URL
sourceManual entry
on device onlyRoom KMP storecalendar data never leaves the phone
schedulerexact alarm per eventWorkManager (Android) · BGTaskScheduler stub (iOS)
T-0full-screen alarmover the lock screen · rings until dismissed
Compose Multiplatform · iOS targets compile from day one.

A local-first Android app that wakes you for calendar meetings the way an alarm clock wakes you in the morning: full-screen, lock-screen-bypassing, ringing until you dismiss it. Built on Compose Multiplatform with iOS targets compiled from day one. Architectural mandate: never weaken alarm reliability.

Problem

Calendar notifications fail too often: silent on Do Not Disturb, swallowed by Doze, killed by aggressive OEM battery managers, or buried in a notification shade you didn't check. For a remote engineer whose income depends on being on time to a 14:00 meeting learned about at 13:42, 'I didn't see the notification' is not an acceptable outcome.

Approach

Multi-source event ingest into a single local database. Schedule exact alarms with the OS-level alarm primitives: the same ones that make your morning alarm reliable. No backend. Crashlytics for stack traces only, with PII scrubbing on calendar content.

Sources

  • Android's CalendarContract provider: zero config if calendars are already synced on the phone
  • Google Calendar OAuth via AppAuth: for work calendars not exposed to the device
  • ICS subscription URLs: fallback when OAuth is blocked by org policy
  • Manual entry: guaranteed-to-work path for any meeting

Deduped into a single local DB keyed by (iCalUID, calendarId). ContentObserver-debounced refresh on Android calendar changes; cadenced refresh for OAuth + ICS.

Architectural mandates

Never weaken alarm reliability

setExactAndAllowWhileIdle, boot-completed receivers, battery-optimization onboarding, full-screen-intent permission flow, foreground-service-only-when-needed. Any code change that risks alarm reliability (even theoretically) gets blocked.

Never store calendar data outside the device

No backend. No analytics on event content. Crashlytics carries stack traces and device metadata only, with PII scrubbing on. The user's calendar is the user's, full stop.

iOS shared code from day one

:composeApp:commonMain already targets iOS. Platform actuals (AlarmManager, CalendarContract, AppAuth) are stubbed via NotImplementedError("iOS implementation pending: <feature>"). The iOS build never breaks; it just runs the stubs.

Stack

  • Kotlin 2.x, Compose Multiplatform + Material 3
  • Koin DI (KMP-native), Coroutines + Flow
  • Room 3 KMP via KSP, DataStore Preferences (KMP)
  • Ktor + Kotlinx Serialization, AppAuth-Android for OAuth
  • WorkManager (Android), BGTaskScheduler stub (iOS)
  • Kermit logging with PII scrubbing, Firebase Crashlytics (Android only)

Status

Android v1 in active development. PRD frozen at v1.2. iOS code already compiles for both targets; platform actuals follow Android ship.