Delivered remotely for a business based in Singapore, Singapore. Names withheld by agreement.
Project Overview
Industry: Consumer wearables and personal productivity.
Type of solution: A paired Android product — a Wear OS application containing a custom-rendered watch face and a set of full-screen calendar views, plus an Android phone companion application that reads the device calendar and synchronises it to the watch.
Business context: Calendar applications on smartwatches have generally been shrunken phone applications: a scrollable agenda, several taps away, showing two entries at a time. That answers "what is next" but not "how much of my day is already committed" — and it requires the wearer to go looking. The premise of this product is that the watch face is the only surface a wearer reliably looks at, so the schedule belongs there, readable at a glance, before any interaction takes place.
General users: The device owner. There is no account system, no login and no server-side user model.
General purpose: To make the day's shape legible on the wrist, and to keep the watch's picture of that day accurate without a network, without a backend and without a measurable cost to battery life.
The Business Challenge
There is no server to arbitrate. The phone owns the calendar; the watch does not. In a conventional product a backend would hold the truth and both clients would reconcile against it. Here there is no backend at all — the two devices must agree directly, over a link that is frequently unavailable.
Disconnection is the normal state. A watch is out of range of its phone for large parts of a day. The face still has to raise-to-wake and show a correct-looking day. "Loading" is not an acceptable state on a watch face.
Delivery is not guaranteed. The device-to-device transport does its best and reports very little. A sync can fail because the watch is out of range, because the companion application is not running, or because the platform services are unavailable — and the sender frequently cannot tell which.
Background execution is rationed. Modern Android aggressively suspends background work, and device manufacturers layer their own restrictions on top. A single periodic timer is not a reliable way to keep anything in sync.
Battery is the product's real constraint. A watch face that shortens battery life gets uninstalled, no matter how good it looks. That rules out continuous animation, frequent redraws and aggressive polling in one stroke.
Time does not map cleanly onto a dial. A twelve-hour face has to represent a twenty-four-hour day. Events crossing noon or midnight need explicit handling, and daylight-saving transitions shift the relationship between wall-clock time and dial position twice a year.
Two events at the same time cannot occupy the same space. Overlapping appointments are ordinary in real calendars and impossible in a single ring of arcs. The system needs a deliberate answer, not a drawing accident.
Screens vary more than on phones. Round, square and chinned displays, across a wide range of resolutions, each needing different radii, different layouts and different text metrics.
Always-on displays are a second product. Ambient mode has a restricted palette, a much lower refresh budget and different legibility requirements — the same information rendered under different rules.
Our Approach
Make the watch a read-only replica, deliberately. The watch never writes to the calendar. That single constraint removes bidirectional write conflicts entirely: the phone is unconditionally authoritative, and every sync is a full snapshot that replaces what came before rather than a delta that has to be merged in order. With a small payload and one writer, snapshot replacement is both simpler and safer than any merge strategy — and it makes a missed sync a recoverable inconvenience rather than a corrupted state.
Put the wire contract in one shared module. Both applications depend on a small shared module that defines the message paths and payload keys, so the two sides cannot drift apart. In a system with no server and no schema registry, that shared definition is the only thing standing between the two applications and a silent protocol mismatch.
Trigger synchronisation three independent ways. A system job registered against the calendar provider fires when the user actually edits an event. A periodic work request runs on the platform's managed scheduler. A periodic background job runs on a third mechanism. Any one of them being suppressed by a device battery policy still leaves two others working — redundancy chosen specifically because background execution cannot be relied upon.
Let the consumer ask. The watch knows when it might be stale — when the face is created, when the date rolls over, on its periodic redraw — and sends a short request back to the phone asking for a fresh snapshot. The producer cannot know this; the consumer can. This reverse path is what repairs a push that never arrived.
Cache the whole snapshot on the watch. The received event set is persisted locally, so the face draws immediately from cache on creation and continues to draw a correct day for as long as the link is down. Synchronisation refreshes the cache; it is never on the critical path to a rendered frame.
De-duplicate and resolve overlaps before drawing. Duplicate events arriving from several calendar accounts are collapsed at the producer. Events that overlap in time are detected by an explicit comparator and diverted to a secondary representation rather than being drawn over one another.
Precompute what does not change. Daylight-saving transition dates for the year are computed once from the device time zone and cached, rather than being recalculated in the render loop.
Budget the redraws. Redraws happen on date rollover, at fixed intervals and on ambient transitions — not continuously. Ambient mode uses a separate, cheaper rendering path over the same geometry.
Share one model between the face and the screens. The watch face service and the full-screen calendar views inherit from a common base that owns node discovery, message sending, the local cache and the overlap comparators, so every surface on the watch renders from the same data and the same rules.
The Solution
A custom-rendered watch face. Calendar events are drawn as graphical segments around the dial using bespoke canvas rendering, with per-event colouring derived from the source calendar, start and end markers, and gradient and shading treatment — all built as a custom Android view rather than assembled from stock widgets.
Analog and digital modes, and 12- or 24-hour clock display, switchable directly on the watch.
Always-on support. A dedicated ambient rendering path presents the same schedule under the restricted palette and refresh budget that always-on displays require.
Complication slots. Seven configurable slots — six positioned around the dial and one at the centre — with supported data types restricted per slot and a dedicated configuration screen, so the wearer can add battery, weather, steps or any other provider alongside their schedule.
Full-screen calendar views on the watch. Beyond the face itself: a three-day view built on a snapping, centre-zooming list; a seven-day overview rendering a week of miniature dials; an event list; and an event detail screen — all with separate round and rectangular layouts.
Watch-side settings. Clock format and display mode are changed on the watch and take effect immediately on the face.
A companion application that stays out of the way. The phone application discovers the available calendars, groups them by account, lets the wearer choose which ones matter, requests calendar permission, and then does its work invisibly in the background.
Selective calendar synchronisation. Only the calendars the wearer selects are read and transmitted, which reduces both the payload and the amount of personal information crossing the link.
Pairing diagnostics. The companion probes for connected watch nodes and waits for an acknowledgement, so it can distinguish "no watch paired" from "watch paired but the watch application is not running" and tell the user which one applies.
Local-only data handling. Calendar content is read from the phone's own calendar provider and sent directly to the paired watch. There is no server, no account and no network call — the data does not leave the device pair.
Key Features
Schedule on the watch face The day's events are rendered directly on the dial as graphical segments, readable on raise-to-wake without a tap.
Offline-first rendering The watch caches the complete event snapshot locally and draws from cache, so the face is correct and immediate whether or not the phone is in range.
Layered synchronisation triggers Calendar edits, a managed periodic work request and a periodic background job each independently trigger a sync, so no single platform restriction can stop the watch being updated.
Watch-initiated refresh The watch requests a fresh snapshot when it has reason to believe it is stale — the recovery path for any push that never arrived.
Full-snapshot synchronisation Each sync replaces the watch's event set outright rather than merging changes, eliminating ordering and merge failures by design.
Duplicate and overlap resolution Events duplicated across calendar accounts are collapsed before transmission, and events that overlap in time are detected and given a distinct secondary representation.
Always-on rendering path A separate reduced-cost, reduced-palette render for ambient mode, over the same geometry as the active face.
Seven configurable complication slots Six around the dial plus a centre slot, with per-slot data type restrictions and a dedicated configuration screen.
Multiple calendar views on the wrist Three-day, seven-day, list and detail views, each with layouts for both round and rectangular displays.
Selective calendar sources Per-calendar selection, grouped by account, so only what the wearer cares about is read and transmitted.
Technical Architecture
Watch application. A canvas-based watch face service with a bespoke custom view for segment rendering, sitting alongside a set of full-screen calendar activities. Face and activities share a base class owning node discovery, message dispatch, the local cache and the event comparators. Complications are consumed through the platform's complications framework with a dedicated configuration activity.
Companion application. A thin phone-side data producer: calendar discovery and selection in the foreground, calendar reads and transmission in the background, and a listener service that responds to refresh requests from the watch.
Shared contract module. A small module depended on by both applications defining the message paths and payload keys, ensuring the producer and consumer cannot diverge.
Transport layer. The platform's device-to-device messaging service, accessed through a reactive wrapper, carrying serialised payloads on fixed paths — events and settings from phone to watch, refresh commands from watch to phone.
Scheduling layer. A system job registered against the calendar provider's content URI for edit-triggered syncs, a managed periodic work request, and a periodic background job — three independent paths converging on one synchronisation routine.
Data layer. No database on either device. The phone reads from the platform calendar provider with a bounded time window and an explicit projection; the watch persists the received snapshot and display settings to local preferences.
Rendering layer. Custom views and canvas drawing throughout, with paints and filters constructed once and reused, resolution-bucketed dimension resources, layout variants per display shape, and scheduled rather than continuous redraws.
Flow: Phone calendar provider → selective read and de-duplication → serialised snapshot → device-to-device message → watch cache → overlap resolution and geometry → canvas rendering (active and ambient paths), with a reverse refresh-request path from watch to phone.
Technology Stack
| Category | Technology |
|---|---|
| Languages | Kotlin, Java |
| Watch platform | Wear OS — canvas watch face service, wearable support library, AndroidX Wear |
| Device-to-device transport | Google Play Services Wearable Data Layer (message, node and capability clients, listener services) |
| Reactive layer | RxJava / RxAndroid with a reactive wrapper over the Data Layer |
| Serialisation | Gson |
| Calendar access | Android calendar content provider |
| Background scheduling | Android JobScheduler with a content-URI trigger, AndroidX WorkManager, and a periodic background job library |
| Date and time | Joda-Time for time-zone transition computation |
| Local storage | Android shared preferences (no database on either device) |
| Rendering | Custom Android views and Canvas — arc segments, multi-colour circular borders, snapping and centre-zoom list components |
| UI | AndroidX AppCompat, RecyclerView, ConstraintLayout, Material Components, data binding and view binding |
| Cross-resolution sizing | Scalable-dp dimension library with resolution-bucketed resources |
| Companion UI support | Glide for image loading, a sectioned list adapter for account-grouped calendars, a vendored top-sheet behaviour component |
| Build | Gradle multi-module (watch, companion, shared model, UI library) with Android Gradle Plugin |
Technical Challenges & Solutions
| Challenge | Our Approach |
|---|---|
| Keeping two devices in agreement with no server to arbitrate | The watch was made a read-only replica and the phone unconditionally authoritative, reducing reconciliation to de-duplication and overlap resolution. Each sync replaces the full snapshot rather than merging deltas, removing ordering and merge failures as a category. |
| A transport that gives no delivery guarantee and little feedback | Redundancy in both directions: three independent schedulers push from the phone, and the watch pulls when it suspects it is stale. A pairing probe with an acknowledgement handshake lets the companion distinguish an absent watch from a watch whose application is not running, so the user gets accurate guidance rather than a generic failure. |
| The watch being disconnected for much of the day | The complete event snapshot is cached locally on the watch and the face renders from cache on creation. Synchronisation refreshes the cache and is never on the path to a rendered frame, so the face is immediate and correct while offline. |
| Background execution suppressed by platform and manufacturer battery policies | Three independent triggers — a calendar-provider content observer for user edits, a managed periodic work request, and a periodic background job — converging on one sync routine, so suppression of any single mechanism does not stop synchronisation. |
| Duplicate events arriving from multiple calendar accounts | De-duplication performed at the producer, before transmission, so the watch never receives or renders the same appointment twice and the payload stays small. |
| Overlapping appointments competing for the same space on the dial | An explicit overlap comparator detects time-overlapping events and demotes the loser to a distinct secondary representation with its own colour treatment, rather than allowing an accidental overdraw. |
| Representing a full day on a twelve-hour dial, across daylight-saving changes | Explicit boundary handling for events crossing noon and midnight, and daylight-saving transition dates computed once from the device time zone and cached, so the render loop never recalculates them. |
| Battery cost of a custom-rendered, always-visible face | Paints, gradients and filters constructed once and reused, redraws scheduled on date rollover and fixed intervals rather than animated continuously, a separate cheaper ambient rendering path, and the week overview pre-rendered to bitmaps instead of running several live custom views at once. |
| Round, square and chinned displays at many resolutions | Layout variants per display shape, resolution-bucketed dimension resources and scalable-dp sizing, with geometry constants resolved by the resource system rather than computed at runtime. |
Security & Reliability
No server, no account, no network. The product has no backend, no user account and makes no web service calls. Calendar content is read from the phone's own calendar provider and delivered directly to the paired watch. There is no cloud store to breach and no credential to steal.
Minimal permissions. The companion requests one dangerous permission — calendar read — through a runtime request flow, and re-requests it if declined. The watch application requests none related to personal data.
User-selected data scope. Only the calendars the wearer explicitly selects are read and transmitted, so the amount of personal information crossing the device link is under the user's control rather than being all-or-nothing.
Bounded data window. The companion queries a narrow time window with an explicit column projection rather than reading the calendar wholesale, limiting both exposure and payload size.
Graceful degradation. Every failure mode — watch out of range, companion not running, platform services unavailable — resolves to the watch rendering its cached day, not to an error state on the face.
Self-healing synchronisation. The watch's ability to request a refresh means a missed push is corrected automatically at the next redraw boundary rather than persisting until the user intervenes.
Deterministic rendering state. The face and the full-screen views share one cached model and one set of comparators, so every surface on the watch shows the same day resolved the same way.
Scalability & Performance
Per-device cost is the scaling axis. With no server, performance work is entirely about battery, redraw budget and payload size — and each was addressed explicitly.
Bounded payloads. A narrow query window and a compact serialised representation keep transfers within the device-to-device transport's practical limits and keep the cached snapshot small.
Scheduled, not continuous, rendering. Redraws are triggered by date rollover, fixed intervals and ambient transitions. The face does not animate continuously.
A cheaper always-on path. Ambient mode renders the same geometry under a reduced palette and refresh budget, so always-on display support does not carry the active-mode cost.
Reused drawing objects. Paints, gradients, emboss and blur filters are constructed once and reused rather than allocated per frame — the difference between a smooth face and a stuttering one on wearable hardware.
Pre-rendered week overview. The seven-day view is composed from pre-rendered bitmaps rather than seven simultaneous live custom views competing for the same redraw budget.
Efficient calendar reads. Explicit projections and precomputed column index constants avoid dynamic lookups inside the cursor loop, keeping the background read short enough to complete within the platform's execution window.
A conservative background interval. Periodic synchronisation runs on a deliberately relaxed schedule, with edit-triggered and watch-requested syncs providing responsiveness instead of frequent polling.
Business Outcomes
- The schedule is legible without interaction, present on the surface the wearer already looks at rather than several taps into an application.
- The watch shows a correct day whether or not the phone is reachable, because rendering is driven by a local cache rather than by live connectivity.
- Synchronisation survives platform and manufacturer battery restrictions, because it does not depend on any single scheduling mechanism.
- Missed updates repair themselves, through a watch-initiated refresh path rather than requiring the user to notice and intervene.
- Duplicate and conflicting calendar entries resolve predictably, rather than producing visual artefacts on the dial.
- Personal calendar data stays within the device pair, with no backend, no account and no network transmission — a meaningful privacy position in a category where the opposite is normal.
- The wearer controls the data scope, choosing which calendars are read and synchronised.
- Always-on displays are supported without a second product, through a dedicated rendering path over shared geometry.
Why it worked
Wearable development punishes the habits that work everywhere else. There is no server to make authoritative, so the client has to hold the truth. There is no reliable connection, so offline has to be the default rather than the error case. There is no spare battery, so polling and continuous animation are simply unavailable. And there is no second chance at the interface — a watch face that takes a moment to load has already lost the user, because they have put their wrist down.
Our team builds for those constraints rather than around them. We chose a read-only replica model because it removes an entire class of synchronisation bugs before any of them can be written. We put the wire contract in a module both applications depend on, because in a system with no server that shared definition is the only thing preventing silent protocol drift. We made synchronisation redundant in three directions because Android's background execution rules cannot be relied upon and the alternative is a product that quietly stops working on some devices. And we made the consumer capable of asking for what it is missing, because the producer is the one component that cannot know it failed.
Our teams work across native Android and Kotlin, Wear OS and the wearable data layer, custom canvas rendering and view development, background scheduling under modern Android execution limits, and offline-first architectures for devices that spend much of their life disconnected — with the judgement to know when the right answer is a simpler data model rather than a cleverer merge algorithm.
Final Summary
A wristwatch gives a product about a second of attention on a screen the size of a coin, and the schedule is the one thing a person checks their watch for. Putting the day's calendar on the watch face itself — as shape rather than as a list — is an obvious idea that turns out to be constrained on every side: no server, no reliable link, no battery headroom, and no tolerance for a loading state.
Our team built the product around those constraints. The phone reads the wearer's selected calendars and pushes a de-duplicated snapshot to the watch; the watch caches that snapshot and renders from cache, so the face is immediate and correct whether or not the phone is in range. Synchronisation is triggered three independent ways so that no single battery policy can stop it, and the watch can request a refresh when it suspects it is stale — which is what repairs an update that never arrived. Because the watch never writes back, every sync is a clean snapshot replacement rather than a merge, and an entire category of conflict simply does not exist.
On top of that sits the rendering work: overlapping appointments resolved deliberately rather than drawn over one another, events crossing noon and midnight handled explicitly, daylight-saving transitions precomputed rather than recalculated per frame, a separate reduced-cost always-on path, seven configurable complication slots, and layouts for round, square and chinned displays. Delivered as a paired Android and Wear OS product with no backend and no account — where the wearer's calendar never leaves the two devices it belongs to.