Implementing a Countdown Timer in Flutter with Decimal Precision

Nov 20, 2025 · Programming · 9 views · 7.8

Keywords: Flutter | Timer | Countdown | Dart | UI Update

Abstract: This article explores methods to create a countdown timer in Flutter that displays time with one decimal precision. It covers using Timer.periodic, CountdownTimer from quiver.async, and the flutter_countdown_timer package, with code examples and best practices for handling button interactions and state updates.

Introduction

In Flutter development, creating a countdown timer that updates in real-time and displays values with decimal precision can be challenging. This article addresses a common scenario where a timer needs to count down from a given duration, rounded to the first decimal place, and update the text of a button child widget. We will explore multiple approaches, including built-in Dart timers and external packages.

Using Timer.periodic for Countdown

The Timer.periodic class from dart:async allows creating a timer that triggers at regular intervals. To implement a countdown timer, we can use it in combination with setState to update the UI. Here's a refined example based on the user's query:

import 'package:flutter/material.dart';
import 'dart:async';

class TimerButton extends StatefulWidget {
  final Duration timerDuration;

  TimerButton(this.timerDuration);

  @override
  _TimerButtonState createState() => _TimerButtonState();
}

class _TimerButtonState extends State<TimerButton> {
  Timer? _timer;
  int _remainingTime; // in milliseconds

  @override
  void initState() {
    super.initState();
    _remainingTime = widget.timerDuration.inMilliseconds;
  }

  void _startTimer() {
    const interval = Duration(milliseconds: 100); // Update every 100ms for one decimal
    _timer = Timer.periodic(interval, (Timer timer) {
      setState(() {
        if (_remainingTime <= 100) {
          _timer?.cancel();
          _remainingTime = 0;
        } else {
          _remainingTime -= 100;
        }
      });
    });
  }

  @override
  void dispose() {
    _timer?.cancel();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    double seconds = _remainingTime / 1000.0;
    String displayText = seconds.toStringAsFixed(1); // Round to one decimal
    return RaisedButton(
      onPressed: _startTimer,
      child: Text(displayText),
    );
  }
}

In this code, the timer updates every 100 milliseconds, decrementing the time and displaying it with one decimal precision. The setState method ensures the UI is rebuilt on each update.

Using CountdownTimer from quiver.async

For a simpler approach, the quiver.async package provides a CountdownTimer class. First, add the dependency to pubspec.yaml:

dependencies:
  quiver: ^3.0.0

Then, use it in your code:

import 'package:quiver/async.dart';

class CountdownWidget extends StatefulWidget {
  @override
  _CountdownWidgetState createState() => _CountdownWidgetState();
}

class _CountdownWidgetState extends State<CountdownWidget> {
  int _start = 10; // seconds
  int _current = 10;
  CountdownTimer? _countdownTimer;

  void _startTimer() {
    _countdownTimer = CountdownTimer(
      Duration(seconds: _start),
      Duration(seconds: 1),
    );

    _countdownTimer!.listen(null).onData((duration) {
      setState(() {
        _current = _start - duration.elapsed.inSeconds;
      });
    });

    _countdownTimer!.listen(null).onDone(() {
      print("Done");
      _countdownTimer!.cancel();
    });
  }

  @override
  void dispose() {
    _countdownTimer?.cancel();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        RaisedButton(
          onPressed: _startTimer,
          child: Text("Start"),
        ),
        Text("$_current"),
      ],
    );
  }
}

This method simplifies the timer logic but requires an external package.

Handling Button Click Behavior

To prevent multiple timers from starting on repeated button clicks, you can disable the button or check if a timer is already running. Here's an example using a condition:

void _startTimer() {
  if (_timer != null) {
    return; // Timer already running, do nothing
  }
  // Start timer code
}

Alternatively, for a restart behavior, cancel the existing timer before starting a new one.

Leveraging the flutter_countdown_timer Package

The flutter_countdown_timer package offers a pre-built widget for countdowns. Add it to pubspec.yaml:

dependencies:
  flutter_countdown_timer: ^4.1.0

Example usage:

import 'package:flutter_countdown_timer/flutter_countdown_timer.dart';

class MyTimerPage extends StatefulWidget {
  @override
  _MyTimerPageState createState() => _MyTimerPageState();
}

class _MyTimerPageState extends State<MyTimerPage> {
  int endTime = DateTime.now().millisecondsSinceEpoch + 1000 * 30; // 30 seconds from now

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: CountdownTimer(
          endTime: endTime,
          widgetBuilder: (_, CurrentRemainingTime time) {
            if (time == null) {
              return Text('Countdown ended');
            }
            return Text('Seconds: ${time.sec}');
          },
        ),
      ),
    );
  }
}

This package handles the timer internally and provides customization options.

Conclusion

Implementing a countdown timer in Flutter with decimal precision can be achieved using various methods. Timer.periodic offers flexibility, while external packages like quiver.async and flutter_countdown_timer simplify the process. Always manage timer lifecycle to avoid memory leaks and ensure smooth UI updates.

Copyright Notice: All rights in this article are reserved by the operators of DevGex. Reasonable sharing and citation are welcome; any reproduction, excerpting, or re-publication without prior permission is prohibited.