Introduction:
In Flutter, a stopwatch timer can be implemented to measure the elapsed time. The stop_watch_timer
package provides functionalities to easily implement a stopwatch timer. This tutorial will guide you through implementing a stopwatch timer in Flutter using the stop_watch_timer
package.
Content:
Step 1. Add Dependencies:
Include the stop_watch_timer
package in your pubspec.yaml
file. Open the file and add the following dependency:
dependencies:
stop_watch_timer: ^3.0.1
Save the file and run flutter pub get
to fetch the package.
Step 2. Import Packages:
Import the required packages in your Dart file:
import 'package:flutter/material.dart';
import 'package:stop_watch_timer/stop_watch_timer.dart';
Step 3. Create a StopwatchTimer Instance:
Create a StopwatchTimer
instance to manage the stopwatch timer. You can customize the timer with options such as format, initial value, and event listeners.
final _stopWatchTimer = StopwatchTimer(
mode: StopWatchMode.countUp, // Stopwatch mode (count up)
onChange: (value) => print('onChange $value'),
onChangeRawSecond: (value) => print('onChangeRawSecond $value'),
onEnded: () => print('onEnded'),
);
Step 4. Start, Stop, and Reset the Timer:
Use the start
, stop
, and reset
methods to control the stopwatch timer.
_startTimer() {
_stopWatchTimer.onExecute.add(StopWatchExecute.start);
}
_stopTimer() {
_stopWatchTimer.onExecute.add(StopWatchExecute.stop);
}
_resetTimer() {
_stopWatchTimer.onExecute.add(StopWatchExecute.reset);
}
Step 5. Display the Timer in the UI:
Display the stopwatch timer in your app’s UI. You can use a Text
widget to show the elapsed time and buttons to control the timer
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Stopwatch Timer Example',
home: Scaffold(
appBar: AppBar(
title: Text('Stopwatch Timer Example'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
StreamBuilder<int>(
stream: _stopWatchTimer.rawTime,
initialData: _stopWatchTimer.rawTime.value,
builder: (context, snapshot) {
final value = snapshot.data!;
final displayTime = StopWatchTimer.getDisplayTime(value);
return Text(displayTime, style: TextStyle(fontSize: 48.0));
},
),
SizedBox(height: 20.0),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: _startTimer,
child: Text('Start'),
),
SizedBox(width: 20.0),
ElevatedButton(
onPressed: _stopTimer,
child: Text('Stop'),
),
SizedBox(width: 20.0),
ElevatedButton(
onPressed: _resetTimer,
child: Text('Reset'),
),
],
),
],
),
),
),
);
}
Sample Code:
import 'package:flutter/material.dart';
import 'package:stop_watch_timer/stop_watch_timer.dart';
class MainPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
centerTitle: true,
elevation: 2,
title: Text(
"Stop watch timer",
style: TextStyle(fontSize: 22, color: Colors.black, fontWeight: FontWeight.w500),
),
backgroundColor: Colors.blue[200],
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Text(
'Count Down Timer',
style: TextStyle(fontSize: 20, color: Colors.green[700], fontWeight: FontWeight.w500),
),
CountDownTimerPage(),
SizedBox(
height: 10,
),
Container(
height: 3,
color: Colors.amber,
),
Text(
'Count Up Timer',
style: TextStyle(fontSize: 20, color: Colors.green[700], fontWeight: FontWeight.w500),
),
CountUpTimerPage()
],
),
),
);
}
}
class CountDownTimerPage extends StatefulWidget {
static Future<void> navigatorPush(BuildContext context) async {
return Navigator.of(context).push<void>(
MaterialPageRoute(
builder: (_) => CountDownTimerPage(),
),
);
}
@override
_State createState() => _State();
}
class _State extends State<CountDownTimerPage> {
final _isHours = true;
final StopWatchTimer _stopWatchTimer = StopWatchTimer(
mode: StopWatchMode.countDown,
presetMillisecond: StopWatchTimer.getMilliSecFromSecond(3),
onChange: (value) => print('onChange $value'),
onChangeRawSecond: (value) => print('onChangeRawSecond $value'),
onChangeRawMinute: (value) => print('onChangeRawMinute $value'),
onStopped: () {
print('onStopped');
},
onEnded: () {
print('onEnded');
},
);
final _scrollController = ScrollController();
@override
void initState() {
super.initState();
_stopWatchTimer.rawTime.listen((value) => print('rawTime $value ${StopWatchTimer.getDisplayTime(value)}'));
_stopWatchTimer.minuteTime.listen((value) => print('minuteTime $value'));
_stopWatchTimer.secondTime.listen((value) => print('secondTime $value'));
_stopWatchTimer.records.listen((value) => print('records $value'));
_stopWatchTimer.fetchStopped.listen((value) => print('stopped from stream'));
_stopWatchTimer.fetchEnded.listen((value) => print('ended from stream'));
/// Can be set preset time. This case is "00:01.23".
// _stopWatchTimer.setPresetTime(mSec: 1234);
}
@override
void dispose() async {
super.dispose();
await _stopWatchTimer.dispose();
}
@override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
/// Display stop watch time
StreamBuilder<int>(
stream: _stopWatchTimer.rawTime,
initialData: _stopWatchTimer.rawTime.value,
builder: (context, snap) {
final value = snap.data!;
final displayTime = StopWatchTimer.getDisplayTime(value, hours: _isHours);
return Column(
children: <Widget>[
Text(
displayTime,
style: const TextStyle(fontSize: 30, fontFamily: 'Helvetica', fontWeight: FontWeight.bold),
),
// Padding(
// padding: const EdgeInsets.all(8),
// child: Text(
// value.toString(),
// style: const TextStyle(fontSize: 16, fontFamily: 'Helvetica', fontWeight: FontWeight.w400),
// ),
// ),
],
);
},
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
StreamBuilder<int>(
stream: _stopWatchTimer.minuteTime,
initialData: _stopWatchTimer.minuteTime.value,
builder: (context, snap) {
final value = snap.data;
print('Listen every minute. $value');
return Column(
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
const Padding(
padding: EdgeInsets.symmetric(horizontal: 4),
child: Text(
'minute',
style: TextStyle(
fontSize: 17,
fontFamily: 'Helvetica',
),
),
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),
child: Text(
value.toString(),
style: const TextStyle(fontSize: 30, fontFamily: 'Helvetica', fontWeight: FontWeight.bold),
),
),
],
),
],
);
},
),
/// Display every second.
StreamBuilder<int>(
stream: _stopWatchTimer.secondTime,
initialData: _stopWatchTimer.secondTime.value,
builder: (context, snap) {
final value = snap.data;
print('Listen every second. $value');
return Column(
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
const Text(
'second',
style: TextStyle(
fontSize: 17,
fontFamily: 'Helvetica',
),
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),
child: Text(
value.toString(),
style: const TextStyle(
fontSize: 30,
fontFamily: 'Helvetica',
fontWeight: FontWeight.bold,
),
),
),
],
),
],
);
},
),
],
),
/// Display every minute.
/// Lap time.
SizedBox(
height: 10,
child: StreamBuilder<List<StopWatchRecord>>(
stream: _stopWatchTimer.records,
initialData: _stopWatchTimer.records.value,
builder: (context, snap) {
final value = snap.data!;
if (value.isEmpty) {
return const SizedBox();
}
Future.delayed(const Duration(milliseconds: 100), () {
_scrollController.animateTo(_scrollController.position.maxScrollExtent,
duration: const Duration(milliseconds: 200), curve: Curves.easeOut);
});
print('Listen records. $value');
return ListView.builder(
controller: _scrollController,
scrollDirection: Axis.vertical,
itemBuilder: (BuildContext context, int index) {
final data = value[index];
return Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.all(8),
child: Text(
'${index + 1} ${data.displayTime}',
style: const TextStyle(fontSize: 17, fontFamily: 'Helvetica', fontWeight: FontWeight.bold),
),
),
const Divider(
height: 1,
)
],
);
},
itemCount: value.length,
);
},
),
),
/// Button
Padding(
padding: const EdgeInsets.only(bottom: 0),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Flexible(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),
child: FilledButton(
onPressed: _stopWatchTimer.onStartTimer,
child: const Text(
'Start',
style: TextStyle(color: Colors.white),
),
),
),
),
Flexible(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),
child: FilledButton(
onPressed: _stopWatchTimer.onStopTimer,
child: const Text(
'Stop',
style: TextStyle(color: Colors.white),
),
),
),
),
Flexible(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),
child: FilledButton(
onPressed: _stopWatchTimer.onResetTimer,
child: const Text(
'Reset',
style: TextStyle(color: Colors.white),
),
),
),
),
],
),
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Flexible(
child: Padding(
padding: const EdgeInsets.all(0).copyWith(right: 8),
child: FilledButton(
onPressed: _stopWatchTimer.onAddLap,
child: const Text(
'Lap',
style: TextStyle(color: Colors.white),
),
),
),
),
],
),
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Flexible(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),
child: FilledButton(
onPressed: () {
_stopWatchTimer.setPresetHoursTime(1);
},
child: const Text(
'Set Hours',
style: TextStyle(color: Colors.white),
),
),
),
),
Flexible(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),
child: FilledButton(
onPressed: () {
_stopWatchTimer.setPresetMinuteTime(59);
},
child: const Text(
'Set Minute',
style: TextStyle(color: Colors.white),
),
),
),
),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Flexible(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),
child: FilledButton(
onPressed: () {
_stopWatchTimer.setPresetSecondTime(10);
},
child: const Text(
'Set +Second',
style: TextStyle(color: Colors.white),
),
),
),
),
Flexible(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),
child: FilledButton(
onPressed: () {
_stopWatchTimer.setPresetSecondTime(-10);
},
child: const Text(
'Set -Second',
style: TextStyle(color: Colors.white),
),
),
),
),
],
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),
child: FilledButton(
onPressed: _stopWatchTimer.clearPresetTime,
child: const Text(
'Clear PresetTime',
style: TextStyle(color: Colors.white),
),
),
),
],
);
}
}
class CountUpTimerPage extends StatefulWidget {
static Future<void> navigatorPush(BuildContext context) async {
return Navigator.push<void>(
context,
MaterialPageRoute(
builder: (_) => CountUpTimerPage(),
),
);
}
@override
_Statee createState() => _Statee();
}
class _Statee extends State<CountUpTimerPage> {
final _isHours = true;
final StopWatchTimer _stopWatchTimer = StopWatchTimer(
mode: StopWatchMode.countUp,
onChange: (value) => print('onChange $value'),
onChangeRawSecond: (value) => print('onChangeRawSecond $value'),
onChangeRawMinute: (value) => print('onChangeRawMinute $value'),
onStopped: () {
print('onStop');
},
onEnded: () {
print('onEnded');
},
);
final _scrollController = ScrollController();
@override
void initState() {
super.initState();
_stopWatchTimer.rawTime.listen((value) => print('rawTime $value ${StopWatchTimer.getDisplayTime(value)}'));
_stopWatchTimer.minuteTime.listen((value) => print('minuteTime $value'));
_stopWatchTimer.secondTime.listen((value) => print('secondTime $value'));
_stopWatchTimer.records.listen((value) => print('records $value'));
_stopWatchTimer.fetchStopped.listen((value) => print('stopped from stream'));
_stopWatchTimer.fetchEnded.listen((value) => print('ended from stream'));
/// Can be set preset time. This case is "00:01.23".
// _stopWatchTimer.setPresetTime(mSec: 1234);
}
@override
void dispose() async {
super.dispose();
await _stopWatchTimer.dispose();
}
@override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
/// Display stop watch time
StreamBuilder<int>(
stream: _stopWatchTimer.rawTime,
initialData: _stopWatchTimer.rawTime.value,
builder: (context, snap) {
final value = snap.data!;
final displayTime = StopWatchTimer.getDisplayTime(value, hours: _isHours);
return Column(
children: <Widget>[
Text(
displayTime,
style: const TextStyle(fontSize: 30, fontFamily: 'Helvetica', fontWeight: FontWeight.bold),
),
// Text(
// value.toString(),
// style: const TextStyle(fontSize: 16, fontFamily: 'Helvetica', fontWeight: FontWeight.w400),
// ),
],
);
},
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
StreamBuilder<int>(
stream: _stopWatchTimer.minuteTime,
initialData: _stopWatchTimer.minuteTime.value,
builder: (context, snap) {
final value = snap.data;
print('Listen every minute. $value');
return Column(
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
const Padding(
padding: EdgeInsets.symmetric(horizontal: 4),
child: Text(
'minute',
style: TextStyle(
fontSize: 17,
fontFamily: 'Helvetica',
),
),
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),
child: Text(
value.toString(),
style: const TextStyle(fontSize: 30, fontFamily: 'Helvetica', fontWeight: FontWeight.bold),
),
),
],
),
],
);
},
),
/// Display every second.
StreamBuilder<int>(
stream: _stopWatchTimer.secondTime,
initialData: _stopWatchTimer.secondTime.value,
builder: (context, snap) {
final value = snap.data;
print('Listen every second. $value');
return Column(
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
const Padding(
padding: EdgeInsets.symmetric(horizontal: 4),
child: Text(
'second',
style: TextStyle(
fontSize: 17,
fontFamily: 'Helvetica',
),
),
),
Text(
value.toString(),
style: const TextStyle(
fontSize: 30,
fontFamily: 'Helvetica',
fontWeight: FontWeight.bold,
),
),
],
),
],
);
},
),
],
),
/// Display every minute.
/// Lap time.
SizedBox(
height: 20,
child: StreamBuilder<List<StopWatchRecord>>(
stream: _stopWatchTimer.records,
initialData: _stopWatchTimer.records.value,
builder: (context, snap) {
final value = snap.data!;
if (value.isEmpty) {
return const SizedBox.shrink();
}
Future.delayed(const Duration(milliseconds: 100), () {
_scrollController.animateTo(_scrollController.position.maxScrollExtent,
duration: const Duration(milliseconds: 200), curve: Curves.easeOut);
});
print('Listen records. $value');
return ListView.builder(
controller: _scrollController,
scrollDirection: Axis.vertical,
itemBuilder: (BuildContext context, int index) {
final data = value[index];
return Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.all(8),
child: Text(
'${index + 1} ${data.displayTime}',
style: const TextStyle(fontSize: 17, fontFamily: 'Helvetica', fontWeight: FontWeight.bold),
),
),
const Divider(
height: 1,
)
],
);
},
itemCount: value.length,
);
},
),
),
/// Button
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Flexible(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),
child: FilledButton(
onPressed: _stopWatchTimer.onStartTimer,
child: const Text(
'Start',
),
),
),
),
Flexible(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),
child: FilledButton(
onPressed: _stopWatchTimer.onStopTimer,
child: const Text(
'Stop',
),
),
),
),
Flexible(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),
child: FilledButton(
onPressed: _stopWatchTimer.onResetTimer,
child: const Text(
'Reset',
),
),
),
),
],
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Flexible(
child: Padding(
padding: const EdgeInsets.all(0).copyWith(right: 8),
child: FilledButton(
onPressed: _stopWatchTimer.onAddLap,
child: const Text(
'Lap',
),
),
),
),
],
),
),
Padding(
padding: const EdgeInsets.all(0),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Flexible(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),
child: FilledButton(
onPressed: () {
_stopWatchTimer.setPresetHoursTime(1);
},
child: const Text(
'Set Hours',
),
),
),
),
Flexible(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),
child: FilledButton(
onPressed: () {
_stopWatchTimer.setPresetMinuteTime(59);
},
child: const Text(
'Set Minute',
),
),
),
),
],
),
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Flexible(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),
child: FilledButton(
onPressed: () {
_stopWatchTimer.setPresetSecondTime(10);
},
child: const Text(
'Set +Second',
),
),
),
),
Flexible(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),
child: FilledButton(
onPressed: () {
_stopWatchTimer.setPresetSecondTime(-10);
},
child: const Text(
'Set -Second',
),
),
),
),
],
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),
child: FilledButton(
onPressed: _stopWatchTimer.clearPresetTime,
child: const Text(
'Clear PresetTime',
),
),
),
],
);
}
}
Output:
Conclusion:
By following these steps, you can easily implement a stopwatch timer in Flutter using the stop_watch_timer
package. Use this stopwatch timer to measure elapsed time in your Flutter apps.