accurate_step_counter 1.3.0 copy "accurate_step_counter: ^1.3.0" to clipboard
accurate_step_counter: ^1.3.0 copied to clipboard

A highly accurate step counter plugin using accelerometer-based detection with low-pass filtering and peak detection. Includes Hive database logging with warmup validation. Supports foreground, backgr [...]

Accurate Step Counter #

pub package License: MIT

A highly accurate Flutter plugin for step counting using native Android TYPE_STEP_DETECTOR sensor with accelerometer fallback. Includes local Hive database logging with warmup validation. Zero external dependencies. Designed for reliability across foreground, background, and terminated app states.

✨ Features #

Feature Description
🎯 Native Detection Uses Android's hardware-optimized TYPE_STEP_DETECTOR sensor
πŸ”„ Accelerometer Fallback Software algorithm for devices without step detector
πŸ’Ύ Hive Logging Local persistent storage with source tracking (foreground/background/terminated)
πŸ”₯ Warmup Validation Buffer steps during warmup, validate walking before logging
πŸ“¦ Zero Dependencies Only requires Flutter SDK + Hive
πŸ”‹ Battery Efficient Event-driven, not polling-based
πŸ“± All App States Foreground, background, and terminated state support
βš™οΈ Configurable Presets for walking/running + custom parameters

πŸ“± Platform Support #

Platform Status
Android βœ… Full support (API 19+)
iOS ❌ Not supported

Note: This is an Android-only package. It won't crash on iOS but step detection won't work.

πŸš€ Quick Start #

1. Install #

dependencies:
  accurate_step_counter: ^1.3.0

2. Add Permissions #

In android/app/src/main/AndroidManifest.xml:

<uses-permission android:name="android.permission.ACTIVITY_RECOGNITION"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_HEALTH"/>
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>

3. Request Runtime Permission #

import 'package:permission_handler/permission_handler.dart';

// Request activity recognition (required)
await Permission.activityRecognition.request();

// Request notification (for Android 13+ foreground service)
await Permission.notification.request();

4. Start Counting! #

import 'package:accurate_step_counter/accurate_step_counter.dart';

final stepCounter = AccurateStepCounter();

// Listen to step events
stepCounter.stepEventStream.listen((event) {
  print('Steps: ${event.stepCount}');
});

// Start counting
await stepCounter.start();

// Stop when done
await stepCounter.stop();

// Clean up
await stepCounter.dispose();

πŸ“– Complete Example #

import 'package:flutter/material.dart';
import 'package:accurate_step_counter/accurate_step_counter.dart';
import 'dart:async';

class StepCounterScreen extends StatefulWidget {
  @override
  State<StepCounterScreen> createState() => _StepCounterScreenState();
}

class _StepCounterScreenState extends State<StepCounterScreen> {
  final _stepCounter = AccurateStepCounter();
  StreamSubscription<StepCountEvent>? _subscription;
  int _steps = 0;
  bool _isRunning = false;

  @override
  void initState() {
    super.initState();
    _subscription = _stepCounter.stepEventStream.listen((event) {
      setState(() => _steps = event.stepCount);
    });
  }

  Future<void> _toggleTracking() async {
    if (_isRunning) {
      await _stepCounter.stop();
    } else {
      await _stepCounter.start();
    }
    setState(() => _isRunning = !_isRunning);
  }

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Step Counter')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text('$_steps', style: const TextStyle(fontSize: 80, fontWeight: FontWeight.bold)),
            const Text('steps', style: TextStyle(fontSize: 24, color: Colors.grey)),
            const SizedBox(height: 40),
            ElevatedButton.icon(
              onPressed: _toggleTracking,
              icon: Icon(_isRunning ? Icons.stop : Icons.play_arrow),
              label: Text(_isRunning ? 'Stop' : 'Start'),
            ),
            TextButton(
              onPressed: () {
                _stepCounter.reset();
                setState(() => _steps = 0);
              },
              child: const Text('Reset'),
            ),
          ],
        ),
      ),
    );
  }
}

βš™οΈ Configuration #

Presets #

// For walking (default)
await stepCounter.start(config: StepDetectorConfig.walking());

// For running (more sensitive, faster detection)
await stepCounter.start(config: StepDetectorConfig.running());

// Sensitive mode (may have false positives)
await stepCounter.start(config: StepDetectorConfig.sensitive());

// Conservative mode (fewer false positives)
await stepCounter.start(config: StepDetectorConfig.conservative());

Custom Configuration #

await stepCounter.start(
  config: StepDetectorConfig(
    threshold: 1.2,                // Movement threshold (higher = less sensitive)
    filterAlpha: 0.85,             // Smoothing factor (0.0 - 1.0)
    minTimeBetweenStepsMs: 250,    // Minimum ms between steps
    enableOsLevelSync: true,       // Sync with OS step counter
    
    // Foreground service options
    useForegroundServiceOnOldDevices: true,
    foregroundServiceMaxApiLevel: 29, // API level threshold (default: 29 = Android 10)
    foregroundNotificationTitle: 'Step Tracker',
    foregroundNotificationText: 'Counting your steps...',
  ),
);

Configuration Parameters #

Parameter Default Description
threshold 1.0 Movement threshold for step detection
filterAlpha 0.8 Low-pass filter smoothing (0.0-1.0)
minTimeBetweenStepsMs 200 Minimum time between steps
enableOsLevelSync true Sync with OS step counter
useForegroundServiceOnOldDevices true Use foreground service on older Android
foregroundServiceMaxApiLevel 29 Max API level for foreground service (29=Android 10, 31=Android 12, etc.)
foregroundNotificationTitle "Step Counter" Notification title
foregroundNotificationText "Tracking your steps..." Notification text

πŸ”§ API Reference #

AccurateStepCounter #

final stepCounter = AccurateStepCounter();

// Properties
stepCounter.stepEventStream       // Stream<StepCountEvent>
stepCounter.currentStepCount      // int
stepCounter.isStarted             // bool
stepCounter.isUsingForegroundService  // bool
stepCounter.currentConfig         // StepDetectorConfig?

// Methods
await stepCounter.start({config});    // Start detection
await stepCounter.stop();             // Stop detection
stepCounter.reset();                  // Reset count to zero
await stepCounter.dispose();          // Clean up resources

// Check sensor type
final isHardware = await stepCounter.isUsingNativeDetector();

// Terminated state sync (automatic, but can be manual)
stepCounter.onTerminatedStepsDetected = (steps, startTime, endTime) {
  print('Synced $steps missed steps');
};

StepCountEvent #

final event = StepCountEvent(stepCount: 100, timestamp: DateTime.now());

event.stepCount   // int - Total steps since start()
event.timestamp   // DateTime - When step was detected

πŸ’Ύ Hive Step Logging #

Setup #

import 'package:flutter/foundation.dart';
import 'package:accurate_step_counter/accurate_step_counter.dart';

final stepCounter = AccurateStepCounter();

// Initialize logging database
// debugLogging: kDebugMode = only show console logs in debug builds
await stepCounter.initializeLogging(debugLogging: kDebugMode);

// Start counting
await stepCounter.start();

// Start logging with a preset
await stepCounter.startLogging(config: StepLoggingConfig.walking());

Debug Logging Parameter #

Control console output with the debugLogging parameter:

// No console output (default - recommended for production)
await stepCounter.initializeLogging(debugLogging: false);

// Always show console logs
await stepCounter.initializeLogging(debugLogging: true);

// Only in debug builds (recommended)
await stepCounter.initializeLogging(debugLogging: kDebugMode);

Console output examples when debugLogging: true:

AccurateStepCounter: Logging database initialized
AccurateStepCounter: Warmup started
AccurateStepCounter: Warmup validated - 15 steps at 1.87/s
AccurateStepCounter: Logged 15 warmup steps (source: StepLogSource.foreground)

Logging Presets #

Preset Warmup Min Steps Max Rate Use Case
walking() 5s 8 3/s Casual walks
running() 3s 10 5/s Jogging/running
sensitive() 0s 3 6/s Quick detection
conservative() 10s 15 2.5/s Strict accuracy
noValidation() 0s 1 100/s Raw data
// Use a preset
await stepCounter.startLogging(config: StepLoggingConfig.walking());

// Custom configuration
await stepCounter.startLogging(
  config: StepLoggingConfig(
    logIntervalMs: 5000,       // Log every 5 seconds
    warmupDurationMs: 8000,    // 8 second warmup period
    minStepsToValidate: 10,    // Need 10+ steps to confirm walking
    maxStepsPerSecond: 4.0,    // Reject rates above 4/second
  ),
);

Complete Example #

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:accurate_step_counter/accurate_step_counter.dart';

class StepTrackerPage extends StatefulWidget {
  @override
  State<StepTrackerPage> createState() => _StepTrackerPageState();
}

class _StepTrackerPageState extends State<StepTrackerPage> 
    with WidgetsBindingObserver {
  final _stepCounter = AccurateStepCounter();
  int _totalSteps = 0;
  int _foregroundSteps = 0;
  int _backgroundSteps = 0;

  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this);
    _init();
  }

  Future<void> _init() async {
    // 1. Initialize logging (with debug output in debug builds)
    await _stepCounter.initializeLogging(debugLogging: kDebugMode);
    
    // 2. Start step detection
    await _stepCounter.start();
    
    // 3. Start logging with walking preset
    await _stepCounter.startLogging(config: StepLoggingConfig.walking());
    
    // 4. Listen to total steps in real-time
    _stepCounter.watchTotalSteps().listen((total) {
      setState(() => _totalSteps = total);
    });
    
    // 5. Handle terminated state sync
    _stepCounter.onTerminatedStepsDetected = (steps, from, to) {
      print('Synced $steps steps from terminated state');
    };
  }

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    // Track app state for proper source detection
    _stepCounter.setAppState(state);
  }

  Future<void> _refreshStats() async {
    final fg = await _stepCounter.getStepsBySource(StepLogSource.foreground);
    final bg = await _stepCounter.getStepsBySource(StepLogSource.background);
    setState(() {
      _foregroundSteps = fg;
      _backgroundSteps = bg;
    });
  }

  @override
  void dispose() {
    WidgetsBinding.instance.removeObserver(this);
    _stepCounter.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        Text('Total: $_totalSteps steps'),
        Text('Foreground: $_foregroundSteps'),
        Text('Background: $_backgroundSteps'),
        ElevatedButton(
          onPressed: _refreshStats,
          child: Text('Refresh Stats'),
        ),
      ],
    );
  }
}

Query API #

// Aggregate total
final total = await stepCounter.getTotalSteps();

// Today's steps
final today = DateTime.now();
final startOfDay = DateTime(today.year, today.month, today.day);
final todaySteps = await stepCounter.getTotalSteps(from: startOfDay);

// By source
final fgSteps = await stepCounter.getStepsBySource(StepLogSource.foreground);
final bgSteps = await stepCounter.getStepsBySource(StepLogSource.background);
final termSteps = await stepCounter.getStepsBySource(StepLogSource.terminated);

// Get all logs
final logs = await stepCounter.getStepLogs();

// Statistics
final stats = await stepCounter.getStepStats();
// Returns: {totalSteps, entryCount, averagePerEntry, averagePerDay, 
//           foregroundSteps, backgroundSteps, terminatedSteps}

Real-Time Streams #

// Watch total steps
stepCounter.watchTotalSteps().listen((total) {
  print('Total: $total');
});

// Watch all logs with filter
stepCounter.watchStepLogs(source: StepLogSource.foreground).listen((logs) {
  for (final log in logs) {
    print('${log.stepCount} steps at ${log.toTime}');
  }
});

Data Management #

// Clear all logs
await stepCounter.clearStepLogs();

// Delete logs older than 30 days
await stepCounter.deleteStepLogsBefore(
  DateTime.now().subtract(Duration(days: 30)),
);

πŸ—οΈ Architecture #

Overall Flow #

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                         Flutter App                              β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  AccurateStepCounter                                            β”‚
β”‚       β”œβ”€β”€ stepEventStream (real-time steps)                     β”‚
β”‚       β”œβ”€β”€ currentStepCount                                      β”‚
β”‚       └── onTerminatedStepsDetected (missed steps callback)     β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  NativeStepDetector (Dart)                                      β”‚
β”‚       β”œβ”€β”€ MethodChannel (commands)                              β”‚
β”‚       └── EventChannel (step events)                            β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                           β”‚
                    Platform Channel
                           β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                     Android Native (Kotlin)                      β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  AccurateStepCounterPlugin                                      β”‚
β”‚       β”œβ”€β”€ NativeStepDetector.kt (sensor handling)               β”‚
β”‚       β”œβ”€β”€ StepCounterForegroundService.kt (Android ≀10)         β”‚
β”‚       └── SharedPreferences (state persistence)                 β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  Android Sensors                                                β”‚
β”‚       β”œβ”€β”€ TYPE_STEP_DETECTOR (primary - hardware)               β”‚
β”‚       └── TYPE_ACCELEROMETER (fallback - software)              β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Step Detection Priority #

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚            Check: TYPE_STEP_DETECTOR            β”‚
β”‚              (Hardware Sensor)                  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                       β”‚
           β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
           β”‚     Available?        β”‚
           β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                       β”‚
          β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
          β”‚                         β”‚
    β”Œβ”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”            β”Œβ”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”
    β”‚    YES    β”‚            β”‚     NO      β”‚
    β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜            β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜
          β”‚                         β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Hardware Step    β”‚    β”‚  Accelerometer    β”‚
β”‚  Detection        β”‚    β”‚  + Algorithm      β”‚
β”‚                   β”‚    β”‚                   β”‚
β”‚  β€’ Best accuracy  β”‚    β”‚  β€’ Low-pass filterβ”‚
β”‚  β€’ Battery saving β”‚    β”‚  β€’ Peak detection β”‚
β”‚  β€’ Event-driven   β”‚    β”‚  β€’ Configurable   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

πŸ“± App State Coverage #

How Each State is Handled #

App State Android 11+ (API 30+) Android ≀10 (API ≀29)
🟒 Foreground Native TYPE_STEP_DETECTOR Native TYPE_STEP_DETECTOR
🟑 Background Native detection continues Foreground Service keeps counting
πŸ”΄ Terminated OS sync on app relaunch Foreground Service prevents termination
πŸ“’ Notification ❌ None (not needed) βœ… Shows (required by Android)

Important: The persistent notification only appears on Android devices with API level ≀ foregroundServiceMaxApiLevel (default: 29 = Android 10). On newer devices, no notification is shown because the native step detector works without needing a foreground service.

Detailed State Behavior #

🟒 Foreground State

App Active β†’ NativeStepDetector β†’ TYPE_STEP_DETECTOR β†’ EventChannel β†’ Flutter UI
  • Real-time step counting with immediate updates
  • Hardware-optimized detection
  • Full access to all sensors

🟑 Background State

Android 11+:

App Minimized β†’ Native detection continues β†’ Steps buffered β†’ UI updates when resumed

Android ≀10:

App Minimized β†’ Foreground Service starts β†’ Persistent notification shown
                    ↓
              Keeps CPU active via WakeLock
                    ↓
              Steps counted continuously
                    ↓
              Results polled every 500ms

πŸ”΄ Terminated State

Android 11+:

App Killed β†’ OS continues counting via TYPE_STEP_COUNTER
                    ↓
             App Relaunched
                    ↓
       Compare saved count with current OS count
                    ↓
       Calculate missed steps
                    ↓
       Trigger onTerminatedStepsDetected callback

Android ≀10:

Foreground Service prevents true termination
                    ↓
       Service continues counting even if Activity destroyed
                    ↓
       No steps are ever missed

Terminated State Sync (Android 11+) #

// Automatic sync happens on start(), but you can handle it:
stepCounter.onTerminatedStepsDetected = (missedSteps, startTime, endTime) {
  print('You walked $missedSteps steps while app was closed!');
  print('From: $startTime to $endTime');
  
  // Optionally save to database or sync to server
  saveToDatabase(missedSteps, startTime, endTime);
};

πŸ”‹ Battery & Performance #

Metric Value
Detection Method Event-driven (not polling)
CPU Usage Minimal (~1-2%)
Battery Impact Low (uses hardware sensor)
Memory ~2-5 MB
Foreground Service Battery Moderate (only Android ≀10)

πŸ› Debugging #

View Logs #

# All plugin logs
adb logcat -s AccurateStepCounter NativeStepDetector StepSync

# Only step events
adb logcat -s NativeStepDetector

Check Sensor Availability #

final isHardware = await stepCounter.isUsingNativeDetector();
print('Using hardware step detector: $isHardware');

❓ Troubleshooting #

Issue Solution
Steps not detected Check ACTIVITY_RECOGNITION permission is granted
Inaccurate counts Try adjusting threshold parameter
Stops in background Enable foreground service or check battery optimization
No notification (Android ≀10) Grant notification permission

πŸ“„ License #

MIT License - see LICENSE


Made with ❀️ for the Flutter community

1
likes
0
points
1.44k
downloads

Publisher

verified publisherrahulsha.com.np

Weekly Downloads

A highly accurate step counter plugin using accelerometer-based detection with low-pass filtering and peak detection. Includes Hive database logging with warmup validation. Supports foreground, background, and terminated state tracking.

Repository (GitHub)
View/report issues

Documentation

Documentation

License

unknown (license)

Dependencies

flutter, hive, hive_flutter

More

Packages that depend on accurate_step_counter

Packages that implement accurate_step_counter