So now let’s come back to our real world of Flutter!

Key takeaways from this article:

Handling complex number calculations in Dart/Flutter. Using ChangeNotifier, ChangeNotifierProvider and Consumer to update the progress of mandelbrot set computation. Utilizing .drawPoints() method to render pixel colours inside CustomPainter.

Step 1

Add complex package as a dependency in your pubspec.yaml file

complex: ^0.5.0

This package provides us a Complex class which is useful in representing complex numbers, i.e. numbers which have both a real and an imaginary part. This class also provides various methods and operators which help in complex number calculations.

Next, add the provider package as a dependency -

provider: ^4.0.4

This package provides us access to useful classes which will help us in managing and injecting the computational state of the Mandelbrot set.

Now, let us import the required libraries and define max iteration.

Step 2

Add the main function and create a MaterialApp.

Step 3

ChangeNotifier provides change notification to its listeners (Consumer). We can extend our model class(MandelbrotClass) with ChangeNotifier and call its method notifyListeners() whenever there is a change that has to be communicated to the widgets that are listening to this model.

We are interested in the values of _currentPixelIteration (2D list of height number of rows and width number of columns) and _currentIter. To better understand the significance of these variables let us go through some theory.

“The” Mandelbrot set is the set obtained from the quadratic recurrence equation

Source: Link

Here z is a complex number and z = 0+0i for all pixels (x, y) at n=0.

C is a complex number representing a pixel (x, y) on the screen where x-axis is real axis and y-axis is imaginary axis. The center of the screen has the value of C as 0+0i.

If the width of the screen is less than the height of the screen (portrait mode), the real value of z ranges [-2.25, 0.75] and the imaginary value of z varies accordingly.

If the width of the screen is mode than the height of the screen (landscape mode), the imaginary value of z ranges [-1.5i, 1.5i] and the real value of z varies accordingly.

To keep the aspect ratio of real and imaginary axis as 1:1, we compute hratio and wratio.

Now, we initialize:

2D Complex lists z and C to store the z and C values at each pixel.

2D boolean list continueIteration which stores the information(true/false) if a z at (x, y) has escaped its threshold value.

_currentPixelIteration — It is the 2D list containing the maximum value of n+1 for a pixel at (x, y) for which the absolute value of z < 2, i.e., the iteration when z escapes the threshold.

_currentIter — It is the value n+1 as per the equation mentioned above. We initialize _currentIter as 0 at the start of the computation when the value of z at n=0 is 0+0i for all the points.

notifyListeners() communicates the change in value of _currentPixelIteration and _currentIter before the iterative computation starts.

Since the mandelbrot set computation is intensive, we are using notifyListeners() to update the progress which is reflected in the UI of our app. Note that we are taking a stepwise approach to increment the maximum iteration (1, 2, 4, 8, 16, 32). This is done to achieve a progressive display having 6 stages. A small Future.delayed(..) call is added to smoothen the progress of mandelbrot display.

The next code segment will clearly show why I went ahead with the entire hassle of using async function, provider package and ChangeNotifier. Any basic demo of Mandelbrot set generation available online has a single step where the entire computation is completed for each pixel till max iteration is reached (32) and then the image is rendered. This could have been achieved in Flutter by simply using a FutureBuilder which updates the UI when the computation is complete. But, it is not a good design from UI/UX perspective as the user has to patiently wait and stare at a blank canvas for a long time before the computation ends and the image gets updated. Even if you go ahead and add a progressbar or circular progress indicator in such cases, it is not helpful as the user still has to stare at a blank canvas with a small progress indication, thereby missing and being unaware of the beauty of the progressive computation happening underneath.

How is the above stepwise computation progressive?

The result at the end of each step is carried forward instead of recomputing it all over again. For example, at the end of step 3 (max iteration=4) the results of the computed z values are used for the next step (max iteration=8). So the progress of iterations are step 3 (n=3..4) to step 4 (n=5..8) instead of step 3 (n=1..4) to step 4 (n=1..8). Using continueIteration 2D list helps keeping track of the points z which need to be computed further in the next step, thereby eliminating unnecessary computation for the entire complex plane. For example, if a pixel has converged before reaching the max iteration value in step 3 it is marked as converged and no operations are performed [not even calculation of abs()] in any future steps 4,5 or 6.

Step 4

We calculate the width and height of the screen and pass the information while creating the ChangeNotifierProvider.

A simple Stack widget if used to display the generated mandelbrot along with the current iteration for which the mandelbrot is being displayed.

There are 2 consumers of the MandelbrotClass which get notified whenever notifyListeners() is called:

CustomPaint(..)

Text(..)

FractalPainter is an extended CustomPainter whose purpose is to paint the pixels on the canvas based on the max iterations for each z.

Step 5

Let’s discuss the final piece of our puzzle. How do we actually paint the Mandelbrot?

We create a FractalPainter by extending the CustomPainter. We override the paint(..) method to paint the entire screen (pixel by pixel) and set the shouldRepaint(..) as true (note!) to refresh the mandelbrot painting for each step.

Done!

Hope you enjoyed the article!

You can find the above code in the repository:

Do connect with me on LinkedIn, Twitter and Github:

Also, don’t forget to subscribe my youtube channel where I share videos on scientific computing: