skip to Main Content

I have a Stop Watch timer in a Flutter Screen which works as required in the beginning inside a FutureBuilder but when I add another futurebuilder to add information from an API it keeps looping indefinitely:

Initially when I opened the screen it was looping indefinelty then when I adjusted the code it looped endlessly only when I clicked the Start button, it continued looping even after clicking the stop button.

Here is the dart file:

class Screen extends StatefulWidget {
  @override
  _ScreenState createState() => _ScreenState();
}

class _ScreenState extends State<Screen> {
  bool isStartButtonDisabled = false;
  bool isStopButtonDisabled = true;
  Stopwatch _stopwatch = Stopwatch();
  String _elapsedTime = "00:00:00";
  late Timer _timer;
void _startStopwatch() {
    print("void _startStopwatch() { Starting stopwatch");
    _stopwatch.start();
    setState(() {
      isStartButtonDisabled = true;
      isStopButtonDisabled = false;
    });

    _timer = Timer.periodic(
      const Duration(seconds: 1),
      (Timer timer) {
        if (mounted) {
          setState(() {
            _elapsedTime = _stopwatch.elapsed.toString().split(".")[0];
          });
        }
      },
    );
  }

  @override
  void dispose() {
    _timer?.cancel();
    super.dispose();
  }

Here is ui

@override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: ListView(
        children: [
          Container(
            child: Column(
              children: [
                GFButton(
                  onPressed: isStartButtonDisabled ? null : startWorkout,
                  text: "Start Workout",
                ),
                Text(
                  _elapsedTime,
                ),
                Card(
                    child: Padding(
                  padding: const EdgeInsets.all(18.0), //change here
                  child: Column(
                    children: [
                      FutureBuilder<List<Model_No_1>>(
                        future: futureModel_No_1,
                        builder: (BuildContext context,
                            AsyncSnapshot<List<Model_No_1>> snapshot) {
                          if (snapshot.hasData) {
                            return Column(
                              children: List.generate(snapshot.data!.length,
                                  (int index) {
                                String Some_VariableName =
                                    snapshot.data![index].name;
                                return Column(
                                  children: [
                                    Text(
                                      snapshot.data![index].name,
                                    ),
                                    Builder(builder: (context) {
                                      return Container(
                                        child: SingleChildScrollView(
                                            child: Column(
                                          children: [
                                            Card(
                                              child: Row(
                                                children: [
                                                  Expanded(
                                                    child: FutureBuilder<
                                                        Get_Old_Model_No>(
                                                      future: () {
                                                        final Map<String,dynamic>                                                            arguments =ModalRoute.of(context)!.settings.arguments as Map<String,dynamic>;final int id =arguments['id'] ??
0;print("This is the id $id");return APIService.Get_Old_Model_No(id);}(),builder:(context, snapshot) {print("Snapshot data:${snapshot.data}");print("Snapshot error:${snapshot.error}");
                                                        if (snapshot.hasData) {
                                                          return Column(
                                                            children: [
                                                              // Text(snapshot
                                                              //     .data
                                                              //     ?.endDate),
                                                            ],
                                                          );
                                                        } else if (snapshot
                                                            .hasError) {
                                                          return Text(
                                                              "${snapshot.error}");
                                                        }
                                                        return CircularProgressIndicator();
                                                      },),),],),),
for (var breakdown in snapshot.data![index].breakdowns)
Form(child: Expanded(child: Column(children: [TextFormField(
keyboardType:TextInputType.number,
onChanged:(value) {
final int?parsedValue =
int.tryParse(value);if (parsedValue !=
null) {setState(
() {variable1 =parsedValue;});} else {}
},),],),),),

When I add the Expanded, which runs the APIService.Get_Old_Model_No(id);

My question is why is the indefinete looping happening? How can I fix it ?

3

Answers


  1. i cannot reproduce your problem from your provided code, but having multiple encapsuled futureBuilders shouldn’t be a problem.

    Any chance that the setState gets called from within one of your future(Builder)s?

    That would trigger a rebuild, reloading the future and then setting the state again, triggering a rebuilt, and so on

    Login or Signup to reply.
  2. When you call setState the build method is called, this triggers the FutureBulder to rebuild, thereby calling the future again.

    What I see happening in your case is that once the _startStopwatch is called, the timer is started and it calls setState every second. I can’t see your stop function, but it seems you are not canceling the Timer.periodic when you stop the stopWatch.

    To solve your problem, you need to manage your state in such a way that only the necessary widget rebuild when the state changes. To achieve this you will need more than setState. See a simple example below using ValueNotifier and ValueListenableBuilder.

    class Screen extends StatefulWidget {
    @override
    _ScreenState createState() => _ScreenState();
    }
    
    class _ScreenState extends State<Screen> {
     ValueNotifier<bool> isStartButtonDisabled = ValueNotifier(false);
     ValueNotifier<bool> isStopButtonDisabled = ValueNotifier(true);
    
     Stopwatch _stopwatch = Stopwatch();
     ValueNotifier<String> _elapsedTime = ValueNotifier("00:00:00");
    
     late Timer _timer;
     void _startStopwatch() {
      print("void _startStopwatch() { Starting stopwatch");
      _stopwatch.start();
      
        isStartButtonDisabled.value = true;
        isStopButtonDisabled.value = false;
      
    
      _timer = Timer.periodic(
       const Duration(seconds: 1),
       (Timer timer) {
         if (mounted) {
           
            _elapsedTime.value = _stopwatch.elapsed.toString().split(".")[0];
           
          }
         },
        );
       }
    
       @override
       void dispose() {
       _timer?.cancel();
       super.dispose();
       }
    

    Now for the widgets that need to rebuild when the state changes, just use ValueListenableBuilder, See below.

      @override
      Widget build(BuildContext context) {
       return Scaffold(
        appBar: AppBar(),
        body: ListView(
         children: [
          Container(
            child: Column(
              children: [
               ValueListenableBuilder<bool>(valueListenable: isStartButtonDisabled, builder: (context, value, child)=> GFButton(
                  onPressed:value ? null : startWorkout,
                  text: "Start Workout",
                )),
                ValueListenableBuilder<String>(valueListenable: _elapsedTime, builder: (context, value, child)=>  Text(
                  value,
                )),
    

    In this way when _elapsedTime changes, only the Text widget is rebuilt, similarly, when isStartButtonDisabled changes, only GFButton is rebuilt.

    Also remember to cancel the Timer when you stop the StopWatch.

    Login or Signup to reply.
  3. You are defining and calling a function in your build method with is getting called every time you set state, which is occuring at every tick of your timer.

    () {
      final Map<String,dynamic> arguments = ModalRoute.of(context)!.settings.arguments as Map<String,dynamic>;final 
      int id =arguments['id'] ?? 0;
      print("This is the id $id");
      return APIService.Get_Old_Model_No(id);
    }()
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search