Accurate Step Counter
A highly accurate Flutter plugin for step counting using native Android TYPE_STEP_DETECTOR sensor with accelerometer fallback. 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 |
| π¦ Zero Dependencies | Only requires Flutter SDK |
| π 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.2.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 (Android β€10)
useForegroundServiceOnOldDevices: true,
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 Android β€10 |
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
ποΈ 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 10 and below. On Android 11+, 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
π Links
Made with β€οΈ for the Flutter community
Libraries
- accurate_step_counter
- Accurate step counter plugin with accelerometer-based detection