Flutter Without Flutter

Stefan Matthias Aust
ICNH
Published in
3 min readMay 2, 2020

This is an experiment. Let’s create the minimal code required to display something on a mobile device without using the Flutter framework.

not pretty, but self-made

Flutter is a high level GUI framework that uses dart:ui (an abstraction of the Skia engine) to actually display something and to interact with the platform that something is displayed on. Of course, we’re free to directly use this low-level dart:ui library our selves.

Setup

Let’s create a new Flutter project:

$ flutter create flutter_without_flutter

then replace `lib/main.dart` with this code:

import ‘dart:ui’;void main() {
window.onBeginFrame = beginFrame;
window.scheduleFrame();
}
void beginFrame(Duration duration) {
}

Explanation: We register a global function called beginFrame with the window singleton and then ask the graphics engine to schedule a callback to this function. It is supposed to prepare and eventually draw a frame, that is, display something on the device screen.

Drawing Something

Because I’m used to work with logical units instead of physical pixels, the first step in beginFrame is to convert the physical screen size of the device to more familiar values:

void beginFame(Duration duration) {
final pixelRatio = window.devicePixelRatio;
final size = window.physicalSize / pixelRatio;
final physicalBounds = Offset.zero & size * pixelRatio;

The next step is to setup a Canvas that uses logical units:


final recorder = PictureRecorder();
final canvas = Canvas(recorder, physicalBounds);
canvas.scale(pixelRatio, pixelRatio);

Let’s then draw a circle filled with red-ish paint:


final paint = Paint()..color = Color(0xFFF44336);
final center = size.center(Offset.zero);
canvas.drawCircle(center, size.shortestSide / 4, paint);

The last step is to take the recording created by calling Canvas methods and use it to build a so called scene that is then rendered by the window singleton:


final picture = recorder.endRecording();
final sceneBuilder = SceneBuilder()
..pushClipRect(physicalBounds)
..addPicture(Offset.zero, picture)
..pop();
window.render(sceneBuilder.build());
}

By the way, the usual instant hot code reloading of Flutter projects still works and this is why it might be useful to use this setup to create low level graphics applications like for example 2D games.

Moving Something

To move the circle and bounce it around the screen, we will store its center point in a global variable (called center) and its velocity in another globale variable (called velocity), modify these variables each time beginFrame is called and then ask for another draw operation using scheduleFrame at the end of the beginFrame function.

Here is the relevant change:

Offset center;
Offset velocity;
void beginFrame(Duration duration) {

canvas.scale(pixelRatio, pixelRatio);
final radius = size.shortestSide / 4;
if (center == null) {
center = size.center(Offset.zero);
velocity = Offset(3, 5);
} else {
if (center.dx < radius || center.dx > size.width — radius)
velocity = velocity.scale(-1, 1);
if (center.dy < radius || center.dy > size.height — radius)
velocity = velocity.scale(1, -1);
center += velocity;
}
final paint = Paint()..color = Color(0xFFF44336);
canvas.drawCircle(center, radius, paint);
window.scheduleFrame();
}

Because we don’t have control over how often the beginFrame function is scheduled, we should probably make use of the passed duration and bind the circle’s velocity to the passed time. To compute the time that passed between calls, we introduce another global variable called lastDuration.

Duration lastDuration;void beginFrame(Duration duration) {

if (center == null) {

} else {

final delta = (duration — lastDuration).inMilliseconds / 1000;
center += velocity * delta;
}
lastDuration = duration;

If you kept the application running and just changed the soure code, the circle should have started moving just like magic.

Reacting to Touches

Last but not least, let’s change the velocity each time we tap the screen. To detect touches, we need to add another callback called onPointerDataPacket. This function receives a list of PointerData objects which describe touch down, touch move, and touch up events. We’re only interested in events of type PointerChange.up, though.

Here is the new main function:

void main() {
window.onBeginFrame = beginFrame;
window.onPointerDataPacket = pointerDataPacket;
window.scheduleFrame();
}

And here is the new callback function:

void pointerDataPacket(PointerDataPacket packet) {
for (final data in packet.data) {
if (data.change == PointerChange.up) {
velocity = Offset.fromDirection(
_random.nextDouble() * pi * 2,
_random.nextDouble() * 800–400,
);
}
}
}
final _random = Random();

Using this foundation, it should be possible for you to create your own Flutter-like GUI framework from scratch.

I leave this as an exercise to the reader :-)

--

--

Stefan Matthias Aust
ICNH
Editor for

App Developer · Co-Founder of I.C.N.H GmbH · Pen & paper role player