voo_watch 0.4.0
voo_watch: ^0.4.0 copied to clipboard
Flutter SDK for Apple Watch and Wear OS companion apps. Typed message bridge, haptics, health, complications, and a scaffolding CLI.
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_watchis 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 CLI —
dart run voo_watch:initgenerates a SwiftUI watchOS target. For Wear OS Flutter UI, usevoo_wear; for Tiles/Complications/Watch Faces use--variant compose. - Build-phase fixer —
dart run voo_watch:init --fix-build-phasesresolves the iOS+watchOS Xcode build cycle automatically. - Testable transport —
FakeVooWatchTransportfor widget tests; no paired device required. - Web-safe —
VooWatch.instancefalls back to an in-memory transport onkIsWeb, so phone-side code that callssend/renderno-ops cleanly in Flutter web hot-reload previews instead of throwingMissingPluginException. Pair withvoo_watch_ui'sWatchUiPreviewfor 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_watchgives both sides a typed-message channel overWCSession. - Wear OS. Flutter can run on Wear OS. Build the watch UI with
voo_wear(orvoo_watch_ui) and usevoo_watchfor the phone↔watch bridge over the Wearable Data Layer. If you also need Tiles/Complications/Watch Faces, scaffold a Compose module viadart 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 #
-
Add a watchOS App target in Xcode. File → New → Target → watchOS App. Pick a target name (e.g.
MyApp Watch App). -
Scaffold the Swift bridge files. From your project root:
dart run voo_watch:initThis drops
WatchSession.swift(and any companion Swift files) into the watch target folder. They wrapWCSessionand forward messages through the same envelope format the Dart side uses. -
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-phasesThis reorders the Runner target's build phases so the watch app embeds correctly. Safe to re-run; idempotent.
-
Pick a device when running. Once a watch companion is present,
flutter build ios --simulatorandflutter runrequire an explicit device id — list withflutter devicesand 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.