Skip to main content

flutter_watch_bridge 0.1.0 copy "flutter_watch_bridge: ^0.1.0" to clipboard
flutter_watch_bridge: ^0.1.0 copied to clipboard

One API for messages, state sync and file transfer between a Flutter phone app and its paired Apple Watch or Wear OS watch, with pairing and reachability streams.

example/lib/main.dart

import 'dart:async';
import 'dart:io';

import 'package:flutter/material.dart';
import 'package:flutter_watch_bridge/flutter_watch_bridge.dart';
import 'package:flutter_watch_bridge/testing.dart';

/// `flutter run --dart-define=DEMO=true` starts the simulated watch and plays
/// a short scripted exchange (used for the README screenshots).
const _demo = bool.fromEnvironment('DEMO');

void main() => runApp(const ExampleApp());

class ExampleApp extends StatelessWidget {
  const ExampleApp({super.key});

  @override
  Widget build(BuildContext context) => MaterialApp(
        title: 'Watch Bridge',
        debugShowCheckedModeBanner: false,
        theme: ThemeData(colorSchemeSeed: Colors.teal, useMaterial3: true),
        home: const HomePage(),
      );
}

class HomePage extends StatefulWidget {
  const HomePage({super.key});

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  late WatchBridge _bridge = WatchBridge.instance;
  final _subs = <StreamSubscription<Object?>>[];
  final _log = <String>[];
  WatchStatus? _status;
  bool _simulated = false;
  SimulatedWatchLink? _link;
  WatchBridge? _simWatch;

  @override
  void initState() {
    super.initState();
    if (_demo) {
      _playDemo();
    } else {
      _start();
    }
  }

  Future<void> _playDemo() async {
    await _toggleSimulator(true);
    final watch = _simWatch!;
    await _run('sendMessage', () => _bridge.sendMessage({'hello': 'watch'}));
    await _run('request', () => _bridge.request({'ping': 42}));
    await _run('syncState', () => _bridge.syncState('phone', {'units': 'metric'}));
    await watch.syncState('watch', {'heartRate': 72});
    await watch.sendMessage({'tapped': 'start workout'});
    await _run('enqueue', () => _bridge.enqueue({'event': 'login'}));
    await _run('sendReliable', () async => (await _bridge.sendReliable({'event': 'sync'})).name);
  }

  Future<void> _start() async {
    for (final s in _subs) {
      await s.cancel();
    }
    _subs.clear();
    try {
      await _bridge.initialize();
    } on WatchBridgeException catch (e) {
      _add('init failed: $e');
    }
    _subs
      ..add(_bridge.status.listen((s) => setState(() => _status = s)))
      ..add(_bridge.messages.listen((m) => _add('message: ${m.data}')))
      ..add(_bridge.queuedItems.listen((m) => _add('queued item: $m')))
      ..add(_bridge.stateStream('watch').listen((m) => _add('watch state: $m')))
      ..add(_bridge.incomingFiles.listen((f) => _add('file: ${f.path}')));
    _bridge.setRequestHandler((m) => {'pong': m.data['ping']});
  }

  void _add(String line) => setState(() => _log.insert(0, line));

  Future<void> _run(String label, Future<Object?> Function() action) async {
    try {
      final r = await action();
      _add('$label ok${r == null ? '' : ': $r'}');
    } on WatchBridgeException catch (e) {
      _add('$label failed: ${e.runtimeType} ${e.message}');
    }
  }

  Future<void> _toggleSimulator(bool on) async {
    if (on) {
      _link = SimulatedWatchLink();
      _bridge = WatchBridge.forPlatform(_link!.phone);
      _simWatch = WatchBridge.forPlatform(_link!.watch);
      await _simWatch!.initialize();
      _simWatch!.setRequestHandler((m) => {'watch_echo': m.data});
      _simWatch!.messages.listen((m) => _add('(sim watch got) ${m.data}'));
    } else {
      _bridge = WatchBridge.instance;
      _link = null;
      _simWatch = null;
    }
    setState(() => _simulated = on);
    await _start();
  }

  @override
  void dispose() {
    for (final s in _subs) {
      s.cancel();
    }
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    final s = _status;
    return Scaffold(
      appBar: AppBar(title: const Text('Watch Bridge')),
      body: ListView(padding: const EdgeInsets.all(16), children: [
        SwitchListTile(
          title: const Text('Simulated watch'),
          subtitle: const Text('In-memory watch, no hardware needed'),
          value: _simulated,
          onChanged: _toggleSimulator,
        ),
        if (_link != null)
          SwitchListTile(
            title: const Text('Simulated watch reachable'),
            value: _link!.reachable,
            onChanged: (v) => setState(() => _link!.reachable = v),
          ),
        Card(
          child: Padding(
            padding: const EdgeInsets.all(12),
            child: s == null
                ? const Text('Waiting for status...')
                : Wrap(spacing: 8, children: [
                    for (final e in {
                      'supported': s.supported,
                      'paired': s.paired,
                      'app installed': s.appInstalled,
                      'reachable': s.reachable,
                      'activated': s.activated,
                    }.entries)
                      Chip(
                        avatar: Icon(e.value ? Icons.check_circle : Icons.cancel,
                            color: e.value ? Colors.green : Colors.red),
                        label: Text(e.key),
                      ),
                  ]),
          ),
        ),
        const SizedBox(height: 8),
        Wrap(spacing: 8, runSpacing: 8, children: [
          FilledButton(
            onPressed: () => _run('sendMessage', () => _bridge.sendMessage({'hello': 'watch'})),
            child: const Text('Send message'),
          ),
          FilledButton(
            onPressed: () => _run('request', () => _bridge.request({'ping': DateTime.now().millisecond})),
            child: const Text('Request / reply'),
          ),
          FilledButton(
            onPressed: () => _run('syncState', () => _bridge.syncState('phone', {'counter': _log.length})),
            child: const Text('Sync state'),
          ),
          FilledButton(
            onPressed: () => _run('enqueue', () => _bridge.enqueue({'queued': _log.length})),
            child: const Text('Queue item'),
          ),
          FilledButton(
            onPressed: () => _run('sendReliable', () async => (await _bridge.sendReliable({'r': 1})).name),
            child: const Text('Send reliably'),
          ),
          FilledButton(
            onPressed: () => _run('sendFile', () async {
              final file = File('${Directory.systemTemp.path}/hello.txt')..writeAsStringSync('hello watch');
              final t = await _bridge.sendFile(file, metadata: {'name': 'hello.txt'});
              t.progress.listen((p) => _add('transfer ${(p.fraction * 100).round()}%'));
              await t.done;
              return 'done';
            }),
            child: const Text('Send file'),
          ),
        ]),
        const Divider(height: 32),
        for (final line in _log) Text(line, style: const TextStyle(fontFamily: 'monospace')),
      ]),
    );
  }
}
0
likes
150
points
66
downloads
screenshot

Documentation

API reference

Publisher

verified publishertechflow.ir

Weekly Downloads

One API for messages, state sync and file transfer between a Flutter phone app and its paired Apple Watch or Wear OS watch, with pairing and reachability streams.

Repository (GitHub)
View/report issues

Topics

#watch #wear-os #watchos #wearable #connectivity

License

MIT (license)

Dependencies

flutter, flutter_web_plugins, plugin_platform_interface

More

Packages that depend on flutter_watch_bridge

Packages that implement flutter_watch_bridge