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