Search code examples
flutterdarttimetimer

Flutter Timer Issue


I implemented a Timer but it doesn't seem to be calling the callback() functions after the right duration, it starts by calling the callback after a few seconds and speeds up every time the callback is called. Ex. 5 sec --> callback(), 4 sec --> callback(), 2 --> callback(), 0.3 sec --> callback () ...

I would like to have a timer that reduces the value of an int after each second.

Current code - ViewModel:

  void displayCountdown(Item item) {
    timer = Timer.periodic(const Duration(seconds: 1), (Timer _timer) => {
      if (lead.remainingTime >= 1) {
        item.remainingTime = item.remainingTime -= 1,
        notifyListeners(),
      }else {
        print('out of time'),
        _timer.cancel(),
        notifyListeners(),
      }
    });
  }

Code - View:

  ListTile _tile(Item item) {
    widget.model.displayCountdown(item);
    Duration duration = Duration(milliseconds: item.remainingTime);
    String countdown = '${duration.toString().split('.')[0]}';
    return ListTile(
      leading: CircleAvatar(
        backgroundImage: item.author["avatarUrl"] == "" ? NetworkImage('https://picsum.photos/250?image=3') : NetworkImage(lead.author["avatarUrl"]),
      ),
      title: Text(item.author["fullName"]),
      subtitle: Text(item.keywords.toString()),
      trailing: Row(mainAxisSize: MainAxisSize.min, children: [Text('$countdown', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: Colors.grey))],),
    );
  }

Solution

  • I don't know why it didn't worked but I found an alternative that works.

    First, I create a getter for my countdowns. Second, when I get my items, I set each timer in my getter. Third, I create my timers with my getter values. Fourth, I call my getter at the index of the current tile in my view.

    ...
      List<int> get countDowns => _durations;
      List<int> _durations = [];
    
      List<Item> items = [];
    ...
    
      Future getItems() async {
        setState(ViewState.Busy);
        final response = await _api.getItems();
        if (response is SuccessState) {
          items = response.value;
          for (var item in items) {
            _durations.add(item.remainingTime);
          }
          displayCountdowns();
        } else if (response is ErrorState) {
          String error = response.msg;
          print('Error $error');
        } else {
          print('Error');
        }
        setState(ViewState.Idle);
      }
    ...
      void displayCountdowns() {
        for (var countDown in _durations) {
          Timer.periodic(const Duration(seconds: 1), (timer) { 
            countDown -= 1000;
            _durations[0] = countDown;
            notifyListeners();
          });
        }
      }
    

    Here is the code for the view:

    ...
    var duration = Duration(milliseconds:widget.model.countDowns[index]);
    ...
      ListTile _tile(Lead lead, Duration duration) {
        String countdown = '${duration.toString().split('.')[0]}';
        return ListTile(
          leading: CircleAvatar(
            backgroundImage: lead.author["avatarUrl"] == "" ? NetworkImage('https://picsum.photos/250?image=3') : NetworkImage(lead.author["avatarUrl"]),
          ),
          title: leadTitle(lead.author["fullName"]),
          subtitle: leadSubtitle(lead.keywords.toString()),
          trailing: Row(mainAxisSize: MainAxisSize.min, children: [Text('$countdown', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: Colors.grey))],),
        );
      }