vitals 0.1.1 copy "vitals: ^0.1.1" to clipboard
vitals: ^0.1.1 copied to clipboard

Type-safe access to Apple Health and Health Connect. Bucketed statistics computed on-device, permissions modelled honestly, and an in-memory fake so health flows are testable.

example/lib/main.dart

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

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

/// Demonstrates availability, permissions, and bucketed statistics.
class VitalsDemo extends StatelessWidget {
  /// Creates the demo.
  const VitalsDemo({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'vitals',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(colorSchemeSeed: const Color(0xFF11A97C)),
      home: const DemoPage(),
    );
  }
}

/// The demo's single screen.
class DemoPage extends StatefulWidget {
  /// Creates the page.
  const DemoPage({super.key});

  @override
  State<DemoPage> createState() => _DemoPageState();
}

class _DemoPageState extends State<DemoPage> {
  /// Swap for `FakeVitals()` to run this without a device.
  final Vitals _vitals = Vitals.instance;

  bool? _available;
  String _status = '';
  List<VitalStatistic> _steps = const <VitalStatistic>[];

  static const PermissionRequest _wanted = PermissionRequest(
    read: <VitalType<VitalSample>>{
      VitalType.steps,
      VitalType.heartRate,
      VitalType.bodyMass,
    },
    write: <VitalType<VitalSample>>{VitalType.water},
  );

  @override
  void initState() {
    super.initState();
    _vitals.isAvailable().then(
      (bool ok) => mounted ? setState(() => _available = ok) : null,
    );
  }

  Future<void> _request() async {
    final bool completed = await _vitals.requestPermissions(_wanted);
    if (!mounted) return;
    setState(
      () => _status = completed
          ? 'Sheet completed. On iOS this does not tell you what was granted.'
          : 'The sheet was dismissed or failed.',
    );
  }

  /// Writes a sample and reads it straight back — the shortest proof the whole
  /// round trip works on a device with no existing health data.
  Future<void> _logWater() async {
    final DateTime now = DateTime.now();
    try {
      await _vitals.writeVolume(
        VitalType.water,
        const Volume.millilitres(250),
        at: now,
      );
      final List<VolumeSample> back = await _vitals.read(
        VitalType.water,
        from: now.subtract(const Duration(minutes: 5)),
        to: now.add(const Duration(minutes: 5)),
      );
      final double total = back.fold<double>(
        0,
        (double sum, VolumeSample s) => sum + s.value.millilitres,
      );
      if (!mounted) return;
      setState(
        () => _status =
            'Wrote 250 ml. Read back ${back.length} sample(s), '
            '${total.round()} ml total.',
      );
    } on VitalsException catch (e) {
      if (!mounted) return;
      setState(() => _status = '${e.runtimeType}: ${e.message}');
    }
  }

  Future<void> _loadSteps() async {
    final DateTime now = DateTime.now();
    final List<VitalStatistic> stats;
    try {
      stats = await _vitals.statistics(
        VitalType.steps,
        from: now.subtract(const Duration(days: 7)),
        to: now,
        bucket: VitalBucket.daily,
      );
    } on VitalsException catch (e) {
      if (!mounted) return;
      return setState(() => _status = '${e.runtimeType}: ${e.message}');
    }
    if (!mounted) return;
    setState(() {
      _steps = stats;
      _status = stats.every((VitalStatistic s) => !s.hasData)
          ? 'No step data. Either permission was refused or there is none — '
                'iOS cannot tell you which.'
          : 'Loaded ${stats.length} days.';
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('vitals')),
      body: ListView(
        padding: const EdgeInsets.all(20),
        children: <Widget>[
          Card(
            child: ListTile(
              title: const Text('Health data available'),
              subtitle: Text(switch (_available) {
                null => 'checking…',
                true => 'yes',
                false => 'no — needs HealthKit or Health Connect',
              }),
            ),
          ),
          const SizedBox(height: 12),
          FilledButton(
            onPressed: _request,
            child: const Text('Request permissions'),
          ),
          const SizedBox(height: 8),
          OutlinedButton(
            onPressed: _loadSteps,
            child: const Text('Steps, last 7 days'),
          ),
          const SizedBox(height: 8),
          OutlinedButton(
            onPressed: _logWater,
            child: const Text('Write 250 ml water, then read it back'),
          ),
          if (_status.isNotEmpty) ...<Widget>[
            const SizedBox(height: 16),
            Text(_status, style: Theme.of(context).textTheme.bodySmall),
          ],
          const SizedBox(height: 16),
          for (final VitalStatistic day in _steps)
            ListTile(
              dense: true,
              title: Text(day.start.toIso8601String().split('T').first),
              trailing: Text(
                day.hasData ? day.value!.round().toString() : '—',
                style: TextStyle(
                  fontWeight: FontWeight.w600,
                  // A recorded zero and no data are different, and the UI
                  // should say so rather than printing 0 for both.
                  color: day.hasData ? null : Theme.of(context).disabledColor,
                ),
              ),
            ),
        ],
      ),
    );
  }
}
1
likes
160
points
35
downloads

Documentation

Documentation
API reference

Publisher

verified publisherjumyn.com

Weekly Downloads

Type-safe access to Apple Health and Health Connect. Bucketed statistics computed on-device, permissions modelled honestly, and an in-memory fake so health flows are testable.

Repository (GitHub)
View/report issues

Topics

#health #healthkit #health-connect #fitness #ios

License

MIT (license)

Dependencies

flutter

More

Packages that depend on vitals

Packages that implement vitals