voo_wear
Flutter SDK for building Wear OS apps in Dart.
voo_wear is the watch-side complement to voo_watch.
Where voo_watch is the phone↔watch bridge that works for both Apple Watch
and Wear OS, voo_wear lets you write the actual Wear OS UI in Flutter — no
Compose, no Kotlin.
Apple Watch note. Flutter cannot run on watchOS; that's an Apple constraint, not a project decision. For Apple Watch you still write a tiny SwiftUI app — or use
voo_watch_uito define one Dart tree that renders as native SwiftUI on Apple Watch and Flutter on Wear OS.
What you get
VooWearScaffold— opinionated app shell for tiny screens (status bar, time tray, round-aware safe area, ambient dimming).- Round-screen detection —
VooWearShape.of(context)returnsroundorrectangular. Branch your layout once, render correctly everywhere. - Ambient mode —
VooWearAmbientBuilderrebuilds when the watch dims so you can swap to a low-color, black-background layout. - Rotary input —
VooRotaryListenerexposes bezel/crown rotation as a Dart stream. - Round safe areas —
VooWearRoundSafeAreainsets your content past the corner mask. - Native plugin — wraps
Configuration.isScreenRound,AmbientLifecycleObserver, andMotionEvent.AXIS_SCROLLfor you. - Test harness —
FakeVooWearPlatformfor widget tests; no AVD required.
Install
dependencies:
voo_wear: ^0.1.0
voo_watch: ^0.2.0 # bridge to the phone (optional but typical)
Wear OS Flutter app setup
-
Create a Flutter app for Wear OS. Either a separate Flutter project or a peer module in your monorepo:
flutter create --platforms=android my_wear_app -
Configure the Android manifest for Wear OS. In
android/app/src/main/AndroidManifest.xml, add inside<manifest>:<uses-feature android:name="android.hardware.type.watch" />And inside
<application>:<meta-data android:name="com.google.android.wearable.standalone" android:value="true" /> -
Set
minSdk = 26inandroid/app/build.gradle.kts(Wear OS requires API 26+):defaultConfig { minSdk = 26 } -
Use
VooWearScaffoldat your app root:import 'package:flutter/material.dart'; import 'package:voo_wear/voo_wear.dart'; void main() => runApp(const MaterialApp( theme: ThemeData(brightness: Brightness.dark, useMaterial3: true), home: WatchHome(), )); class WatchHome extends StatelessWidget { const WatchHome({super.key}); @override Widget build(BuildContext context) => VooWearScaffold( body: VooWearAmbientBuilder( builder: (context, ambient) { if (ambient == VooWearAmbientState.ambient) { return const _AmbientLayout(); } return const _ActiveLayout(); }, ), ); } -
Run on a Wear OS AVD. Open Android Studio → Device Manager → Create Device → Wear OS. Pick a Wear OS image (API 30+ recommended). Then:
flutter run -d <wear-avd-id>
Round vs rectangular
VooWearShapeBuilder(
builder: (context, shape) => shape == VooWearShape.round
? _RoundLayout()
: _RectLayout(),
);
For round screens, wrap content in VooWearRoundSafeArea to clear the corner
clipping mask:
VooWearRoundSafeArea(
child: Column(
children: [Text('Centered text'), Text('Stays inside the curve')],
),
)
Ambient mode
VooWearAmbientBuilder rebuilds when the watch enters or leaves ambient. Use
it to swap to a minimal, monochrome layout while the wrist is down:
VooWearAmbientBuilder(
builder: (context, ambient) =>
ambient == VooWearAmbientState.ambient
? const _AmbientFace()
: const _ActiveFace(),
);
Rotary input
VooRotaryListener(
onScroll: (delta) => _scrollController.jumpTo(_scrollController.offset + delta),
child: ListView(...),
);
Or listen as a stream via VooWearPlatform.instance.rotaryEvents.
Pair with the phone
Add voo_watch to send messages between the Wear OS
app and the phone:
import 'package:voo_watch/voo_watch.dart';
VooWatch.instance.registerCodec<MyMessage>('my_msg', MyMessage.fromJson);
VooWatch.instance.messages<MyMessage>().listen(_onMessage);
await VooWatch.instance.send(MyMessage(...));
For a one-stop same-tree-on-both-watches approach, see
voo_watch_ui — phone defines the UI; both watches
render it.
Companion demos
apps/voo_watch_demo— Flutter phone app driving both watches via voo_watch_ui.apps/voo_wear_demo— Flutter Wear OS app usingVooWearScaffold+WatchUiTreeListener.
Pair a Pixel AVD with a Wear OS AVD, install both APKs, and you have a fully-Dart smartwatch experience on Wear OS.
Testing
import 'package:flutter_test/flutter_test.dart';
import 'package:voo_wear/voo_wear.dart';
import 'package:voo_wear/testing.dart';
testWidgets('ambient builder swaps layout', (tester) async {
await FakeVooWearPlatform.run(() async {
await tester.pumpWidget(/* ... */);
FakeVooWearPlatform.setAmbient(VooWearAmbientState.ambient);
await tester.pump();
expect(find.text('Ambient'), findsOneWidget);
});
});
Status
v0.1 covers the core widgets and platform-channel bindings (round detection,
ambient mode, rotary input). Tiles, Complications, and Watch Faces are
intentionally out of scope — those still require Compose on the native side.
If you need them, drop into dart run voo_watch:init --platform android --variant compose to scaffold a Compose module that lives alongside your
Flutter app.