How to reset a timer whenever the app goes to the background?

116 Views Asked by At

I am trying to get my countdown timer to reset when the app goes to the background. I currently can detect the different App Lifecycle states, but can't figure out how to trigger a timer reset when the "paused" state is detected.

The App lifecycle detector is in the root widget and the "home" of my root widget is currently the countdown timer page. (I set it up like this so the App Lifecycle detection would work across the entire app, but I'm not sure if this was the right implementation.)

I initially thought I could just do controller.reset under case AppLifecycleState.paused, but I believe since controller is defined later in my code I can't use it in the root widget.

So, here is the Root widget:

class TimerApp extends StatefulWidget {
  const TimerApp({super.key});

  @override
  State<TimerApp> createState() => _TimerAppState();
}

class _TimerAppState extends State<TimerApp> with WidgetsBindingObserver {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        primarySwatch: Colors.amber,
      ),
      home: const TimerPage(),
    );
  }

  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this);
  }

  @override
  void dispose() {
    WidgetsBinding.instance.removeObserver(this);
    super.dispose();
  }

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    super.didChangeAppLifecycleState(state);
    switch (state) {
     ** case AppLifecycleState.paused:
        debugPrint('Paused State');**
        break;
      case AppLifecycleState.resumed:
        debugPrint('Resumed State');
        break;
      case AppLifecycleState.inactive:
        debugPrint('Incactive State');
        break;
      case AppLifecycleState.detached:
        debugPrint('Detached State');
        break;
    }
  }
}

And, here is the Countdown Timer Page widget:

class TimerPage extends StatefulWidget {
  const TimerPage({super.key});

  

  @override
  State<TimerPage> createState() => _TimerPageState();
}

class _TimerPageState extends State<TimerPage> with TickerProviderStateMixin {
  late AnimationController controller;

  bool isPlaying = false;
  

  String get countText {
    Duration count = controller.duration! * controller.value;
    return controller.isDismissed
        ? '${controller.duration!.inHours}:${(controller.duration!.inMinutes % 60).toString().padLeft(2, '0')}:${(controller.duration!.inSeconds % 60).toString().padLeft(2, '0')}'
        : '${count.inHours}:${(count.inMinutes % 60).toString().padLeft(2, '0')}:${(count.inSeconds % 60).toString().padLeft(2, '0')}';
  }

  double progress = 1.0;
  @override
  void initState() {
    super.initState();
    controller = AnimationController(
      vsync: this,
      duration: const Duration(seconds: 90),
    );
    controller.addListener(
      () {
        if (controller.isAnimating) {
          setState(
            () {
              progress = controller.value;
            },
          );
        } else {
          setState(
            () {
              progress = 1.0;
              isPlaying = false;
            },
          );
        }
      },
    );
  }

  @override
  void dispose() {
    controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        actions: [
          IconButton(
            onPressed: () {
              debugPrint('invert colors');
            },
            icon: const Icon(Icons.invert_colors),
          ),
        ],
      ),
      body: Container(
        color: const Color.fromARGB(255, 152, 57, 57),
        height: 800,
        child: Column(
          children: [
            Expanded(
              child: Center(
                child: Stack(
                  alignment: Alignment.center,
                  children: [
                    SizedBox(
                      width: 300,
                      height: 300,
                      child: CircularProgressIndicator(
                        backgroundColor: Colors.grey,
                        value: progress,
                        strokeWidth: 8,
                      ),
                    ),
                    GestureDetector(
                      onTap: () {
                        if (controller.isDismissed) {
                          showModalBottomSheet(
                            context: context,
                            builder: (context) => SizedBox(
                              height: 300,
                              child: CupertinoTimerPicker(
                                initialTimerDuration: controller.duration!,
                                onTimerDurationChanged: (time) {
                                  setState(
                                    () {
                                      controller.duration = time;
                                    },
                                  );
                                },
                              ),
                            ),
                          );
                        }
                      },
                      child: AnimatedBuilder(
                        animation: controller,
                        builder: (context, child) => Text(
                          countText,
                          style: const TextStyle(
                              fontSize: 60, fontWeight: FontWeight.bold),
                        ),
                      ),
                    ),
                  ],
                ),
              ),
            ),
            Padding(
              padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 20),
              child:
                  Row(mainAxisAlignment: MainAxisAlignment.center, children: [
                GestureDetector(
                  onTap: () {
                    if (controller.isAnimating) {
                      controller.stop();
                      setState(() {
                        isPlaying = false;
                      });
                    } else {
                      controller.reverse(
                          from: controller.value == 0 ? 1.0 : controller.value);
                      setState(
                        () {
                          isPlaying = true;
                        },
                      );
                    }
                  },
                  child: RoundButton(
                      icon: isPlaying == true
                          ? Icons.pause_circle_outline_sharp
                          : Icons.play_circle_outline_sharp),
                ),
                GestureDetector(
                    onTap: () {
                      controller.reset();
                      setState(
                        () {
                          isPlaying = false;
                        },
                      );
                    },
                    child: const RoundButton(icon: Icons.stop_circle_outlined)),
              ]),
            ),
          ],
        ),
      ),
    );
  }
}

How can I trigger a timer reset when the paused state is detected?

0

There are 0 best solutions below