Search code examples
flutterasync-awaitflame

Looking for a synchronous alternative to Flutter's .toImage() method


At the moment I'm experimenting with Flutter and the Flame game engine. To do so I'm extending Flame's BaseGame class and do some heavy processing inside it's constructor. The heavy processing includes composing an Image out of other images and ultimatively drawing it onto a temporary Canvas and the result is stored in a Picture object.

ui.PictureRecorder rec = new ui.PictureRecorder();
Canvas tempCanvas = new Canvas(rec, bgRect);
// left out picture operations
ui.Picture pic = rec.endRecording();

To finally get an Image object, I need to call the asynchronous .toData() method which returns a Future. I'm wrapping the call in an async method getImage()

getImage(ui.Picture pic, Rect bgRect) async {
    background = await pic.toImage(bgRect.width.toInt(), bgRect.height.toInt());
    done = true;
}

(background is a class variable of type Image which is used inside the render() method of the BaseGame class)

Problem is, because it's asynchronous the rest of my statements inside the game's constructor get executed and after it finishes, the render() method fires but the background might not be available yet. To workaround, I added a class variable done of type bool which gets set to true inside the getImage() method. Now I modified the render() to wait for done to be true.

void render(Canvas canvas) {
    if (done) {
        canvas.drawImage(background, new Offset(0.0, 0.0), new Paint());
    }
}

Of course this ain't to elegant. Is there a way to wait for the .toImage() method to finish inside the constructor function of the extended BaseGame class? I tried making the constructor async like:

class TheGame extends BaseGame {
    Image background;
    bool done = false;

    TheGame(DeviceOrientation orientation) async {  
    }
}

but this gives me the error:

The modifier 'async' can't be applied to the body of a constructor

What else could I try to make it 'synchronous'?


Solution

  • If you really need the image before the first frame is rendered, you can just create a static method which is responsible for creating TheGame

    class TheGame extends BaseGame {
        final Image background;
    
        TheGame._(DeviceOrientation orientation, this.background);
    
        static Future<TheGame> create(DeviceOrientation orientation) async {
          return TheGame._(orientation, await generateImage());
        }
    }
    

    but I assume it doesn't really hurt if you render a few frames without a background image, then I would suggest you simply check background != null instead of the done property, which feels a bit redundant.