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

PlatformAndroid

Live sensor readings (heart rate, steps, ...) on Wear OS via the Health Services MeasureClient, without starting an exercise session.

example/lib/main.dart

import 'dart:async';

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:wear_health_measure/wear_health_measure.dart';

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

/// Entrypoint for the multi-engine check. It is executed in a second, headless
/// FlutterEngine by `MainActivity.spawnAndDestroySecondEngine()`. Plugins are
/// registered in that engine too; destroying it must not affect the first one.
@pragma('vm:entry-point')
Future<void> secondEngineMain() async {
  // Headless engine: no runApp(), so the binding must be set up by hand.
  WidgetsFlutterBinding.ensureInitialized();
  debugPrint('[second engine] started, plugin registered');
  final caps = await const WearHealthMeasure().getCapabilities();
  debugPrint('[second engine] capabilities: $caps');
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'wear_health_measure',
      theme: ThemeData.dark(useMaterial3: true),
      home: const MeasurePage(),
    );
  }
}

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

  @override
  State<MeasurePage> createState() => _MeasurePageState();
}

class _MeasurePageState extends State<MeasurePage> {
  static const _multiEngine = MethodChannel(
    'wear_health_measure_example/multi_engine',
  );

  final _client = const WearHealthMeasure();
  late Future<Set<MeasureDataType>> _capabilities;
  final Map<MeasureDataType, StreamSubscription<MeasureEvent>> _subscriptions =
      {};
  final Map<MeasureDataType, String> _lastValue = {};
  final Map<MeasureDataType, String> _availability = {};
  final Map<MeasureDataType, String> _errors = {};
  String? _multiEngineStatus;

  @override
  void initState() {
    super.initState();
    _capabilities = _client.getCapabilities();
  }

  @override
  void dispose() {
    for (final sub in _subscriptions.values) {
      sub.cancel();
    }
    super.dispose();
  }

  void _toggle(MeasureDataType type, bool start) {
    if (!start) {
      _subscriptions.remove(type)?.cancel();
      setState(() {});
      return;
    }
    _errors.remove(type);
    _subscriptions[type] = _client
        .measure(type)
        .listen(
          (event) => setState(() {
            switch (event) {
              case MeasureSample(:final value, :final timestamp):
                final v = value is double ? value.toStringAsFixed(1) : '$value';
                final t = timestamp.toLocal().toIso8601String().substring(
                  11,
                  19,
                );
                _lastValue[type] = '$v  @ $t';
              case MeasureAvailabilityChanged(:final availability):
                _availability[type] = availability.name;
            }
          }),
          onError: (Object e) => setState(() {
            _errors[type] = e.toString();
            _subscriptions.remove(type);
          }),
          onDone: () => setState(() => _subscriptions.remove(type)),
        );
    setState(() {});
  }

  Future<void> _spawnAndDestroySecondEngine() async {
    setState(() => _multiEngineStatus = 'second engine running…');
    await _multiEngine.invokeMethod<void>('spawnAndDestroy');
    await Future<void>.delayed(const Duration(seconds: 4));
    if (mounted) {
      setState(
        () => _multiEngineStatus =
            'second engine destroyed – values above must keep updating',
      );
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: FutureBuilder<Set<MeasureDataType>>(
        future: _capabilities,
        builder: (context, snapshot) {
          if (snapshot.hasError) {
            return Center(
              child: Padding(
                padding: const EdgeInsets.all(24),
                child: Text(
                  '${snapshot.error}',
                  textAlign: TextAlign.center,
                  style: const TextStyle(color: Colors.redAccent),
                ),
              ),
            );
          }
          if (!snapshot.hasData) {
            return const Center(child: CircularProgressIndicator());
          }
          final types = snapshot.data!.toList()
            ..sort((a, b) => a.index.compareTo(b.index));
          return ListView(
            padding: const EdgeInsets.symmetric(vertical: 32, horizontal: 12),
            children: [
              Center(
                child: Text(
                  '${types.length} measurable types',
                  style: Theme.of(context).textTheme.titleSmall,
                ),
              ),
              for (final type in types) _tile(type),
              const SizedBox(height: 16),
              FilledButton.tonal(
                onPressed: _spawnAndDestroySecondEngine,
                child: const Text('Multi-engine check'),
              ),
              if (_multiEngineStatus != null)
                Padding(
                  padding: const EdgeInsets.all(8),
                  child: Text(
                    _multiEngineStatus!,
                    textAlign: TextAlign.center,
                    style: Theme.of(context).textTheme.bodySmall,
                  ),
                ),
              const SizedBox(height: 32),
            ],
          );
        },
      ),
    );
  }

  Widget _tile(MeasureDataType type) {
    final active = _subscriptions.containsKey(type);
    final subtitle = [
      ?_availability[type],
      ?_lastValue[type],
      ?_errors[type],
    ].join('\n');
    return SwitchListTile(
      dense: true,
      contentPadding: const EdgeInsets.symmetric(horizontal: 8),
      title: Text(type.name, style: const TextStyle(fontSize: 13)),
      subtitle: subtitle.isEmpty
          ? null
          : Text(
              subtitle,
              style: TextStyle(
                fontSize: 11,
                color: _errors.containsKey(type) ? Colors.redAccent : null,
              ),
            ),
      value: active,
      onChanged: (value) => _toggle(type, value),
    );
  }
}
0
likes
160
points
303
downloads

Documentation

API reference

Publisher

verified publishercodivo.de

Weekly Downloads

Live sensor readings (heart rate, steps, ...) on Wear OS via the Health Services MeasureClient, without starting an exercise session.

Repository (GitHub)
View/report issues

Topics

#wear-os #health #heart-rate #sensors #android

License

MIT (license)

Dependencies

flutter

More

Packages that depend on wear_health_measure

Packages that implement wear_health_measure