skip to Main Content

How can I completely dispose the function/screen?

Below the reproduce code, after I press navigator pop to the home screen, I expected the next screen is popped and all function is stopped, but the function in the while keeps running.

import 'package:flutter/material.dart';

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

class Config {
  static int count = 0;
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const HomeScreen(),
    );
  }
}

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

  @override
  State<NextScreen> createState() => _NextScreenState();
}

class _NextScreenState extends State<NextScreen> with WidgetsBindingObserver {
  int timerCount = 0;
  bool status = false;
  bool isCurrentStatus = true;

  @override
  void initState() {
    super.initState();
    getInit();
  }

  void getInit() {
    try {
      WidgetsBinding.instance.addPostFrameCallback((_) async {
        startLoopStatus();
      });
    } catch (e) {
      print('[getInit] ERROR:: $e');
    }
  }

  Future<void> startLoopStatus() async {
    print('[startLoopStatus] Running startLoopStatus 001.');
    print(isCurrentStatus);
    while (status == false && isCurrentStatus) {
      if (mounted) {
        Future.delayed(Duration.zero, () {
          setState(() {
            timerCount += 1;
          });
        });
      }

      await Future.delayed(const Duration(seconds: 2), () {
        debugPrint("delay----------");
        Config.count += 1;
        print(Config.count);
      });
      await loopArrive();
    }
  }

  Future<void> loopArrive() async {
    print('[loopArrive] Running loopArrive 001.');
    print('[loopArrive] timerCount: $timerCount');
    try {
      if (mounted) {
        if (ModalRoute.of(context)!.isCurrent) {
          print('[loopArrive] Running loopArrive 002.');
        }
      }
    } catch (e) {
      print('[loopArrive] ERROR:: $e');
    }
  }

  @override
  void dispose() {
    print('[NextScreen] dispose()');
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Next'),
        leading: IconButton(
          icon: const Icon(Icons.arrow_back),
          onPressed: () {
            Navigator.of(context).pop();
          },
        ),
      ),
      body: const SafeArea(
        child: Center(
          child: Column(
            children: [
              Text('Please see the terminal output.'),
            ],
          ),
        ),
      ),
    );
  }
}

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

  @override
  State<HomeScreen> createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Home'),
      ),
      body: SafeArea(
        child: Center(
          child: Column(
            children: [
              ElevatedButton(
                onPressed: () {
                  Navigator.push(
                    context,
                    MaterialPageRoute(builder: (_) => const NextScreen()),
                  );
                },
                child: const Text('Go to next page'),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

2

Answers


  1. The second screen’s state continues to exist even when you navigate to the home screen. If your method that displays messages is running periodically using something like a Timer or Future.delayed, it will continue running until explicitly stopped.

    To ensure that the method stops when you navigate away from the second screen, you can use the dispose method of the StatefulWidget.

    So you can add a Timer to cancel with the dispose call.
    But also with that the :

    while (status == false && isCurrentStatus) {}
    

    will continue to work !! so you can add a Completer<void> to finish the block..

    the full code :

    int timerCount = 0;
      bool status = false;
      bool isCurrentStatus = true;
      late Timer _timer;
      bool isActive = true;
    
      @override
      void initState() {
        super.initState();
        getInit();
      }
    
      void getInit() {
        try {
          WidgetsBinding.instance.addPostFrameCallback((_) async {
            startLoopStatus();
          });
        } catch (e) {
          print('[getInit] ERROR:: $e');
        }
      }
    
      Future<void> startLoopStatus() async {
        print('[startLoopStatus] Running startLoopStatus 001.');
        print(isCurrentStatus);
        _timer = Timer.periodic(Duration(seconds: 2), (timer) async {
        while (status == false && isCurrentStatus && isActive) {
          if (mounted) {
            Future.delayed(Duration.zero, () {
              setState(() {
                timerCount += 1;
              });
            });
          }
    
          await Future.delayed(const Duration(seconds: 2), () {
            debugPrint("delay----------");
            Config.count += 1;
            print(Config.count);
          });
          await loopArrive();
        }
       });
      }
    
      Future<void> loopArrive() async {
        print('[loopArrive] Running loopArrive 001.');
        print('[loopArrive] timerCount: $timerCount');
        try {
          if (mounted) {
            if (ModalRoute.of(context)!.isCurrent) {
              print('[loopArrive] Running loopArrive 002.');
            }
          }
        } catch (e) {
          print('[loopArrive] ERROR:: $e');
        }
      }
    
      @override
      void dispose() {
        print('[NextScreen] dispose()');
         isActive = false;
         _timer.cancel();
        super.dispose();
      }
    
    Login or Signup to reply.
  2. Just put a variable to stop the loop here :

    onPressed: () {
                  stopTheLoop = true;
                  Navigator.push(
                    context,
                    MaterialPageRoute(builder: (_) => const NextScreen()),
                );}
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search