accurate_step_counter 1.3.1 copy "accurate_step_counter: ^1.3.1" to clipboard
accurate_step_counter: ^1.3.1 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 #

Sweet & Simple - Works in ALL States (Foreground, Background, Terminated) #

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

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: const StepCounterPage(),
    );
  }
}

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

  @override
  State<StepCounterPage> createState() => _StepCounterPageState();
}

class _StepCounterPageState extends State<StepCounterPage> with WidgetsBindingObserver {
  final _stepCounter = AccurateStepCounter();
  int _steps = 0;

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

  Future<void> _init() async {
    // Request permissions
    await Permission.activityRecognition.request();
    await Permission.notification.request();

    // Initialize logging for persistent storage
    await _stepCounter.initializeLogging(debugLogging: kDebugMode);

    // Start counting with terminated state sync enabled
    await _stepCounter.start(
      config: StepDetectorConfig(enableOsLevelSync: true),
    );

    // Start auto-logging to database
    await _stepCounter.startLogging(
      config: StepRecordConfig.walking(),
    );

    // Listen to real-time step count
    _stepCounter.stepEventStream.listen((event) {
      setState(() => _steps = event.stepCount);
    });

    // Handle steps from terminated state
    _stepCounter.onTerminatedStepsDetected = (steps, start, end) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('Recovered $steps steps from terminated state!')),
      );
    };
  }

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

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Step Counter - All States')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            // Real-time step count
            Text(
              '$_steps',
              style: const TextStyle(fontSize: 72, fontWeight: FontWeight.bold),
            ),
            const Text('steps', style: TextStyle(fontSize: 24, color: Colors.grey)),
            const SizedBox(height: 40),

            // Works in foreground
            const ListTile(
              leading: Icon(Icons.phone_android, color: Colors.green),
              title: Text('Foreground'),
              subtitle: Text('Counts while app is open'),
            ),

            // Works in background
            const ListTile(
              leading: Icon(Icons.layers, color: Colors.orange),
              title: Text('Background'),
              subtitle: Text('Counts when app is minimized'),
            ),

            // Works in terminated state
            const ListTile(
              leading: Icon(Icons.power_off, color: Colors.red),
              title: Text('Terminated'),
              subtitle: Text('Recovers steps when app is killed & reopened'),
            ),
          ],
        ),
      ),
    );
  }
}

That's it! This simple example:

  • βœ… Real-time counting - Updates UI instantly
  • βœ… Foreground state - Counts while app is open
  • βœ… Background state - Continues counting when minimized
  • βœ… Terminated state - Recovers missed steps when app is killed & reopened
  • βœ… Auto-logging - Saves all steps to database with source tracking
  • βœ… Lifecycle tracking - setAppState() ensures proper source detection

How It Works in Each State #

State What Happens
🟒 Foreground Real-time updates, steps logged as foreground
🟑 Background Continues counting (service on Android ≀10), steps logged as background
πŸ”΄ Terminated OS tracks steps, synced on relaunch (Android 11+), logged as terminated

Try it: Open app β†’ Walk 50 steps β†’ Press home β†’ Walk 50 more β†’ Force kill β†’ Walk 50 more β†’ Reopen app β†’ See all steps recovered! πŸŽ‰

βš™οΈ 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 #

// Convenient date-based queries
final todaySteps = await stepCounter.getTodaySteps();
final yesterdaySteps = await stepCounter.getYesterdaySteps();
final last2Days = await stepCounter.getTodayAndYesterdaySteps();

// Custom date range
final weekSteps = await stepCounter.getStepsInRange(
  DateTime.now().subtract(Duration(days: 7)),
  DateTime.now(),
);

// Specific date
final jan15Steps = await stepCounter.getStepsInRange(
  DateTime(2025, 1, 15),
  DateTime(2025, 1, 15),
);

// All-time total (or with custom dates)
final total = await stepCounter.getTotalSteps();
final customRange = await stepCounter.getTotalSteps(
  from: DateTime(2025, 1, 1),
  to: DateTime.now(),
);

// 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 System Flow #

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                            Flutter App Layer                             β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  AccurateStepCounter (Main API)                                         β”‚
β”‚    β”œβ”€β”€ stepEventStream            β†’ Real-time step events               β”‚
β”‚    β”œβ”€β”€ currentStepCount            β†’ Current session steps              β”‚
β”‚    β”œβ”€β”€ onTerminatedStepsDetected  β†’ Missed steps callback              β”‚
β”‚    β”œβ”€β”€ setAppState()               β†’ Track foreground/background        β”‚
β”‚    └── Database Logging API                                             β”‚
β”‚         β”œβ”€β”€ initializeLogging()    β†’ Setup Hive database                β”‚
β”‚         β”œβ”€β”€ startLogging()         β†’ Auto-log with warmup validation    β”‚
β”‚         β”œβ”€β”€ getTotalSteps()        β†’ Query aggregate                    β”‚
β”‚         β”œβ”€β”€ getStepsBySource()     β†’ Query by source type               β”‚
β”‚         └── watchTotalSteps()      β†’ Real-time database stream          β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  Hive Database (Local Storage)                                          β”‚
β”‚    β”œβ”€β”€ StepRecord (Model)         β†’ {stepCount, fromTime, toTime}       β”‚
β”‚    β”œβ”€β”€ StepRecordSource           β†’ foreground | background | terminatedβ”‚
β”‚    └── StepRecordStore (Service)  β†’ CRUD operations + streams           β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  NativeStepDetector (Dart Side)                                         β”‚
β”‚    β”œβ”€β”€ MethodChannel              β†’ Commands (start, stop, reset)       β”‚
β”‚    └── EventChannel               β†’ Step events from native             β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                          β”‚
                   Platform Channel (MethodChannel + EventChannel)
                          β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                      Android Native Layer (Kotlin)                       β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  AccurateStepCounterPlugin                                              β”‚
β”‚    β”œβ”€β”€ NativeStepDetector.kt      β†’ Sensor management + event streaming β”‚
β”‚    β”œβ”€β”€ StepCounterForegroundService.kt β†’ Background service (API ≀29)   β”‚
β”‚    └── SharedPreferences          β†’ State persistence (OS-level sync)   β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  Android Sensor Framework                                               β”‚
β”‚    β”œβ”€β”€ TYPE_STEP_DETECTOR         β†’ Hardware step sensor (preferred)    β”‚
β”‚    β”œβ”€β”€ TYPE_STEP_COUNTER          β†’ OS-level counter (for sync)         β”‚
β”‚    └── TYPE_ACCELEROMETER         β†’ Fallback (software algorithm)       β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

App State Handling Architecture #

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                         App Lifecycle States                            β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

🟒 FOREGROUND (AppLifecycleState.resumed)
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  App Active & Visible                       β”‚
β”‚  β”œβ”€β”€ NativeStepDetector active             β”‚
β”‚  β”œβ”€β”€ Real-time UI updates                  β”‚
β”‚  β”œβ”€β”€ Steps logged as: foreground           β”‚
β”‚  └── Best accuracy & responsiveness        β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                    ↓ Press Home / Switch App

🟑 BACKGROUND (AppLifecycleState.paused)
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  App Minimized but Running                  β”‚
β”‚                                             β”‚
β”‚  Android 11+ (API 30+)                     β”‚
β”‚  β”œβ”€β”€ Native sensor continues automatically β”‚
β”‚  β”œβ”€β”€ No notification needed                 β”‚
β”‚  └── Steps logged as: background           β”‚
β”‚                                             β”‚
β”‚  Android ≀10 (API ≀29)                     β”‚
β”‚  β”œβ”€β”€ Foreground Service activated          β”‚
β”‚  β”œβ”€β”€ Persistent notification shown          β”‚
β”‚  β”œβ”€β”€ WakeLock keeps CPU active             β”‚
β”‚  └── Steps logged as: background           β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                    ↓ Force Stop / OS Kills App

πŸ”΄ TERMINATED (App Killed)
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  App Completely Stopped                     β”‚
β”‚                                             β”‚
β”‚  Android 11+ (API 30+)                     β”‚
β”‚  β”œβ”€β”€ OS continues via TYPE_STEP_COUNTER    β”‚
β”‚  β”œβ”€β”€ Steps tracked by Android system       β”‚
β”‚  β”œβ”€β”€ On relaunch: sync missed steps        β”‚
β”‚  └── Steps logged as: terminated           β”‚
β”‚                                             β”‚
β”‚  Android ≀10 (API ≀29)                     β”‚
β”‚  β”œβ”€β”€ Foreground Service prevents death     β”‚
β”‚  β”œβ”€β”€ Service survives Activity destruction β”‚
β”‚  └── No true terminated state               β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                    ↓ User Reopens App

🟒 FOREGROUND (Back to resumed)
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  App Relaunched                             β”‚
β”‚  β”œβ”€β”€ onTerminatedStepsDetected fires       β”‚
β”‚  β”œβ”€β”€ Missed steps synced to database       β”‚
β”‚  └── Resume normal counting                 β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Sensor Selection & Fallback Strategy #

                    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                    β”‚   App Starts        β”‚
                    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                               β”‚
                    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                    β”‚ Check Android API   β”‚
                    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                               β”‚
              β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
              β”‚                                 β”‚
     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”              β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”
     β”‚ API ≀ 29        β”‚              β”‚ API β‰₯ 30        β”‚
     β”‚ (Android ≀10)   β”‚              β”‚ (Android 11+)   β”‚
     β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜              β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜
              β”‚                                 β”‚
   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”          β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
   β”‚ Foreground Service  β”‚          β”‚ Native Detection   β”‚
   β”‚ - Persistent notify β”‚          β”‚ + OS-level sync    β”‚
   β”‚ - WakeLock active   β”‚          β”‚ - No notification  β”‚
   β”‚ - Keeps app alive   β”‚          β”‚ - Better battery   β”‚
   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜          β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
              β”‚                                 β”‚
              β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                               β”‚
                    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                    β”‚ Check TYPE_STEP_DETECTORβ”‚
                    β”‚    (Hardware Sensor)    β”‚
                    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                               β”‚
              β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
              β”‚                                 β”‚
     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”              β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”
     β”‚  βœ… Available   β”‚              β”‚  ❌ Not Found   β”‚
     β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜              β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜
              β”‚                                 β”‚
   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”          β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
   β”‚ Hardware Detection  β”‚          β”‚ Accelerometer      β”‚
   β”‚ β”œβ”€ Event-driven     β”‚          β”‚ + Software Algo    β”‚
   β”‚ β”œβ”€ Best accuracy    β”‚          β”‚ β”œβ”€ Low-pass filter β”‚
   β”‚ β”œβ”€ Battery efficientβ”‚          β”‚ β”œβ”€ Peak detection  β”‚
   β”‚ └─ Android optimizedβ”‚          β”‚ └─ Configurable    β”‚
   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜          β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Data Flow: Step Detection β†’ Database #

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  User Walks      β”‚  πŸ‘£
β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
         β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Android Sensor (TYPE_STEP_DETECTOR or ACCELEROMETER)      β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
         β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  NativeDetector  β”‚  (Kotlin)
β”‚  - Filters noise β”‚
β”‚  - Emits events  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
         β”‚ EventChannel
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  AccurateStep    β”‚  (Dart)
β”‚  Counter         β”‚
β”‚  - stepCount++   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
         β”‚
    β”Œβ”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”
    β”‚ Warmup? β”‚
    β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”˜
         β”‚
    β”Œβ”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚ YES: Buffer steps                β”‚
    β”‚  β”œβ”€ Wait for warmup duration    β”‚
    β”‚  β”œβ”€ Validate step count          β”‚
    β”‚  β”œβ”€ Validate step rate           β”‚
    β”‚  └─ Log if validated             β”‚
    β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
         β”‚
    β”Œβ”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚ NO: Normal logging               β”‚
    β”‚  β”œβ”€ Check interval elapsed       β”‚
    β”‚  β”œβ”€ Validate step rate           β”‚
    β”‚  └─ Log to database              β”‚
    β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
         β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Determine       β”‚
β”‚  Source Type     β”‚
β”‚  β”œβ”€ Foreground   β”‚
β”‚  β”œβ”€ Background   β”‚
β”‚  └─ Terminated   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
         β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Hive Database   β”‚  πŸ’Ύ
β”‚  StepRecord      β”‚
β”‚  - stepCount     β”‚
β”‚  - fromTime      β”‚
β”‚  - toTime        β”‚
β”‚  - source        β”‚
β”‚  - confidence    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Terminated State Sync Flow (Android 11+) #

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  App Running    β”‚
β”‚  Walk 100 steps β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜
         β”‚
         β”‚  Save state to SharedPreferences:
         β”‚  - lastStepCount: 1000 (OS counter)
         β”‚  - timestamp: 10:00 AM
         β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  App Killed     β”‚  ❌ (Force stop or OS kills it)
β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜
         β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  User Walks     β”‚  πŸ‘£ Walk 50 more steps
β”‚  (OS counting)  β”‚  OS step counter: 1000 β†’ 1050
β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜
         β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  App Relaunched β”‚  πŸ”„
β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜
         β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Sync Process (automatic)                β”‚
β”‚  1. Read current OS count: 1050          β”‚
β”‚  2. Read saved count: 1000               β”‚
β”‚  3. Calculate missed: 1050 - 1000 = 50   β”‚
β”‚  4. Validate:                            β”‚
β”‚     βœ“ Positive number                    β”‚
β”‚     βœ“ < 50,000 (max reasonable)          β”‚
β”‚     βœ“ Step rate < 3 steps/sec            β”‚
β”‚     βœ“ Time not negative                  β”‚
β”‚  5. onTerminatedStepsDetected(50, ...)   β”‚
β”‚  6. Log to database as: terminated       β”‚
β”‚  7. Save new baseline: 1050              β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

πŸ“± 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

πŸ§ͺ Testing & Verification #

Quick Setup Verification #

Run this code to verify everything is configured correctly:

Future<void> verifySetup() async {
  final stepCounter = AccurateStepCounter();

  // 1. Check permission
  final hasPermission = await stepCounter.hasActivityRecognitionPermission();
  print('βœ“ Permission: $hasPermission');

  // 2. Initialize logging
  await stepCounter.initializeLogging(debugLogging: true);
  print('βœ“ Logging initialized: ${stepCounter.isLoggingInitialized}');

  // 3. Start counter
  await stepCounter.start();
  print('βœ“ Started: ${stepCounter.isStarted}');

  // 4. Check detector
  final isHardware = await stepCounter.isUsingNativeDetector();
  print('βœ“ Hardware detector: $isHardware');

  // 5. Enable logging
  await stepCounter.startLogging(config: StepRecordConfig.walking());
  print('βœ“ Logging enabled: ${stepCounter.isLoggingEnabled}');
}

Real-Life Test Scenarios #

The package includes 7 comprehensive test scenarios covering all app states:

  1. Morning Walk - Foreground state counting
  2. Background Mode - Shopping while app is backgrounded
  3. Terminated State Recovery - App killed and relaunched
  4. All-Day Tracking - Mixed states throughout the day
  5. Running Workout - High-intensity activity
  6. Device Reboot - Handling sensor resets
  7. Permission Handling - Edge cases and failures

See TESTING_SCENARIOS.md for detailed testing instructions.

Automated Test Runner #

Use the included test script for easy testing:

chmod +x test_runner.sh
./test_runner.sh

The script will:

  • βœ… Check device connection
  • βœ… Build and install the example app
  • βœ… Grant required permissions
  • βœ… Run scenario tests
  • βœ… Monitor logs in real-time

Manual Testing Checklist #

[ ] Foreground counting (100 steps, Β±5% accuracy)
[ ] Background counting (proper source tracking)
[ ] Terminated state sync (missed steps recovered)
[ ] Warmup validation (prevents false positives)
[ ] Real-time stream updates
[ ] Database logging persists
[ ] Notification shows on Android ≀10
[ ] No crashes or errors

πŸ“‹ Quick Reference #

Essential API Calls #

// Basic Setup
final stepCounter = AccurateStepCounter();
await stepCounter.initializeLogging(debugLogging: kDebugMode);
await stepCounter.start(config: StepDetectorConfig(enableOsLevelSync: true));
await stepCounter.startLogging(config: StepRecordConfig.walking());

// App Lifecycle (CRITICAL for proper source tracking)
void didChangeAppLifecycleState(AppLifecycleState state) {
  stepCounter.setAppState(state);
}

// Real-time Step Count
stepCounter.stepEventStream.listen((event) {
  print('Steps: ${event.stepCount}');
});

// Terminated State Callback
stepCounter.onTerminatedStepsDetected = (steps, start, end) {
  print('Recovered $steps steps from $start to $end');
};

// Query Database - Convenient date methods
final todaySteps = await stepCounter.getTodaySteps();
final yesterdaySteps = await stepCounter.getYesterdaySteps();
final last2Days = await stepCounter.getTodayAndYesterdaySteps();
final weekSteps = await stepCounter.getStepsInRange(
  DateTime.now().subtract(Duration(days: 7)),
  DateTime.now(),
);

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

// Real-time Database Stream
stepCounter.watchTotalSteps().listen((total) {
  print('Total: $total');
});

// Cleanup
await stepCounter.stop();
await stepCounter.dispose();

Configuration Presets Quick Pick #

Activity Detector Config Logging Config
Casual Walking StepDetectorConfig.walking() StepRecordConfig.walking()
Running/Jogging StepDetectorConfig.running() StepRecordConfig.running()
High Sensitivity StepDetectorConfig.sensitive() StepRecordConfig.sensitive()
Strict Accuracy StepDetectorConfig.conservative() StepRecordConfig.conservative()
Raw Data Default StepRecordConfig.noValidation()

Platform Behavior Matrix #

Feature Android 11+ Android ≀10
Foreground Counting βœ… Native detector βœ… Native detector
Background Counting βœ… Automatic βœ… Foreground service
Notification ❌ None βœ… Required
Terminated Recovery βœ… OS-level sync ⚠️ Service prevents termination
Battery Impact 🟒 Low 🟑 Medium
Setup Required Minimal Notification permission

Common Patterns #

Pattern 1: Basic Real-Time Counter

final counter = AccurateStepCounter();
await counter.start();
counter.stepEventStream.listen((e) => print(e.stepCount));

Pattern 2: Persistent All-Day Tracking

final counter = AccurateStepCounter();
await counter.initializeLogging(debugLogging: kDebugMode);
await counter.start(config: StepDetectorConfig(enableOsLevelSync: true));
await counter.startLogging(config: StepRecordConfig.walking());

// Track app state in didChangeAppLifecycleState
counter.setAppState(state);

// Query anytime
final total = await counter.getTotalSteps();

Pattern 3: Activity Tracking with Source Breakdown

final counter = AccurateStepCounter();
await counter.initializeLogging(debugLogging: true);
await counter.start(config: StepDetectorConfig(enableOsLevelSync: true));
await counter.startLogging(config: StepRecordConfig.walking());

// Get breakdown
final stats = await counter.getStepStats();
print('Foreground: ${stats['foregroundSteps']}');
print('Background: ${stats['backgroundSteps']}');
print('Terminated: ${stats['terminatedSteps']}');

Troubleshooting Quick Fixes #

Problem Solution
No steps counted Check ACTIVITY_RECOGNITION permission granted
Stops in background (Android ≀10) Notification permission granted? Check battery optimization
No terminated sync Set enableOsLevelSync: true in config
Database empty Called initializeLogging() and startLogging()?
No real-time updates Subscribed to stepEventStream?
Wrong source tracking Implemented didChangeAppLifecycleState with setAppState()?

Debug Commands #

# View all logs
adb logcat -s AccurateStepCounter NativeStepDetector StepSync

# Clear logs and watch
adb logcat -c && adb logcat -s AccurateStepCounter

# Check sensor availability
adb shell dumpsys sensorservice | grep -i step

πŸ“„ 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