Milind Mevada Mobile software engineer.

Understanding Flutter’s Timer class and Timer.periodic

6 min read 1807

Understanding Flutter's 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:

  • What is the Timer class and how we can use it
  • How to create a periodic timer with timer.periodic
  • How to create a restartable timer
  • Examples of using the Timer class

And with that, let’s get started!

What is the Timer class?

Flutter’s Timer class allows us to create a countdown timer. It passes through the below states respectively in its lifetime:

  • Creates a timer
  • Executes a callback
  • The timer finishes

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';

Creating a simple timer

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.

Creating a simple periodic timer

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:

  • How to cancel an active timer
  • How to know whether the timer is still active or not
  • How to know the duration passed

How to use the Timer class

The 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.

How to cancel an active timer

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.

How to know whether a timer is still active or not

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
}

How to know how much time passes

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.

How to schedule a callback

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
});

Creating a restartable timer in Flutter

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.

Flutter Timer examples and use cases

Creating a screen for a specific duration before navigating away

When 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.

Automatically increment a counter app by 1 second

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).

The limitations of Flutter’s Timer class

When 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.

Wrapping up

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 🤩.

Get setup with LogRocket's modern error tracking in minutes:

  1. Visit https://logrocket.com/signup/ to get an app ID.
  2. Install LogRocket via NPM or script tag. LogRocket.init() must be called client-side, not server-side.
  3. $ 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>
  4. (Optional) Install plugins for deeper integrations with your stack:
    • Redux middleware
    • ngrx middleware
    • Vuex plugin
Get started now
Milind Mevada Mobile software engineer.

Leave a Reply