Timer class and Timer.periodic
While building mobile applications, we often come across scenarios when we must perform a task after a certain duration. Remember seeing a shiny splash screen before onboarding in an app? Or, maybe a relaxing screen after ordering a favorite dish on a food app?
Or we might need a block of code to execute repeatedly after a certain duration, like showing a remaining time limit to fill a one-time password or changing a color of a widget every second to create a beautiful animation.
To address these needs in Flutter applications, we have the Timer class. So in this article, we will cover the following points to best understand how to implement these functionalities into your own Flutter applications:
Timer class and how we can use ittimer.periodicTimer classAnd with that, let’s get started!
The Replay is a weekly newsletter for dev and engineering leaders.
Delivered once a week, it's your curated guide to the most important conversations around frontend dev, emerging AI tools, and the state of modern software.
Timer class?Flutter’s Timer class allows us to create a countdown timer. It passes through the below states respectively in its lifetime:
To use the Timer class, which is a part of the Dart async library, we can import it with the below import statement:
import 'dart:async';
Now, to create a simple 3-second timer, add the following, which triggers a callback after it executes:
final timer = Timer(
const Duration(seconds: 3),
() {
// Navigate to your favorite place
},
);
Once the callback triggers, we can navigate a user to a new screen, for example. Note, however, that the callback triggers only once.
Using Timer.periodic, we can create a repeating timer that executes after a given duration. Periodic timers stay alive until they are manually canceled. Flutter has a different factory method, named periodic, to create such a timer.
A good example of a periodic timer is displaying the remaining time for a time-sensitive operation, like completing a payment within 10 minutes. In the following example, the code produces a timer that triggers a callback every second:
final periodicTimer = Timer.periodic(
const Duration(seconds: 1),
(timer) {
// Update user about remaining time
},
);
Note that the periodic timer stays active infinitely by default.
Simple, isn’t it? Yes, but there are other questions we must still answer when using this in a practical use case:
Timer classThe Timer class gives us a lot of other options to easily work with it. Let’s dig into understanding how to use these other options and how they work with normal and periodic timers.
The Timer class has a cancel() method that cancels any active timer. For normal timers, calling cancel does not invoke a callback. For periodic timers, the cancel method becomes very important because we must finish the timer:
final periodicTimer = Timer.periodic(
const Duration(seconds: 1),
(timer) {
// Update user about remaining time
},
);
final shouldStop = true; //No more tick-tock now! Please
if (shouldStop) {
timer.cancel();
}
Note that we can call cancel as many times as we want without any side effects; the further calls will simply be ignored.
Normal timers are called active if the callback does not trigger and we haven’t canceled it explicitly.
On the other hand, periodic timers are always active if we haven’t specifically canceled them:
final timer = Timer(
const Duration(seconds: 3),
() {
// Navigate to your favorite place
},
);
if (timer.isActive) {
//Oh no!, it's taking longer than expected
}
Creating a periodic timer with a 1-second duration will tick 60 times in a minute. While we know this is inherently true, how can we know the counts for certain?
This is where tick comes in. A tick value starts at zero and increments each time a timer event occurs; this value is a count that reflects the number of durations that pass.
For example, a periodic timer with a duration of 10 seconds will have six events in a minute, and a tick will give the value with respect to the current point in time. That is, after half a minute, the tick value will be 3 and continue incrementing on every event:
final periodicTimer = Timer.periodic(
const Duration(seconds: 10),
(timer) {
// Update user about remaining time
},
);
final howMuchTimeBuddy = periodicTimer.tick;
In the above code we created a periodic timer with a duration of 10 seconds. We can get the tick value using periodicTimer.tick for any given point in time.
There is one more interesting use case of the Timer class. With Timer, we can schedule a callback that executes as soon as possible in an asynchronous mode. To do this, just start a timer with zero duration:
final zeroDurationTimer = Timer(
Duration.zero,
() {
//Execute this callback ASAP but asynchronously
},
);
There is also a handy method that is equivalent to the above code, but cleaner:
final zeroDurationTimer = Timer.run(() {
//Execute this callback ASAP but asynchronously
});
As we saw above, we can cancel Timer by calling the cancel() method. However, there is no direct way within the Timer class to restart the same timer unless we recreate the timer instance.
To achieve this, Flutter has the RestartableTimer. The underlying implementation of this restartable timer is the same as creating a timer instance. Since Timer is an abstract class, RestartableTimer is one of its concrete implementations.
RestartableTimer is a part of the async package that contains utility classes for dart:async. It is already a part of the Flutter SDK and can be imported with the following:
import 'package:async/async.dart';
With it imported, we can create a simple 3 second RestartableTimer:
final restartableTimer = RestartableTimer(
const Duration(seconds: 3),
() {
//Callback
},
);
//Restart the timer
restartableTimer.reset();
It is important to note here that resetting a timer recreates a timer from its original duration. RestartableTimer is only for the nonperiodic timer.
Timer examples and use casesWhen developing apps, we must often create a screen that stays active for a duration and then continue with the app’s flow. This could be a splash screen, a “Your order is placed” screen, or any other transitional element.
In this example, we have the first screen, RelaxingScreen, which will be visible to user for 3 seconds and navigate to NextScreen when the time completes.
When using Timer, it’s important to cancel on dispose. This guarantees that no timer stays alive when the respective widget is removed from the tree.
Take a look at the below code to achieve the same using the Timer class:
import 'dart:async';
import 'package:flutter/material.dart';
const Color darkBlue = Color.fromARGB(255, 18, 32, 47);
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(
scaffoldBackgroundColor: darkBlue,
),
debugShowCheckedModeBanner: false,
home: const Scaffold(
body: Center(
child: RelaxingScreen(),
),
),
);
}
}
/// Releaxing screen that stays visible for 3 seconds
class RelaxingScreen extends StatefulWidget {
const RelaxingScreen({Key? key}) : super(key: key);
@override
_RelaxingScreenState createState() => _RelaxingScreenState();
}
class _RelaxingScreenState extends State<RelaxingScreen> {
//Declare a timer
Timer? timer;
@override
void initState() {
super.initState();
/// Initialize timer for 3 seconds, it will be active as soon as intialized
timer = Timer(
const Duration(seconds: 3),
() {
/// Navigate to seconds screen when timer callback in executed
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const NextScreen(),
),
);
},
);
}
/// cancel the timer when widget is disposed,
/// to avoid any active timer that is not executed yet
@override
void dispose() {
super.dispose();
timer?.cancel();
}
@override
Widget build(BuildContext context) {
return const Text("Relaxing Screen!!");
}
}
class NextScreen extends StatelessWidget {
const NextScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return const Scaffold(
body: Center(
child: Text("Next Screen"),
),
);
}
}
In the above example, we first have RelaxingScreen visible to the user, which is a stateful widget, and then we must register Timer on initState().
After a 3 second timer, a callback triggers to navigate the user to NextScreen. It is important to cancel the timer on the dispose() method to avoid any anomalies that may be created if a user leaves the RelaxingScreen before the callback triggers.
As a Flutter dev, you’re most likely familiar with the very famous counter app 😅 . In this example, we will create a similar counter app, but instead of pressing the + button, we will auto-increment the counter every 1 second and show it to the user by rebuilding a Text widget:
import 'dart:async';
import 'package:flutter/material.dart';
const Color darkBlue = Color.fromARGB(255, 18, 32, 47);
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(
scaffoldBackgroundColor: darkBlue,
),
debugShowCheckedModeBanner: false,
home: const Scaffold(
body: Center(
child: CounterScreen(),
),
),
);
}
}
class CounterScreen extends StatefulWidget {
const CounterScreen({Key? key}) : super(key: key);
@override
_CounterScreenState createState() => _CounterScreenState();
}
class _CounterScreenState extends State<CounterScreen> {
/// declare a cound variable with initial value
int count = 0;
/// declare a timer
Timer? timer;
@override
void initState() {
super.initState();
/// Initialize a periodic timer with 1 second duration
timer = Timer.periodic(
const Duration(seconds: 1),
(timer) {
/// callback will be executed every 1 second, increament a count value
/// on each callback
setState(() {
count++;
});
},
);
}
/// Since periodic timer doesn't cancels untill expicitely called
/// It is important to cancel them on dispose, so that it doesn't stays active
/// when widget is not binded to tree
@override
void dispose() {
super.dispose();
timer?.cancel();
}
@override
Widget build(BuildContext context) {
return Text("Counter reached $count");
}
}
In the above example, there is a StatefulWidget, CounterScreen, where we registered a periodic timer that ticks every 1 second. On every callback triggered, we increment the state variable count. A Text widget then displays the latest value of count.
A similar approach can also be taken to show the countdown timer or the remaining duration (for instance, a one-time password timeout).
Timer classWhen we think of a general timer, it is common to expect utilities like pausing or resuming a timer. As we’ve seen so far, Flutter’s Timer class is intended to schedule a block of code for later or execute it repetitively for certain durations.
To implement utilities like pausing and resuming timers in Flutter, you can use the Stopwatch class.
Flutter’s Timer class handles every use case related to the countdown timer. With it, we can create a normal and periodic timer with full utilities like canceling the timer, identifying whether the timer is active or not, and ticking counts.
We also saw how using the RestartableTimer can reset and start timers again.
Thanks for reading and don’t forget to share what beautiful UX you are creating with the help of Timer 🤩.
Install LogRocket via npm or script tag. LogRocket.init() must be called client-side, not
server-side
$ npm i --save logrocket
// Code:
import LogRocket from 'logrocket';
LogRocket.init('app/id');
// Add to your HTML:
<script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script>
<script>window.LogRocket && window.LogRocket.init('app/id');</script>

Promise.all still relevant in 2025?In 2025, async JavaScript looks very different. With tools like Promise.any, Promise.allSettled, and Array.fromAsync, many developers wonder if Promise.all is still worth it. The short answer is yes — but only if you know when and why to use it.

Discover what’s new in The Replay, LogRocket’s newsletter for dev and engineering leaders, in the October 29th issue.

Learn about the new features in the Next.js 16 release: why they matter, how they impact your workflow, and how to start using them.

Test out Meta’s AI model, Llama, on a real CRUD frontend projects, compare it with competing models, and walk through the setup process.
Hey there, want to help make our blog better?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up now
One Reply to "Understanding Flutter’s <code>Timer</code> class and <code>Timer.periodic</code>"
Great doc. The only thing I’d add is an embellishment to the Timer.periodic. Basically it’s quite common to “keep waiting until a condition is true”, which you can do by using myTimer = Timer.periodic(…{checkCondition()}) and having checkCondition, if the condition is true, cancelling myTimer.