Search code examples
flutterdartsetstatestatefulwidget

setState() method in Flutter is not updating the view until the last call in a for loop


The problem

I am trying to build a sorting visualizer using Flutter. I have used a CustomPainter to paint vertical lines that correspond to the values of the integer list that I intend to sort. The application is set up such that when a user clicks the "Bubble sort" option from the DropDownButton (I intend to add more sorting algorithms later) the vertical lines in the CustomPainter's Canvas must sort themselves.

Code

import 'dart:io';
import 'dart:math';

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';

void main() => runApp(HomePage());

class HomePage extends StatefulWidget {
  @override
  HomePageSate createState() {
    return HomePageState();
  }
}

class HomePageState extends State<HomePage> {
  List<int> arr;

  @override
  void initState() {
    super.initState();
    arr = _getRandomIntegerList();
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Sorting Visualiser'),
          actions: <Widget>[
            DropdownButton<String>(
              onChanged: (dynamic choice) {
                switch (choice) {
                  case 'Bubble Sort':
                    _bubbleSortVisualiser();
                    break;
                }
              },
              items: <String>['Bubble Sort']
                  .map<DropdownMenuItem<String>>((String value) {
                return DropdownMenuItem<String>(
                  value: value,
                  child: Text(value),
                );
              }).toList(),
            )
          ],
        ),
        //The following flatButton refreshes arr to have a new array of random integers.
        bottomNavigationBar: FlatButton(
          onPressed: () {
            setState(() {
              arr = _getRandomIntegerList();
            });
          },
          child: Icon(Icons.refresh),
        ),
        body: CustomPaint(
          size: Size(double.infinity, double.infinity),
          painter: SortingCanvas(arr),
        ),
      ),
    );
  }

  //function to sort the list using bubble sort and repaint the canvas at every iteration.
  void _bubbleSortVisualiser() {
    print('Bubble sort function called');
    List<int> bubbleArr = List.from(arr);

    for (int i = 0; i < bubbleArr.length - 1; i++) {
      for (int j = 0; j < bubbleArr.length - 1 - i; j++) {
        int temp;
        if (bubbleArr[j] < bubbleArr[j + 1]) {
          temp = bubbleArr[j];
          bubbleArr[j] = bubbleArr[j + 1];
          bubbleArr[j + 1] = temp;
          //Every time arr changes setState() is called to visualise the changing array.
          setState(() {
            arr = List.from(bubbleArr);
            print("Updated to : $arr");
          });

          //Sleep is called so that the change is noticeable
          sleep(Duration(seconds: 1));

        }
      }
    }
  }
}

class SortingCanvas extends CustomPainter {
  List<int> arr;

  SortingCanvas(this.arr);

  @override
  void paint(Canvas canvas, Size size) {
    var linePaint = Paint()
      ..color = Colors.black
      ..style = PaintingStyle.stroke
      ..strokeWidth = 5.0
      ..isAntiAlias = true;
    //IMP the first offset is the bottom point and the second is the top point of the vertical line. 
    //It is offset from the top left corner of the canvas
    for (int i = 1; i <= arr.length; i++) {
      canvas.drawLine(Offset(50.0 + (20 * i), size.height - 50),
          Offset(50.0 + (20 * i), 50.0 * arr[i - 1]), linePaint);
    }
  }

  @override
  bool shouldRepaint(SortingCanvas oldDelegate) {
    return !listEquals(this.arr, oldDelegate.arr);
  }
}

//Helper function to get a list of 10 random integers
List<int> _getRandomIntegerList() {
  List<int> arr = [];
  Random rng = new Random();

  for (int i = 0; i < 10; i++) {
    arr.add(rng.nextInt(10));
  }
  return arr;
}

However, what ends up occurring is that only the final sorted arr is visualized on the canvas and all the intermediate steps are skipped for some reason. But the log shows the list updating.

Screenshots

The initial screen with the random list Clicking bubble sort in the dropdown button The sorted list

The sleep() function is added just to emulate an animation and I intend to replace it with a proper animation once this issue is resolved. Also, I am aware that the application is a tad ugly, but I would like to get the logic right before any UI polishing.


Solution

  • Try using Future.delay like

          void _bubbleSortVisualiser() async {
            print('Bubble sort function called');
            List<int> bubbleArr = List.from(arr);
    
            for (int i = 0; i < bubbleArr.length - 1; i++) {
              for (int j = 0; j < bubbleArr.length - 1 - i; j++) {
                int temp;
                if (bubbleArr[j] < bubbleArr[j + 1]) {
                  temp = bubbleArr[j];
                  bubbleArr[j] = bubbleArr[j + 1];
                  bubbleArr[j + 1] = temp;
                  //Every time arr changes setState() is called to visualise the changing array.
        await Future.delayed(const Duration(seconds: 1), () {
          setState(() {
                    arr = List.from(bubbleArr);
                    print("Updated to : $arr");
                  });
        });
    
    
    
    
            }
          }
        }
      }
    }