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.periodic
Timer
classAnd with that, let’s get started!
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>
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 nowCompare Prisma and Drizzle ORMs to learn their differences, strengths, and weaknesses for data access and migrations.
It’s easy for devs to default to JavaScript to fix every problem. Let’s use the RoLP to find simpler alternatives with HTML and CSS.
Learn how to manage memory leaks in Rust, avoid unsafe behavior, and use tools like weak references to ensure efficient programs.
Bypass anti-bot measures in Node.js with curl-impersonate. Learn how it mimics browsers to overcome bot detection for web scraping.
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.