flutter_watch_bridge: one Dart API for Apple Watch and Wear OS

pub package CI License: MIT

One Dart API for talking to a paired Apple Watch (WatchConnectivity) or Wear OS watch (Wearable Data Layer) from a Flutter phone app.

Flutter can't draw the watch UI, so you write that natively (SwiftUI / Compose for Wear OS). This package gives the phone a single API and ships a Swift package and a Kotlin module so the watch side speaks the same protocol.

Platform support

Android iOS Web Windows Linux macOS
Wear OS Apple Watch fallback fallback fallback fallback

Windows, Linux, macOS and web have no OS API for pairing with a watch. There status.supported is false and sends throw WatchUnsupportedException, so an app that targets those platforms still compiles and runs.

Apple Watch Wear OS
Live messages + replies sendMessage MessageClient
Latest state sync application context DataClient item
Guaranteed queue transferUserInfo DataClient items, consumed once
File transfer transferFile ChannelClient
Pairing / reachability isPaired, isReachable, ... connected nodes + capability

How it fits together

Architecture: Flutter app, Dart layer and platform channels, iOS and Android plugins, Apple Watch and Wear OS helpers

Try it without a watch

The example app has a Simulated watch switch that wires the phone to an in-memory watch, so you can send messages, sync state and queue items immediately. Run it scripted with flutter run --dart-define=DEMO=true.

Example app with a simulated watch, showing the status chips and a log of messages and state

Quick start

final bridge = WatchBridge.instance;
await bridge.initialize();              // call early, then attach listeners

bridge.status.listen((s) => print('reachable: ${s.reachable}'));

// Live message, optionally waiting for an answer
await bridge.sendMessage({'hello': 'watch'});
final reply = await bridge.request({'ping': 1}, timeout: const Duration(seconds: 5));

// Answer requests coming from the watch
bridge.setRequestHandler((request) => {'pong': request.data['ping']});

// State: the watch always ends up with the latest value
await bridge.syncState('settings', {'units': 'metric'});
bridge.stateStream('workout').listen((s) => print(s));

// Guaranteed, ordered delivery (survives offline + restarts)
await bridge.enqueue({'event': 'login'});

// Live if reachable, queued otherwise
final outcome = await bridge.sendReliable({'event': 'sync'});

// Files, with progress
final transfer = await bridge.sendFile(File(path), metadata: {'name': 'a.png'});
transfer.progress.listen((p) => print(p.fraction));
await transfer.done;
bridge.incomingFiles.listen((f) => print(f.path));

Payloads are Map<String, Object?> holding null, bool, num, String, List, nested maps and Uint8List. They are limited to about 90 KB; use sendFile for more.

Choosing a delivery method

Need Use
Instant, watch is open sendMessage / request (throws WatchNotReachableException)
"What is the current X" syncState (older values are replaced)
Every event, in order enqueue
Don't care which of the two sendReliable
Large data sendFile

Setup

  1. Add the dependency, then run dart run flutter_watch_bridge:doctor to check the native setup.
  2. Add a watch app: watchOS / Wear OS.
  3. Use WatchBridge.instance on the phone and the helper in watch/apple or watch/android on the watch.

See doc/platform_differences.md for the places where the two platforms behave differently, and doc/migration.md if you come from another package.

Testing without a watch in code

import 'package:flutter_watch_bridge/testing.dart';

final link = SimulatedWatchLink();
final phone = WatchBridge.forPlatform(link.phone);
final watch = WatchBridge.forPlatform(link.watch);
link.reachable = false;   // state and queue are held, messages throw

The example app has a "Simulated watch" switch that uses the same thing.

Limitations

  • Real communication works on Android and iOS phones only. Windows, Linux, macOS and web have no OS API for pairing with an Apple Watch or Wear OS watch, so there status.supported is false and sends throw WatchUnsupportedException. Apps that target those platforms still compile and run.
  • The watch UI is native. Sample sources are in example/watch_samples.
  • Wear OS messages are only received while the app process runs; state and queued items are picked up on the next initialize().
  • Not a Health/sensor API, and no complications or tiles.

Libraries

flutter_watch_bridge
One API for messages, state sync and file transfer between a Flutter phone app and its paired Apple Watch or Wear OS watch.
testing
In-memory fakes for testing and for developing without a watch.