voo_watch

The phone↔watch bridge for Flutter apps. Typed messages, reachability, haptics, health, complications — one Dart API that works for Apple Watch and Wear OS.

Looking to write the watch UI in Dart instead of native code? See voo_watch_ui — same Dart tree renders as native SwiftUI on Apple Watch and Flutter on Wear OS. voo_watch is the underlying bridge.

What voo_watch ships

  • Typed messages — define a Dart class, send and receive it.
  • Reachability stream — observe pairing state without polling.
  • Application context — durable, OS-coalesced state delivered when the watch app foregrounds (independent of sendMessage).
  • Haptics — fire watch haptics from the phone with a unified type enum.
  • Health passthrough — HealthKit samples (watchOS) and Health Services samples (Wear OS) forwarded to the phone.
  • Complications & Tiles — update complications on Apple Watch and tiles on Wear OS from Dart.
  • Native scaffolding CLIdart run voo_watch:init generates a SwiftUI watchOS target. For Wear OS Flutter UI, use voo_wear; for Tiles/Complications/Watch Faces use --variant compose.
  • Build-phase fixerdart run voo_watch:init --fix-build-phases resolves the iOS+watchOS Xcode build cycle automatically.
  • Testable transportFakeVooWatchTransport for widget tests; no paired device required.
  • Web-safeVooWatch.instance falls back to an in-memory transport on kIsWeb, so phone-side code that calls send/render no-ops cleanly in Flutter web hot-reload previews instead of throwing MissingPluginException. Pair with voo_watch_ui's WatchUiPreview for browser-based iteration on watch UI trees.

How the platforms work

  • Apple Watch. Flutter cannot run on watchOS (Apple constraint). You write a small SwiftUI app; voo_watch gives both sides a typed-message channel over WCSession.
  • Wear OS. Flutter can run on Wear OS. Build the watch UI with voo_wear (or voo_watch_ui) and use voo_watch for the phone↔watch bridge over the Wearable Data Layer. If you also need Tiles/Complications/Watch Faces, scaffold a Compose module via dart run voo_watch:init --platform android --variant compose.

Install

dependencies:
  voo_watch: ^0.3.0

Usage

import 'package:flutter/widgets.dart';
import 'package:voo_watch/voo_watch.dart';

class PingMessage extends VooWatchMessage {
  const PingMessage(this.text);
  final String text;

  @override
  String get type => 'ping';

  @override
  Map<String, Object?> toJson() => {'text': text};

  static PingMessage fromJson(Map<String, Object?> json) =>
      PingMessage(json['text']! as String);
}

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  VooWatch.instance.registerCodec<PingMessage>('ping', PingMessage.fromJson);

  // Listen for messages from the watch.
  VooWatch.instance.messages<PingMessage>().listen((msg) {
    debugPrint('watch said: ${msg.text}');
  });

  // Send a message to the watch.
  await VooWatch.instance.send(const PingMessage('hello watch'));
}

Apple Watch setup

  1. Add a watchOS App target in Xcode. File → New → Target → watchOS App. Pick a target name (e.g. MyApp Watch App).

  2. Scaffold the Swift bridge files. From your project root:

    dart run voo_watch:init
    

    This drops WatchSession.swift (and any companion Swift files) into the watch target folder. They wrap WCSession and forward messages through the same envelope format the Dart side uses.

  3. Fix the iOS+watchOS build cycle. Companion-app builds hit Xcode's "Cycle inside Runner" error out of the box. One-time fix:

    dart run voo_watch:init --fix-build-phases
    

    This reorders the Runner target's build phases so the watch app embeds correctly. Safe to re-run; idempotent.

  4. Pick a device when running. Once a watch companion is present, flutter build ios --simulator and flutter run require an explicit device id — list with flutter devices and pass -d <id> (or pick the device in the VS Code status bar before F5).

The full Xcode walkthrough is in ios/voo_watch/README.md (created by init).

Wear OS setup

voo_watch is included automatically as a Flutter plugin — Gradle resolves the Android side. No init step needed for the bridge itself; just declare the dependency.

If you also want to write the Wear OS UI in Flutter, depend on voo_wear and build a separate Wear OS Flutter app module.

If you want a native Compose Wear OS module (for Tiles, Complications, or Watch Faces), run:

dart run voo_watch:init --platform android --variant compose

This scaffolds a Kotlin/Compose module wired to share the voo_watch transport with the Flutter app.

Sub-libraries

import 'package:voo_watch/voo_watch.dart';        // bridge + typed messages
import 'package:voo_watch/haptics.dart';          // VooWatchHaptics
import 'package:voo_watch/health.dart';           // VooWatchHealth
import 'package:voo_watch/complications.dart';    // VooWatchComplications + Tiles
import 'package:voo_watch/testing.dart';          // FakeVooWatchTransport

Testing

import 'package:flutter_test/flutter_test.dart';
import 'package:voo_watch/voo_watch.dart';
import 'package:voo_watch/testing.dart';

testWidgets('ping roundtrips', (tester) async {
  await FakeVooWatchTransport.run(() async {
    VooWatch.instance.registerCodec<PingMessage>('ping', PingMessage.fromJson);
    final received = <String>[];
    VooWatch.instance.messages<PingMessage>().listen((m) => received.add(m.text));

    FakeVooWatchTransport.simulateIncoming(const PingMessage('hi from watch'));
    await tester.pump();
    expect(received, ['hi from watch']);
  });
});

No paired device, simulator, or AVD required.

Status

v0.2 covers paired-companion mode on iOS↔watchOS and Android↔Wear OS, with the full message bridge, application context, reachability, haptics, health passthrough, and complications/tiles. Standalone watch apps, the watch-face SDK, and background workout sessions are tracked for v0.3.

Libraries

complications
Complications (Apple Watch) and tiles (Wear OS).
haptics
Watch haptics.
health
Health metric passthrough from the watch.
testing
Test utilities — in-process transport for widget tests.
voo_watch
Flutter SDK for Apple Watch and Wear OS companion experiences.