killerbird
killerbird

Reputation: 23

Dart - execute a function after x seconds unless cancelled by event

I am currently writing an app using Flutter and Dart. On a button onPressed event I would like to invoke an action that executes after timeLeft seconds unless it is cancelled by correctly entering a pin. Additionally, I would like to use the value timeLeft in a Text widget.

This would require a structure with the following functionality:

I am wondering how to do this according to flutter's and dart's best practices. For state management I am using the provider pattern so preferably this approach is compatible with the provider pattern.

This is what I have tried so far:

class Home extends ChangeNotifier {
  int secondsLeft = 10;

  void onPressedEmergencyButton(BuildContext context) {
    countDown();
    showDialog<void>(
      context: context,
      builder: (context) {
        return ScreenLock(
          title: Text(
              "Sending message in ${context.read<Home>().secondsLeft} seconds"),
          correctString: '1234',
          canCancel: false,
          didUnlocked: () {
            Navigator.pop(context);
          },
        );
      },
    );
  }

  void countDown() {
    Future.delayed(const Duration(seconds: 1), () {
      secondsLeft =- 1;
      notifyListeners();
      if (secondsLeft <= 0) {
        // Do something
        return;
      }
    });
  }
}

Upvotes: 2

Views: 1660

Answers (2)

Md. Yeasin Sheikh
Md. Yeasin Sheikh

Reputation: 63604

You can use CancelableOperation from async package.

Simplifying code-snippet and about _cancelTimer(bool) , this bool used to tell widget about true = time end, and on cancel false like _cancelTimer(false);, rest are described on code-comments.

class TS extends StatefulWidget {
  const TS({Key? key}) : super(key: key);

  @override
  State<TS> createState() => _TSState();
}

class _TSState extends State<TS> {
  Timer? _timer;
  final Duration _refreseRate = const Duration(seconds: 1);

  CancelableOperation? _cancelableOperation;

  Duration taskDuration = const Duration(seconds: 5);

  bool isSuccess = false;

  _initTimer() {
    if (_cancelableOperation != null) {
      _cancelTimer(false);
    }

    _cancelableOperation = CancelableOperation.fromFuture(
      Future.delayed(Duration.zero),
    ).then((p0) {
      _timer = Timer.periodic(_refreseRate, (timer) {
        setState(() {
          taskDuration -= _refreseRate;
        });
        if (taskDuration <= Duration.zero) {
          /// task complete on end of duration

          _cancelTimer(true);
        }
      });
    }, onCancel: () {
      _timer?.cancel();

      setState(() {});
    });
  }

  _cancelTimer(bool eofT) {
    // cancel and reset everything
    _cancelableOperation?.cancel();
    _timer?.cancel();
    _timer = null;
    taskDuration = const Duration(seconds: 5);
    isSuccess = eofT;
    setState(() {});
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            if (isSuccess)
              Container(
                height: 100,
                width: 100,
                color: Colors.green,
              ),
            if (_timer != null)
              Text("${taskDuration.inSeconds}")
            else
              const Text("init Timer"),
          ],
        ),
      ),
      floatingActionButton: Row(
        mainAxisSize: MainAxisSize.min,
        children: [
          FloatingActionButton(
            child: const Text("init"),
            onPressed: () {
              _initTimer();
            },
          ),
          FloatingActionButton(
            child: const Text("Cancel"),
            onPressed: () {
              _cancelTimer(false);
            },
          ),
        ],
      ),
    );
  }
}

Upvotes: 1

mmcdon20
mmcdon20

Reputation: 6696

You can use the Timer class to run a function after a set Duration. It doesn't give you the time remaining, but you can calculate it yourself.

Here is a quick implementation I put together:

import 'dart:async';

import 'package:flutter/material.dart';

void main() async {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData.light(),
      home: const Scaffold(
        body: Center(
          child: Countdown(),
        ),
      ),
    );
  }
}

class Countdown extends StatefulWidget {
  const Countdown({Key? key}) : super(key: key);

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

class _CountdownState extends State<Countdown> {
  bool active = false;
  Timer? timer;
  Timer? refresh;
  Stopwatch stopwatch = Stopwatch();
  Duration duration = const Duration(seconds: 5);

  _CountdownState() {
    // this is just so the time remaining text is updated
    refresh = Timer.periodic(
        const Duration(milliseconds: 100), (_) => setState(() {}));
  }

  void start() {
    setState(() {
      active = true;
      timer = Timer(duration, () {
        stop();
        onCountdownComplete();
      });
      stopwatch
        ..reset()
        ..start();
    });
  }

  void stop() {
    setState(() {
      active = false;
      timer?.cancel();
      stopwatch.stop();
    });
  }

  void onCountdownComplete() {
    showDialog(
      context: context,
      builder: (context) => const AlertDialog(
        title: Text('Countdown was not stopped!'),
      ),
    );
  }

  int secondsRemaining() {
    return duration.inSeconds - stopwatch.elapsed.inSeconds;
  }

  @override
  void dispose() {
    timer?.cancel();
    refresh?.cancel();
    stopwatch.stop();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisSize: MainAxisSize.min,
      children: [
        if (active) Text(secondsRemaining().toString()),
        if (active)
          TextButton(onPressed: stop, child: const Text('Stop'))
        else
          TextButton(onPressed: start, child: const Text('Start')),
      ],
    );
  }
}

Upvotes: 1

Related Questions