Search code examples
flutterflutter-layoutflutter-dependenciesflutter-webflutter-animation

Flutter: Countdown Timer


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

Solution

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