HomeCase studiesA Wear OS Watch Face with Offline-First...

Case study · Singapore, Singapore

A Wear OS Watch Face with Offline-First Calendar Synchronisation

Consumer wearables and personal productivity

A smartwatch has about one second of the wearer's attention and a screen the size of a coin. Our team built a Wear OS product that puts the day's schedule on the watch face itself — calendar events rendered as graphical segments around the dial — paired with an Android companion application that keeps the watch supplied with the phone's calendar. The engineering problem was not the drawing. It was keeping two devices in agreement over an intermittent, low-bandwidth link with no server anywhere in the system, on a battery budget that forbids polling.

Industry
Consumer wearables and personal productivity
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.
Platforms
Android · Web & API
Stack
Kotlin · Java
Location
Singapore, Singapore · delivered remotely
Role
Engineering, with the team behind for mobile and QA

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.

01 — Questions

asked about this kind of project

How do you synchronise data between a phone and a smartwatch without a backend?

Decide which device is authoritative and make the other a replica. In this case the phone owns the calendar and the watch never writes to it, so there is no bidirectional conflict to merge — each sync replaces the watch's copy in full. That is only viable when the payload is small, but when it is, snapshot replacement removes ordering and merge failures entirely. The alternative — two writers reconciling deltas over an unreliable link with no arbiter — is where these projects usually go wrong.

How do you make a wearable app work offline?

Treat offline as the default rather than the failure case. The watch persists the complete data snapshot locally and renders from that cache, so nothing on screen waits for connectivity. Synchronisation refreshes the cache in the background and is never on the path to a rendered frame. The practical test is simple: if the device has been out of range all morning, the screen should still be correct and immediate, not showing a spinner.

How do you keep background synchronisation running under Android's battery restrictions?

Do not depend on a single mechanism. Platform Doze rules and manufacturer battery policies suppress background work unpredictably, and a lone periodic timer will quietly stop firing on some devices. Combining an event-driven trigger (a content observer on the data source, which fires when the user actually changes something), a managed periodic work request and a second periodic job means suppression of any one path still leaves the others. Then let the consuming device request a refresh when it suspects it is stale — that is the recovery path that catches everything else.

What makes watch face development different from normal Android UI work?

The budget. A watch face is potentially always visible, so every allocation in the draw path costs battery, and always-on ambient mode has both a restricted palette and a much lower refresh allowance — effectively a second rendering path over the same geometry. Add round, square and chinned displays across many resolutions, and stock widgets stop being sufficient. Most of the work is custom canvas drawing with objects constructed once and reused, and redraws scheduled deliberately rather than animated.

How do you handle overlapping items in a circular or timeline visualisation?

Decide the rule explicitly rather than letting the drawing order decide for you. Overlaps are detected with a comparator before rendering, and the overlapped item is demoted to a distinct secondary representation with its own visual treatment. Without that, two simultaneous entries overdraw each other and the display silently misrepresents the data — which is worse than showing nothing, because the user has no way of knowing.

Is it safe to read a user's calendar or other personal data on-device?

It is when the data does not leave the device. This product has no backend, no account and makes no network calls: personal data is read through the platform provider under a runtime permission and delivered directly to the user's own paired watch. The user also selects which sources are read, so the scope is theirs to control. Reading a narrow time window with an explicit column projection, rather than pulling everything, keeps both exposure and payload small.

Should a Wear OS app be standalone or companion-dependent?

It depends on where the data lives. When the source of truth is a phone-side provider — the device calendar, for instance — a companion is the natural producer, and the watch application is best designed to keep working from cache when the companion is unreachable. Where the watch can obtain data itself, standalone operation removes an entire failure mode. The two are not mutually exclusive, and the architecture should not assume the companion is always there.

What should be built first in a wearable product?

The data path, before the visuals. Synchronisation, caching and offline behaviour determine whether the product works at all, and they are much harder to retrofit than a rendering change. It is tempting to start with the face because that is what gets demonstrated — but a beautiful face showing yesterday's schedule is a broken product, and the reasons it broke are all in the layer nobody looks at.

03 — Similar project?

describe what is different about yours

Build yours.

Tell me what exists, what you need and the deadline. Fixed price for a defined scope, or hourly from $10 with a written estimate first.

Ahmedabad, India · IST (UTC+5:30) · --:-- IST · Mon–Fri 09:00–18:00 IST · US & EU overlap daily

No newsletter, no CRM. Just a reply.