flutter_watchos 0.1.0-beta.2 copy "flutter_watchos: ^0.1.0-beta.2" to clipboard
flutter_watchos: ^0.1.0-beta.2 copied to clipboard

Platform detection and utilities for Flutter apps running on Apple Watch (watchOS). Provides runtime checks for watchOS, device info, capability queries, and Taptic Engine haptics.

example/lib/main.dart

// Copyright 2026 The FlutterWatch Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'dart:async';

import 'package:flutter/material.dart';
import 'package:flutter_watchos/flutter_watchos.dart';

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

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

  @override
  Widget build(BuildContext context) {
    // Force a dark theme so text/buttons stay readable on the black watch
    // background — the default light text theme renders dark-on-black.
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      theme: ThemeData.dark(useMaterial3: true),
      home: const HomeScreen(),
    );
  }
}

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

  @override
  State<HomeScreen> createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  @override
  Widget build(BuildContext context) {
    // A compact, scrollable layout — sized for the watch screen.
    return Scaffold(
      backgroundColor: Colors.black,
      body: SafeArea(
        // WatchCrownScroll gives this list the native watch feel: watch-tuned
        // physics with a firm, shallow edge bounce (no edge haptic, matching
        // watchOS 26). The crown scroll motion/acceleration/detent ticks come
        // from the watchOS engine.
        child: WatchCrownScroll(
          child: ListView(
            padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
            children: [
              const Text(
                'flutter_watchos',
                style: TextStyle(
                    fontSize: 15,
                    fontWeight: FontWeight.bold,
                    color: Colors.white),
              ),
              const SizedBox(height: 8),
              _Row('isWatch', '${FlutterWatchosPlatform.isWatch}'),
              _Row('Platform.isIOS', '${FlutterWatchosPlatform.isAppleMobile}'),
              _Row('isWatchOS', '${WatchOSInfo.isWatchOS}'),
              _Row('version', WatchOSInfo.watchOSVersion),
              _Row('model', WatchOSInfo.deviceModel),
              _Row('machine', WatchOSInfo.machineId),
              _Row('simulator', '${WatchOSInfo.isSimulator}'),
              _Row('screen', WatchOSInfo.screenResolution),
              _Row('scale', '${WatchOSInfo.screenScale}x'),
              const SizedBox(height: 10),
              Padding(
                padding: const EdgeInsets.only(bottom: 8),
                child: ElevatedButton(
                  onPressed: () => Navigator.of(context).push(
                    MaterialPageRoute<void>(
                      builder: (_) => const CrownDemoScreen(),
                    ),
                  ),
                  child: const Text('crown demo →'),
                ),
              ),
              // System clock (WatchStatusBar) — the watch draws the time over
              // every app; hide it for an immersive/full-bleed screen. Visible
              // by default, per the watchOS HIG.
              Padding(
                padding: const EdgeInsets.only(bottom: 8),
                child: ElevatedButton(
                  onPressed: () => setState(
                      () => WatchStatusBar.hidden = !WatchStatusBar.hidden),
                  child: Text(
                      'system clock: ${WatchStatusBar.hidden ? "hidden" : "shown"}',
                      style: const TextStyle(fontSize: 11)),
                ),
              ),
              // Crown scroll options (WatchCrownScrolling) — the native-parity
              // sensitivity + detent-haptic knobs, applied engine-side.
              const Text('crown sensitivity',
                  style: TextStyle(color: Colors.white70, fontSize: 11)),
              Row(
                children: [
                  for (final s in WatchCrownSensitivity.values)
                    Expanded(
                      child: Padding(
                        padding: const EdgeInsets.all(2),
                        child: ElevatedButton(
                          style: ElevatedButton.styleFrom(
                            padding: EdgeInsets.zero,
                            backgroundColor:
                                WatchCrownScrolling.sensitivity == s
                                    ? Colors.blueGrey
                                    : null,
                          ),
                          onPressed: () => setState(
                              () => WatchCrownScrolling.sensitivity = s),
                          child: Text(s.name,
                              style: const TextStyle(fontSize: 11)),
                        ),
                      ),
                    ),
                ],
              ),
              Padding(
                padding: const EdgeInsets.only(bottom: 8),
                child: ElevatedButton(
                  onPressed: () => setState(() => WatchCrownScrolling
                      .detentHaptics = !WatchCrownScrolling.detentHaptics),
                  child: Text(
                      'detent ticks: ${WatchCrownScrolling.detentHaptics ? "on" : "off"}',
                      style: const TextStyle(fontSize: 11)),
                ),
              ),
              for (final type in WatchHapticType.values)
                Padding(
                  padding: const EdgeInsets.only(bottom: 4),
                  child: ElevatedButton(
                    onPressed: () => WatchHaptics.play(type),
                    child: Text('haptic: ${type.name}'),
                  ),
                ),
            ],
          ),
        ),
      ),
    );
  }
}

/// Demonstrates raw Digital Crown input via [WatchCrown]: the crown drives a
/// 0–100 value directly (it does not scroll), with a tick at each end.
class CrownDemoScreen extends StatefulWidget {
  const CrownDemoScreen({super.key});

  @override
  State<CrownDemoScreen> createState() => _CrownDemoScreenState();
}

class _CrownDemoScreenState extends State<CrownDemoScreen> {
  static const double _sensitivity = 6.0; // crown units → value units
  double _value = 50;
  StreamSubscription<CrownRotationEvent>? _sub;

  @override
  void initState() {
    super.initState();
    // Subscribing switches the crown into raw mode; cancelling (in dispose)
    // hands it back to scroll for the rest of the app.
    _sub = WatchCrown.instance.rotations.listen(_onRotate);
  }

  void _onRotate(CrownRotationEvent e) {
    final double next = (_value + e.delta * _sensitivity).clamp(0.0, 100.0);
    if (next == _value) return;
    if ((next == 0.0 || next == 100.0) && next != _value) {
      WatchHaptics.play(WatchHapticType.stop); // hit an end
    }
    setState(() => _value = next);
  }

  @override
  void dispose() {
    _sub?.cancel();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.black,
      body: SafeArea(
        child: Stack(
          children: [
            Center(
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  const Text('Turn the crown',
                      style: TextStyle(color: Colors.white70, fontSize: 13)),
                  const SizedBox(height: 8),
                  Text(
                    _value.toStringAsFixed(0),
                    style: const TextStyle(
                        color: Colors.white,
                        fontSize: 48,
                        fontWeight: FontWeight.bold),
                  ),
                  const SizedBox(height: 12),
                  SizedBox(
                    width: 140,
                    child: LinearProgressIndicator(
                      value: _value / 100,
                      backgroundColor: Colors.white24,
                      color: Colors.lightBlueAccent,
                    ),
                  ),
                ],
              ),
            ),
            // The watch host has no native swipe-back, so provide one.
            Align(
              alignment: Alignment.topLeft,
              child: IconButton(
                visualDensity: VisualDensity.compact,
                padding: EdgeInsets.zero,
                constraints: const BoxConstraints(),
                onPressed: () => Navigator.of(context).pop(),
                icon: const Icon(Icons.chevron_left,
                    color: Colors.white, size: 28),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

class _Row extends StatelessWidget {
  const _Row(this.label, this.value);

  final String label;
  final String value;

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.symmetric(vertical: 2),
      child: Row(
        mainAxisAlignment: MainAxisAlignment.spaceBetween,
        children: [
          Text(label,
              style: const TextStyle(color: Colors.white70, fontSize: 12)),
          Flexible(
            child: Text(
              value,
              textAlign: TextAlign.right,
              style: const TextStyle(
                  fontSize: 12,
                  fontWeight: FontWeight.w600,
                  color: Colors.white),
            ),
          ),
        ],
      ),
    );
  }
}
0
likes
130
points
124
downloads

Documentation

API reference

Publisher

verified publisherflutterwatch.dev

Weekly Downloads

Platform detection and utilities for Flutter apps running on Apple Watch (watchOS). Provides runtime checks for watchOS, device info, capability queries, and Taptic Engine haptics.

License

BSD-3-Clause (license)

Dependencies

ffi, flutter

More

Packages that depend on flutter_watchos

Packages that implement flutter_watchos