Skip to main content

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

Pedometer & Step Detection. Get step count in a timeRange & stream live steps.

example/lib/main.dart

import 'dart:async';

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:pedometer_pro/pedometer_pro.dart';
import 'package:permission_handler/permission_handler.dart';

void main() {
  runApp(const PedometerProExampleApp());
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Pedometer Pro',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.teal),
        useMaterial3: true,
      ),
      home: const PedometerHomePage(),
    );
  }
}

class _DaySteps {
  const _DaySteps(this.day, this.result);
  final DateTime day;
  final StepCountResult result;
}

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

  @override
  State<PedometerHomePage> createState() => _PedometerHomePageState();
}

class _PedometerHomePageState extends State<PedometerHomePage> {
  final Pedometer _pedometer = Pedometer();

  /// iOS stores about 7 days; Android Recording API stores about 10.
  int get _historyDays =>
      (defaultTargetPlatform == TargetPlatform.iOS) ? 7 : 10;

  PermissionStatus? _permission;
  int? _todaySteps;
  int? _historyTotal;
  List<_DaySteps> _daily = const [];
  String? _historySource;
  bool _startingStreams = false;
  String? _rangeError;
  bool _loadingRange = false;

  int? _streamSteps;
  int? _streamFromToday;
  PedestrianStatus? _status;
  String? _streamError;
  String? _statusError;

  StreamSubscription<int>? _stepSub;
  StreamSubscription<int>? _stepFromSub;
  StreamSubscription<PedestrianStatus>? _statusSub;

  @override
  void dispose() {
    _stepSub?.cancel();
    _stepFromSub?.cancel();
    _statusSub?.cancel();
    super.dispose();
  }

  Future<void> _requestPermission() async {
    final status = (defaultTargetPlatform == TargetPlatform.android)
        ? await Permission.activityRecognition.request()
        : await Permission.sensors.request();
    if (mounted) setState(() => _permission = status);
  }

  bool get _granted {
    final status = _permission;
    return status == PermissionStatus.granted ||
        status == PermissionStatus.limited;
  }

  Future<void> _loadHistory() async {
    if (_loadingRange) return;
    setState(() {
      _loadingRange = true;
      _rangeError = null;
      _todaySteps = null;
      _historyTotal = null;
      _daily = const [];
      _historySource = null;
    });
    try {
      final now = DateTime.now();
      final todayStart = DateTime(now.year, now.month, now.day);
      final daily = <_DaySteps>[];
      for (var i = _historyDays - 1; i >= 0; i--) {
        if (!mounted) return;
        final day = DateTime(now.year, now.month, now.day - i);
        final next = DateTime(day.year, day.month, day.day + 1);
        final end = next.isAfter(now) ? now : next;
        if (!day.isBefore(end)) continue;
        final result = await _pedometer.getStepCountDetailed(
          from: day,
          to: end,
        );
        daily.add(_DaySteps(day, result));
      }
      if (!mounted) return;
      final sources = daily.map((d) => d.result.source).toSet();
      setState(() {
        _daily = daily;
        _todaySteps = daily
            .where((d) => d.day == todayStart)
            .firstOrNull
            ?.result
            .steps;
        // A total is meaningful only when every day came from the same provider.
        _historyTotal = sources.length == 1
            ? daily.fold<int>(0, (sum, d) => sum + d.result.steps)
            : null;
        _historySource = sources.map((s) => s.name).join(', ');
        if (sources.length > 1) {
          _rangeError =
              'History source changed. Reload to get a consistent total.';
        }
      });
    } catch (e) {
      if (mounted) setState(() => _rangeError = e.toString());
    } finally {
      if (mounted) setState(() => _loadingRange = false);
    }
  }

  Future<void> _listenStreams() async {
    if (_startingStreams) return;
    setState(() => _startingStreams = true);
    try {
      await Future.wait([
        if (_stepSub != null) _stepSub!.cancel(),
        if (_stepFromSub != null) _stepFromSub!.cancel(),
        if (_statusSub != null) _statusSub!.cancel(),
      ]);
      if (!mounted) return;
      setState(() {
        _streamError = null;
        _statusError = null;
        _streamSteps = null;
        _status = null;
        _streamFromToday = null;
      });
      _stepSub = _pedometer.stepCountStream().listen(
        (steps) {
          if (mounted) setState(() => _streamSteps = steps);
        },
        onError: (Object e) {
          if (mounted) setState(() => _streamError = e.toString());
        },
      );
      _statusSub = _pedometer.pedestrianStatusStream().listen(
        (status) {
          if (mounted) setState(() => _status = status);
        },
        onError: (Object e) {
          if (mounted) setState(() => _statusError = e.toString());
        },
      );
      if ((defaultTargetPlatform == TargetPlatform.iOS)) {
        final now = DateTime.now();
        final todayStart = DateTime(now.year, now.month, now.day);
        _stepFromSub = _pedometer
            .stepCountStreamFrom(from: todayStart)
            .listen(
              (steps) {
                if (mounted) setState(() => _streamFromToday = steps);
              },
              onError: (Object e) {
                if (mounted) setState(() => _streamError = e.toString());
              },
            );
      }
    } catch (e) {
      if (mounted) setState(() => _streamError = e.toString());
    } finally {
      if (mounted) setState(() => _startingStreams = false);
    }
  }

  String _formatDay(DateTime day) {
    final m = day.month.toString().padLeft(2, '0');
    final d = day.day.toString().padLeft(2, '0');
    return '${day.year}-$m-$d';
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Pedometer Pro')),
      body: ListView(
        padding: const EdgeInsets.all(16),
        children: [
          Text(
            (defaultTargetPlatform == TargetPlatform.android)
                ? 'Android history comes from Health Connect (Samsung Health / Google Fit / other apps). '
                      'Galaxy phones: install Health Connect, open Samsung Health → Settings → Health Connect, '
                      'and allow this app to read steps. '
                      'play-services-fitness Local Recording is only this app\'s own log after subscribe, '
                      'not Samsung Health. If Health Connect is empty, today falls back to TYPE_STEP_COUNTER '
                      '(steps since last boot).'
                : 'iOS: getStepCount reads CoreMotion history (about 7 days).',
            style: Theme.of(context).textTheme.bodyMedium,
          ),
          const SizedBox(height: 16),
          FilledButton(
            onPressed: _requestPermission,
            child: const Text('Request permission'),
          ),
          const SizedBox(height: 8),
          Text('Permission: ${_permission?.name ?? 'not requested'}'),
          const Divider(height: 32),
          Text(
            'History (getStepCount)',
            style: Theme.of(context).textTheme.titleMedium,
          ),
          const SizedBox(height: 8),
          FilledButton.tonal(
            onPressed:
                !_loadingRange &&
                    ((defaultTargetPlatform == TargetPlatform.android) ||
                        _granted)
                ? _loadHistory
                : null,
            child: Text('Load history (today + last $_historyDays days)'),
          ),
          const SizedBox(height: 8),
          if (_loadingRange) const LinearProgressIndicator(),
          if (_rangeError != null)
            Text(_rangeError!, style: const TextStyle(color: Colors.red))
          else ...[
            Text('Today: ${_todaySteps ?? '-'}'),
            Text('Last $_historyDays days: ${_historyTotal ?? '-'}'),
            if (_historySource != null) Text('Source: $_historySource'),
            if (_daily.isNotEmpty) ...[
              const SizedBox(height: 12),
              Text('Per day', style: Theme.of(context).textTheme.titleSmall),
              const SizedBox(height: 4),
              for (final entry in _daily)
                Padding(
                  padding: const EdgeInsets.symmetric(vertical: 2),
                  child: Row(
                    children: [
                      SizedBox(width: 120, child: Text(_formatDay(entry.day))),
                      Text(
                        '${entry.result.steps} (${entry.result.source.name})',
                      ),
                    ],
                  ),
                ),
            ],
          ],
          const Divider(height: 32),
          Text('Live streams', style: Theme.of(context).textTheme.titleMedium),
          const SizedBox(height: 8),
          FilledButton.tonal(
            onPressed: _granted && !_startingStreams ? _listenStreams : null,
            child: const Text('Start live streams'),
          ),
          const SizedBox(height: 8),
          Text(_streamError ?? 'Steps since boot: ${_streamSteps ?? '-'}'),
          if ((defaultTargetPlatform == TargetPlatform.iOS))
            Text('Steps from today (stream): ${_streamFromToday ?? '-'}'),
          Text(_statusError ?? 'Pedestrian status: ${_status?.name ?? '-'}'),
          if (_permission == PermissionStatus.denied ||
              _permission == PermissionStatus.permanentlyDenied)
            TextButton(
              onPressed: openAppSettings,
              child: const Text('Open app settings'),
            ),
        ],
      ),
    );
  }
}
0
likes
160
points
127
downloads
screenshot

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Pedometer & Step Detection. Get step count in a timeRange & stream live steps.

Homepage
Repository (GitHub)
View/report issues

Topics

#pedometer #step-counter #step-tracker #fit #health

License

BSD-3-Clause (license)

Dependencies

flutter

More

Packages that depend on pedometer_pro

Packages that implement pedometer_pro