voo_watch_ui 0.2.0 copy "voo_watch_ui: ^0.2.0" to clipboard
voo_watch_ui: ^0.2.0 copied to clipboard

Cross-platform UI DSL for smartwatches. Define your watch UI in Dart and render it identically on Apple Watch (native SwiftUI) and Wear OS (Flutter).

voo_watch_ui #

Write your watch UI in Dart. Render it on Apple Watch and Wear OS.

voo_watch_ui is a cross-platform UI DSL for smartwatches. You define a UI tree in Dart on your phone; a tiny native SwiftUI interpreter renders it on Apple Watch, and a Flutter renderer draws it on Wear OS. One source of truth, two ecosystems.

The honest truth. Flutter's engine cannot run on watchOS — Apple does not allow it. This package does not run Flutter on Apple Watch. Instead, it ships a small Swift interpreter (added to your watchOS target by the init CLI) that walks a JSON tree and produces native SwiftUI views. From the developer's seat you write Dart that "feels like Flutter"; on the watch the user sees fully native rendering.

What's in the box #

  • 40+ widget primitives covering layout, display, input, lists, motion, feedback, navigation. See Widget surface below.
  • Cross-platform theme system with phone-side auto-sync from Theme.of(context). One WatchUiThemeBinding and your watch follows the phone's brand colors automatically.
  • Modals: WatchAlert, WatchSheet, WatchToast — declarative, phone owns visibility.
  • Watch-native animation: WatchAnimatedScale / WatchAnimatedRotation for state-tracking animation, plus WatchPulse for trigger-based feedback that animates locally on the watch (no IPC round-trip jank).
  • Haptics: VooWatchUi.instance.haptic(WatchHapticKind.success) fires a tap on the wrist via the existing voo_watch bridge.
  • Crown / rotary support out of the box for sliders, steppers, and lists.
  • Per-instance color overrides on every input — switches, sliders, buttons, steppers all accept an activeColor / color that overrides the theme.

Quick start #

dependencies:
  voo_watch_ui: ^0.2.0
  voo_watch: ^0.2.0    # the bridge
import 'package:flutter/material.dart';
import 'package:voo_watch_ui/voo_watch_ui.dart';

void main() => runApp(const MaterialApp(
  theme: ThemeData(useMaterial3: true, colorSchemeSeed: Colors.indigo),
  home: WatchUiThemeBinding(child: HomeScreen()),
));

class HomeScreen extends StatefulWidget {
  @override State<HomeScreen> createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  int _bpm = 72;

  @override
  void initState() {
    super.initState();
    VooWatchUi.instance.onEvent('refresh', (_) {
      VooWatchUi.instance.haptic(WatchHapticKind.click);
      setState(() => _bpm += 1);
      _push();
    });
    _push();
  }

  void _push() => VooWatchUi.instance.render(
    WatchView.column(
      mainAxisAlignment: WatchMainAxisAlignment.center,
      children: <WatchView>[
        WatchView.text('Heart rate', style: WatchTextStyle.caption),
        WatchView.text('$_bpm bpm', style: WatchTextStyle.headline),
        WatchView.button(text: 'Refresh', onTap: 'refresh'),
      ],
    ),
  );

  @override Widget build(BuildContext context) =>
    const Scaffold(body: Center(child: Text('Watch UI driven from this app')));
}

WatchUiThemeBinding is a StatelessWidget you place under your MaterialApp — it watches Theme.of(context).colorScheme and pushes a matching WatchTheme to the watch automatically. Toggle dark mode and the watch follows.

Set up the Apple Watch side #

  1. In Xcode, add a watchOS App target to your Flutter project (File → New → Target → watchOS App).

  2. From your project root: dart run voo_watch_ui:init. This copies the Swift interpreter (WatchUiNode.swift, WatchUiRenderer.swift, WatchUiState.swift, WatchUiSession.swift) into the watch target folder.

  3. Replace the watch target's App.swift body with:

    @main
    struct MyWatchApp: App {
        @StateObject private var state = WatchUiState.shared
        init() { WatchUiSession.shared.start() }
        var body: some Scene {
            WindowGroup {
                WatchUiRenderer.render(state.root)
                    .environmentObject(state)
                    .environment(\.watchUiTheme, state.theme)
            }
        }
    }
    
  4. Build the watch scheme. Done.

If your iOS+watchOS companion build hits Xcode's "Cycle inside Runner" error, run dart run voo_watch:init --fix-build-phases.

Set up the Wear OS side #

A Wear OS Flutter app uses WatchUiTreeListener:

import 'package:flutter/material.dart';
import 'package:voo_watch_ui/voo_watch_ui.dart';
import 'package:voo_wear/voo_wear.dart';

class WatchHome extends StatelessWidget {
  @override
  Widget build(BuildContext context) => VooWearScaffold(
    body: WatchUiTreeListener(
      // Show brand colors from frame 1, before the phone's first update.
      initialTheme: WatchTheme.fromColorScheme(Theme.of(context).colorScheme),
      builder: (context, rendered) =>
        rendered ?? const Center(child: Text('Awaiting phone…')),
    ),
  );
}

Widget surface #

Layout — Column, Row, Stack, Wrap, Center, Padding, SizedBox, Expanded, Flexible, Spacer, SafeArea.

Display — Text, Image, Icon, Container, Divider, Card, Avatar (circle image / initials fallback), Badge (count or dot overlay), Chip (pill-shaped tag).

Input — Button, IconButton, TextField, Switch, Slider, Checkbox, Stepper (+/- with crown support). Every input accepts an optional per-instance accent color that overrides the theme.

Lists — ListView, ScrollView, ListTile (composable sugar).

Motion — GestureDetector, AnimatedOpacity, AnimatedScale, AnimatedRotation, Pulse (trigger-based scale animation; runs locally on the watch — no IPC round-trip per frame).

Feedback — ProgressIndicator (color/size/strokeWidth), Alert, Sheet, Toast.

Navigation — Page, PageView (horizontal-swipe paging with native dot indicators).

ThemeWatchTheme (9 semantic colors), WatchUiThemeBinding (auto-sync from Material), WatchTheme.fromColorScheme, per-widget overrides.

HapticsVooWatchUi.instance.haptic(WatchHapticKind.success) (7 kinds: click / success / warning / failure / notification / start / stop).

Escape hatchWatchView.custom(type: 'my-thing', props: {...}) lets you plug your own native view types into the renderer.

How it works #

Touch events are string IDs (onTap: 'refresh'). When the user taps on the watch, the watch sends WatchUiEvent('refresh') back to the phone via voo_watch's message bridge. The phone resolves the callback in a Dart-side registry and calls it. Re-renders are full-tree replacements with deep-equality deduplication — both renderers diff and patch on their side.

Latency on tap is ~50–150ms on real devices (one bridge round trip); ~500–1000ms on simulators (WCSession is much slower in simulation). UI updates from the phone are similarly asymmetric.

For animations that need to feel instant — tap feedback, success bursts — prefer WatchPulse (watch-side, single IPC) over WatchAnimatedScale (phone drives every frame, two IPCs per pulse).

Testing #

import 'package:voo_watch_ui/testing.dart';

testWidgets('button event reaches the registered handler', (tester) async {
  // FakeWatchUi swaps the bridge with an in-process fake.
  await FakeWatchUi.run(() async {
    var taps = 0;
    VooWatchUi.instance.onEvent('tap', (_) => taps += 1);
    await VooWatchUi.instance.render(
      WatchView.button(text: 'Hi', onTap: 'tap'),
    );
    FakeWatchUi.simulateTap('tap');
    expect(taps, 1);
  });
});

The Wear OS rendering path is also covered by wear_os_smoke_test.dart in this package — every primitive renders cleanly through WatchUiFlutterRenderer.

Status #

v0.2.0 ships the full design system: theme, modals, animation, haptics, plus the rounded-out widget set. APIs are considered stable for v0.x.

Out of scope (planned for later):

  • Light/dark adaptive theme presets (use WatchUiThemeBinding with a Material theme that already adapts — that's enough for most apps).
  • Animated theme transitions.
  • Tiles, complications, watch faces — see voo_watch.
2
likes
0
points
295
downloads

Publisher

verified publishervoostack.com

Weekly Downloads

Cross-platform UI DSL for smartwatches. Define your watch UI in Dart and render it identically on Apple Watch (native SwiftUI) and Wear OS (Flutter).

Homepage
Repository (GitHub)
View/report issues

Topics

#flutter #watch #watchos #wear-os #sdui

License

unknown (license)

Dependencies

args, flutter, meta, path, voo_watch, voo_wear

More

Packages that depend on voo_watch_ui