Flutter: Countdown Timer

Viewed 463

I am trying to create a timer app that has multiple countdown timers for different tasks. The issue, I am facing is that, if I start a one-timer, and press the back button, the timer stops. So I want, that timer to run till either it is being paused or the timer ends and alerts the user or the app is destroyed. Help me how can I do this using Flutter?

Any Sample Code Will be Appreciated?

enter link description here

CountDownController _controller = CountDownController();

CircularCountDownTimer(
                width: MediaQuery.of(context).size.width / 6,
                height: MediaQuery.of(context).size.width / 6,
                duration: 120,
                fillColor: Colors.green,
                ringColor: Colors.white,
                controller: _controller,
                backgroundColor: Colors.white54,
                strokeWidth: 10.0,
                strokeCap: StrokeCap.round,
                isTimerTextShown: true,
                isReverse: false,
                onComplete: () {
                  Notify();
                },
                textStyle: TextStyle(fontSize: 20.0, color: 
      Colors.black),
              ),
1 Answers

When you pop back, any "state" in the widget will be destroyed.

There are three kinds of method you can do to prevent "state" being destroyed (or memory release):

  • Using static property
  • Using state manager by Provider
  • Using state manager by static instance

There are still many method to manage your state, but not mention here, see details in this repo

Static property

Static property is something like variable outside your class, like:

// prefix "_" for private variable
const _myStaticVar = 'hello world';

class MyWidget {}

Rather, it is class based variable. Which means it can help you describe the variable more. like class Dog can has a static property static final footShouldHave = 4. Class based programing is popular because it can manage your state and any logic action "inside" the class, and make it easier to understand and code.

When the class is being destroyed (memory release), any "state" inside the class should be pop from stack but not static. You can see more detail by knowing how compiler works.

In your case, you can do something like:

class MyTimer extends StatlessWidget {
  static DateTime? starter;

  Widget build(context) {
    if (starter == null) {
      starter = DateTime.now();
    }
    final secondPass = (DateTime.now().millisecondsSinceEpoch - starter!.millisecondsSinceEpoch) / 1000;
    final secondLeft = 60 - secondPass;
    return Text(secondLeft.toString());
  }
}

Provide state manager by Provider

Provider is made for flutter and also maintained by flutter team. It can make you easy to manage your class by accessing it from context.

You can also set up how the class create.

  • lazy, create only when you need it
  • create in future
  • ...

In your case, it should be like:

  1. Build your helper class TimerManager
class TimerManager {
  final DateTime? starter;

  void startIfNeed() {
    if (starter != null) {
      starter = DateTime.now();
    }
  }

  num get secondLeft => 60 - (DateTime.now().millisecondsSinceEpoch - starter!.millisecondsSinceEpoch) / 1000
}
  1. Bind with Provider
class Homepage extends statelessWidget {
  Widget build(context) {
    return TextButton(
      onPressed: () => navigateToTimer(context),
      child: Text('go'),
    );
  }

  void navigateToTimer(Build context) {
    Navigator.of(context).push(
      MaterialPageRoute(builder: (_) => MyTimer()),
    );
  } 
}

void main() {
  runApp(MaterialApp(
    home: Provider<TimerManager>(
      create: () => TimerManager(),
      child: Homepage(),
    )
  ));
}
  1. Get it from your context.

Now when your widget is released, it is still existed in parent context (if their do exist a parent).

// remember to import provider to able `context.read()`.
// see more detail in document.
import 'package:provider/provider.dart';

class MyTimer extends StatlessWidget {
  Widget build(context) {
    final manager = context.read<TimerManager>();
    manager.startIfNeed();

    return Text(manager.secondLeft.toString());
  }
}

static instance

Kind of combined method from 1 and 2.

class TimerManager {
  // make it singleton
  static final TimerManager  instance = TimerManager._();

  // It is now private constructor
  const TimerManager._();

  ...
}

Just call it in your widget

class MyTimer extends StatlessWidget {
  Widget build(context) {
    TimerManager.instance.startIfNeed();

    return Text(TimerManager.instance.secondLeft.toString());
  }
}

Summary

There is no best way to keep your state in generally, but in your case, I recommend Provider method.

Related