I'm experimenting with Flutter on Android in my spare time, and I'm struggling to make a toy Mandelbrot application wait for an async function to complete.
I've designed my Widget build method to show a circular progress indicator until the Mandelbrot points have been calculated (which takes a few seconds) - it checks a boolean flag "_gotPointValues" and shows the progress indicator if it is false.
The issue is that the async/await mechanism doesn't wait for the long running calculation to complete.
Execution from the line:
print("** Got data from async future **");
.. onwards continues immediately after entering the MandelbrotWidgetState._getMandelbrotPoints function.
I'm sure I'm missing something very obvious - appreciate any help if anyone can spot what I'm doing wrong.
Thanks.
import 'package:flutter/material.dart';
import 'package:phil_flutter_app/mandelbrot_painter.dart';
import 'package:phil_flutter_app/mandelbrot_points.dart';
/// This class defines a Mandelbrot widget.
class MandelbrotWidget extends StatefulWidget
{
@override
MandelbrotWidgetState createState()
{
return MandelbrotWidgetState();
}
}
/// This class defines a Mandelbrot widget state.
class MandelbrotWidgetState extends State<MandelbrotWidget>
{
int _pointsWidth;
int _pointsHeight;
int _maxIterations;
MandelbrotPoints _mandelbrotPoints;
MandelbrotPainter _mandelbrotPainter;
List _pointValues;
bool _gotPointValues;
/// Initialise.
void initState()
{
super.initState();
print("initState");
_pointsWidth = 1080;
_pointsHeight = 1920;
_maxIterations = 256;
_mandelbrotPoints = new MandelbrotPoints(_pointsWidth, _pointsHeight, _maxIterations);
_mandelbrotPainter = new MandelbrotPainter(_pointsWidth, _pointsHeight);
_pointValues = null;
_gotPointValues = false;
_getMandelbrotPoints();
}
/// Get the mandelbrot points.
void _getMandelbrotPoints() async
{
_pointValues = await _mandelbrotPoints.calculateMandelbrotPoints();
print("** Got data from async future **");
setState(() {
_gotPointValues = true;
});
}
@override
Widget build(BuildContext context)
{
print("build");
_mandelbrotPainter.setDevicePixelRatio(MediaQuery.of(context).devicePixelRatio);
return Scaffold(
backgroundColor: Colors.black,
appBar: AppBar(
title: Text("PH - Mandelbrot Test - Google Flutter"),
centerTitle: true
),
body: _buildBody(),
floatingActionButton: new FloatingActionButton(
onPressed: _redraw,
tooltip: 'Refresh',
child: Icon(Icons.refresh),
),
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat
);
}
/// Build the body.
Widget _buildBody()
{
if (_gotPointValues)
{
print("** Building fractal **");
_mandelbrotPainter.setPointValues(_pointValues);
return new Center(
child: new CustomPaint(
painter: _mandelbrotPainter,
child: Center()
)
);
}
else
{
print("** Building progress indicator **");
return new Center(
child: new CircularProgressIndicator()
);
}
}
/// Redraw.
void _redraw()
{
_mandelbrotPainter.repaint();
}
}
/// This class calculates points for the Mandelbrot fractal.
class MandelbrotPoints
{
int width;
int height;
int _maxIterations;
/// Constructor.
MandelbrotPoints(int width, int height, int _maxIterations)
{
this.width = width;
this.height = height;
this._maxIterations = _maxIterations;
}
/// Calculate the Mandelbrot points.
Future<List> calculateMandelbrotPoints() async
{
print("MandelbrotPoints.calculateMandelbrotPoints");
int w = width;
int h = height;
List _pointValues = new List.generate(w, (i)
=> new List(h));
int xOffset = 0;
int yOffset = -(h ~/ 6);
double zoom = 0.8;
for (int x = 0; x < w; x ++)
{
for (int y = 0; y < h; y ++)
{
double cRe = 1.5 * (y + yOffset - h / 2) / (0.5 * zoom * h);
double cIm = 1.0 * (x + xOffset - w / 2) / (0.5 * zoom * w);
double zx = 0;
double zy = 0;
int iteration = 0;
while (zx * zx + zy * zy <= 4 && iteration < _maxIterations)
{
double zyNew = zy * zy - zx * zx + cRe;
zx = 2 * zx * zy + cIm;
zy = zyNew;
iteration ++;
}
if (iteration < _maxIterations)
{
_pointValues[x][y] = iteration;
}
else
{
_pointValues[x][y] = -1;
}
}
}
return _pointValues;
}
}
import 'dart:math';
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:phil_flutter_app/mandelbrot_colour_utils.dart';
/// This class defines a custom painter
/// to draw a Mandelbrot fractal.
class MandelbrotPainter extends CustomPainter
{
int _pointsWidth;
int _pointsHeight;
List _pointValues;
Random _random;
MandelbrotColorUtils _mandelbrotColorUtils;
int _randomHue;
Size _size;
Canvas _canvas;
double _devicePixelRatio;
Paint _paint;
double _width;
double _height;
/// Constructor.
MandelbrotPainter(int _pointsWidth, int _pointsHeight)
{
print("MandelbrotPainter constructor");
this._pointsWidth = _pointsWidth;
this._pointsHeight = _pointsHeight;
_random = new Random();
_mandelbrotColorUtils = new MandelbrotColorUtils();
_randomHue = _getRandomNumber(0, 255);
_canvas = null;
_width = -1;
_height = -1;
_paint = null;
}
/// Set the device pixel ratio.
void setDevicePixelRatio(double devicePixelRatio)
{
_devicePixelRatio = devicePixelRatio;
}
@override
void paint(Canvas canvas, Size size)
{
print("paint");
_size = size;
_width = size.width;
_height = (size.height * 0.86);
_canvas = canvas;
_paint = Paint();
_paint.strokeWidth = 1;
_paint.style = PaintingStyle.fill;
_randomHue = _getRandomNumber(0, 255);
_drawMandelbrotPoints();
}
/// Repaint.
void repaint()
{
paint(_canvas, _size);
}
/// Set the point values.
void setPointValues(List _pointValues)
{
this._pointValues = _pointValues;
}
/// Draw the Mandelbrot points.
void _drawMandelbrotPoints()
{
print("_drawMandelbrotPoints");
_paint.color = Colors.black;
_canvas.drawRect(Rect.fromLTWH(0, 0, _width, _height), _paint);
int w = (_width * _devicePixelRatio).round();
int h = (_height * _devicePixelRatio).round();
for (int x = 0; x < _pointsWidth; x ++)
{
for (int y = 0; y < _pointsHeight; y ++)
{
int pixelValue = _pointValues[x][y];
if (pixelValue == -1)
{
_paint.color = Color.fromARGB(255, 0, 0, 0);
}
else
{
double h = ((2 * pixelValue) + _randomHue).toDouble();
_paint.color =
_mandelbrotColorUtils.createColorFromHsv(h, 100, 100);
}
List<Offset> points = new List();
int pointX = (x * (w / _pointsWidth)).round();
int pointY = (y * (h / _pointsHeight)).round();
points.add(Offset(pointX.toDouble() / _devicePixelRatio,
pointY.toDouble() / _devicePixelRatio));
_canvas.drawPoints(PointMode.points, points, _paint);
}
}
}
@override
bool shouldRepaint(CustomPainter oldDelegate)
{
return true;
}
/// Get a random number.
int _getRandomNumber(int min, int max)
{
return min + _random.nextInt(max - min);
}
}