2

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.

Eran
  • 387,369
  • 54
  • 702
  • 768

2 Answers2

1

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");
              });
    });




        }
      }
    }
  }
}
Dev
  • 6,628
  • 2
  • 25
  • 34
0

This line:

class HomePageState extends State<StatefulWidget> {

should be

class HomePageState extends State<HomePage> {

That's all. It should work then.

Also, you should change this:

  @override
  State<StatefulWidget> createState() {
    return HomePageState();
  }

to:

  @override
  State<HomePage> createState() => HomePageState();
Benjamin
  • 5,783
  • 4
  • 25
  • 49
  • Thanks for pointing out these errors. I've corrected them but the problem still persists. What I'm looking for is a way to delay the intermediate repaints to the stateful widget. In any case I have edited the code in the question with your corrections. The way the app runs is : i press the dropdown button, The app seemingly freezes for a period of time and then the array on screen is directly converted into the sorted array – Vineet Kalghatgi Dec 08 '19 at 15:38
  • What setState() do you think is the issue? Also, did you try completely restarting the application? – Benjamin Dec 08 '19 at 15:45
  • The setState() within the _bubbleSortVisualizer() function. I'm using it to update the list member variable with the partially sorted list. And yes i have tried restarting the application but to no avail – Vineet Kalghatgi Dec 08 '19 at 15:50
  • Try extracting `List.from(bubbleArr)` into its own variable above it (not in `setState()`) and set `arr` equal to that. – Benjamin Dec 08 '19 at 15:52
  • Or a better approach is to modify the return type of `_bubbleSortVisualizer()` and return the `List.from(bubbleArr)` and set the state where you call the method. – Benjamin Dec 08 '19 at 15:55
  • I assume you mean like this : `List partially_sorted_list = List.from(bubbleArr); setState(() { arr = partially_sorted_list; print("Updated to : $arr"); }); ` But the problem is still persisting – Vineet Kalghatgi Dec 08 '19 at 15:59
  • S In reference to your solution of changing the return type, I would still have to call setState() within a loop, only now it is within the switch case of the dropDownButton. Something like `while(isSorted(arr)){ arr = _bubbleSortVisualizer(); sleep(Duration(seconds:10)); }` The outcome would be the same since setSate() is still in a loop. (isSorted() is a hypothetical helper function ). – Vineet Kalghatgi Dec 08 '19 at 16:16