How to Track Daily Steps (Implementing a Pedometer) in Flutter
Recently, I was trying to add a daily steps tracker in one of the apps I was working on.
A quick google search led me to the Flutter pedometer
package. However, as I tested the plugin, I learned that the plugin only returns cumulative steps since the last device reboot- the author is pretty insistent on keeping the plugin "lean". Β It wasn't straightforward to get (and store) a user's daily steps- I needed to do some simple math to get it working.
Here's how I did it.
In a hurry? Find the code on Github here.
1. Add pedometer and a plugin for persistent storage to your pubspec.yaml.
I'm going to use Hive, but you can also use Shared Prefs. The original pedometer plugin has not yet been updated to work with AndroidX, so I'm using Filip's fork.
For iOS, you also need to carry out the setup specified in the README.
2. Initialize Pedometer.
The pedometer plugin provides us with a stream, where we can get the number of steps since the phone was booted. Let's instantiate it in our main widget, DailyStepsPage
. We also need to dispose the subscription once we're done with it.
import 'dart:async';
import 'package:pedometer/pedometer.dart';
class DailyStepsPage extends StatefulWidget {
@override
_DailyStepsPageState createState() => _DailyStepsPageState();
}
class _DailyStepsPageState extends State<DailyStepsPage> {
Pedometer _pedometer;
StreamSubscription<int> _subscription;
@override
void initState() {
super.initState();
startListening();
}
void startListening() {
_pedometer = Pedometer();
_subscription = _pedometer.pedometerStream.listen(
getTodaySteps,
onError: _onError,
onDone: _onDone,
cancelOnError: true,
);
}
void _onDone() => print("Finished pedometer tracking");
void _onError(error) => print("Flutter Pedometer Error: $error");
Future<int> getTodaySteps(int value) async {
// This is where we'll write our logic
}
@override
void dispose() {
stopListening();
super.dispose();
}
void stopListening() {
_subscription.cancel();
}
3. Write the logic for calculating/getting the daily steps based off the data from the plugin.
You'll need the following vars:
savedStepCount
to save previous days' step count. If you already save and display/use/show previous days' step count, you cannot use that var for this purpose, as this savedStepCount will reset intermittentlylastDaySaved
, either an int or a DateTime to save the last time you "reset" your pedometer. this will become apparent shortly. persist this as well.value
is the count received from the pedometer streamtodayStepCount
- the counts for the current day.
Here's the code:
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:hive/hive.dart';
import 'package:jiffy/jiffy.dart';
import 'package:pedometer/pedometer.dart';
class _DailyStepsPageState extends State<DailyStepsPage> {
Pedometer _pedometer;
StreamSubscription<int> _subscription;
Box<int> stepsBox = Hive.box('steps');
int todaySteps;
...
Future<int> getTodaySteps(int value) async {
print(value);
int savedStepsCountKey = 999999;
int savedStepsCount = stepsBox.get(savedStepsCountKey, defaultValue: 0);
int todayDayNo = Jiffy(DateTime.now()).dayOfYear;
if (value < savedStepsCount) {
// Upon device reboot, pedometer resets. When this happens, the saved counter must be reset as well.
savedStepsCount = 0;
// persist this value using a package of your choice here
stepsBox.put(savedStepsCountKey, savedStepsCount);
}
// load the last day saved using a package of your choice here
int lastDaySavedKey = 888888;
int lastDaySaved = stepsBox.get(lastDaySavedKey, defaultValue: 0);
// When the day changes, reset the daily steps count
// and Update the last day saved as the day changes.
if (lastDaySaved < todayDayNo) {
lastDaySaved = todayDayNo;
savedStepsCount = value;
stepsBox
..put(lastDaySavedKey, lastDaySaved)
..put(savedStepsCountKey, savedStepsCount);
}
setState(() {
todaySteps = value - savedStepsCount;
});
stepsBox.put(todayDayNo, todaySteps);
return todaySteps; // this is your daily steps value.
}
Okay, now let's write the UI, initialize Hive in our main()
function, and wire it all together!
import 'package:flutter/material.dart';
import 'package:hive/hive.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:google_fonts/google_fonts.dart'; // Add google_fonts to your dependencies first
import 'package:daily_steps/daily_steps_page.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Hive.initFlutter();
await Hive.openBox<int>('steps');
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Daily Steps',
home: DailyStepsPage(),
theme: ThemeData(
primarySwatch: Colors.deepOrange,
textTheme: GoogleFonts.darkerGrotesqueTextTheme(
Theme.of(context).textTheme,
),
visualDensity: VisualDensity.adaptivePlatformDensity,
),
darkTheme: ThemeData.dark().copyWith(
textTheme: GoogleFonts.darkerGrotesqueTextTheme(
Theme.of(context).textTheme,
),
),
);
}
}
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:hive/hive.dart';
import 'package:jiffy/jiffy.dart';
import 'package:pedometer/pedometer.dart';
import 'package:google_fonts/google_fonts.dart';
class DailyStepsPage extends StatefulWidget {
@override
_DailyStepsPageState createState() => _DailyStepsPageState();
}
class _DailyStepsPageState extends State<DailyStepsPage> {
Pedometer _pedometer;
StreamSubscription<int> _subscription;
Box<int> stepsBox = Hive.box('steps');
int todaySteps;
final Color carbonBlack = Color(0xff1a1a1a);
@override
void initState() {
super.initState();
startListening();
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: carbonBlack,
appBar: AppBar(
title: Text(
"Daily Steps Tracker",
style: GoogleFonts.darkerGrotesque(fontSize: 40),
),
centerTitle: true,
backgroundColor: Colors.transparent,
elevation: 0,
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Spacer(),
Card(
color: Colors.black87.withOpacity(0.7),
elevation: 3,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10)),
child: Container(
margin: const EdgeInsets.only(
top: 10,
bottom: 30,
right: 20,
left: 20,
),
child: Column(
children: <Widget>[
gradientShaderMask(
child: Text(
todaySteps?.toString() ?? '0',
style: GoogleFonts.darkerGrotesque(
fontSize: 80,
color: Colors.white,
fontWeight: FontWeight.w900,
),
),
),
Text(
"Steps Today",
style: TextStyle(
color: Colors.white,
fontSize: 28,
fontWeight: FontWeight.w700,
),
),
],
),
),
),
Spacer(),
],
),
),
);
}
@override
void dispose() {
stopListening();
super.dispose();
}
Widget gradientShaderMask({@required Widget child}) {
return ShaderMask(
shaderCallback: (bounds) => LinearGradient(
colors: [
Colors.orange,
Colors.deepOrange.shade900,
],
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
).createShader(Rect.fromLTWH(0, 0, bounds.width, bounds.height)),
child: child,
);
}
void startListening() {
_pedometer = Pedometer();
_subscription = _pedometer.pedometerStream.listen(
getTodaySteps,
onError: _onError,
onDone: _onDone,
cancelOnError: true,
);
}
void _onDone() => print("Finished pedometer tracking");
void _onError(error) => print("Flutter Pedometer Error: $error");
Future<int> getTodaySteps(int value) async {
print(value);
int savedStepsCountKey = 999999;
int savedStepsCount = stepsBox.get(savedStepsCountKey, defaultValue: 0);
int todayDayNo = Jiffy(DateTime.now()).dayOfYear;
if (value < savedStepsCount) {
// Upon device reboot, pedometer resets. When this happens, the saved counter must be reset as well.
savedStepsCount = 0;
// persist this value using a package of your choice here
stepsBox.put(savedStepsCountKey, savedStepsCount);
}
// load the last day saved using a package of your choice here
int lastDaySavedKey = 888888;
int lastDaySaved = stepsBox.get(lastDaySavedKey, defaultValue: 0);
// When the day changes, reset the daily steps count
// and Update the last day saved as the day changes.
if (lastDaySaved < todayDayNo) {
lastDaySaved = todayDayNo;
savedStepsCount = value;
stepsBox
..put(lastDaySavedKey, lastDaySaved)
..put(savedStepsCountKey, savedStepsCount);
}
setState(() {
todaySteps = value - savedStepsCount;
});
stepsBox.put(todayDayNo, todaySteps);
return todaySteps; // this is your daily steps value.
}
void stopListening() {
_subscription.cancel();
}
}
Here's the result:

You can find the full code on Github here.
And that's it! If this post helped you or if you have any questions, consider buying me a coffee, or come say hi. It helps me keep this site (and my morals) up and ad-free.