DEV Community

Jordan Holland
Jordan Holland

Posted on • Updated on • Originally published at developermemos.com

Capturing a Flutter widget as an image using RepaintBoundary

The other day I decided I wanted to add a feature in one of my projects to share a graph and I was looking for ways to convert the graph into an image. In the end the approach that I landed on was using the RepaintBoundary widget and I'm going to explain how to use it in this post.

The widget

First of all let's just start with a widget, a green Container with some text inside. Here's what the code for it looks like:

Container(
  decoration: BoxDecoration(
    color: Colors.green,
  ),
  height: 200,
  width: 400,
  child: Center(
    child: Text(
      "Hello world!",
      style: TextStyle(
        color: Colors.white,
        fontSize: 24,
      ),
    )
  )
)
Enter fullscreen mode Exit fullscreen mode

And here's what the above widget looks like visually(this was generated using DartPad):

Container Widget

Wrap it with RepaintBoundary

To convert this widget to an image file(or capture/screenshot it) you need to wrap the widget in a RepaintBoundary widget:

RepaintBoundary(
  child: Container(
    decoration: BoxDecoration(
      color: Colors.green,
    ),
    height: 200,
    width: 400,
    child: Center(
      child: Text(
        "Hello world!",
        style: TextStyle(
          color: Colors.white,
          fontSize: 24,
        ),
      ),
    ),
  ),
)
Enter fullscreen mode Exit fullscreen mode

Then you need to create a GlobalKey and pass that to the RepaintBoundary widget:


final key = GlobalKey();

...

RepaintBoundary(
  key: key,
  child: Container(
    decoration: BoxDecoration(
      color: Colors.green,
    ),
    height: 200,
    width: 400,
    child: Center(
      child: Text(
        "Hello world!",
        style: TextStyle(
          color: Colors.white,
          fontSize: 24,
        ),
      ),
    ),
  ),
)
Enter fullscreen mode Exit fullscreen mode

Create the image

Now the last step is to use the key above kind of like a controller and create an image representation of the widget wrapped inside RepaintBoundary. I probably should also add that the widget you wrap doesn't have to be a Container widget. Okay, onto the code for getting the bytes for an image. You could trigger this code with a button or something along those lines:

final boundary = key.currentContext?.findRenderObject() as RenderRepaintBoundary?;
final image = await boundary?.toImage();
final byteData = await image?.toByteData(format: ImageByteFormat.png);
final imageBytes = byteData?.buffer.asUint8List();
Enter fullscreen mode Exit fullscreen mode

Then the next step is to write those bytes to a file somewhere(you will need the path_provider package to use getApplicationDocumentsDirectory):

if (imageBytes != null) {
  final directory = await getApplicationDocumentsDirectory();
  final imagePath = await File('${directory.path}/container_image.png').create();
  await imagePath.writeAsBytes(imageBytes);
}
Enter fullscreen mode Exit fullscreen mode

And here is the full code for this step:

final boundary = key.currentContext?.findRenderObject() as RenderRepaintBoundary?;
final image = await boundary?.toImage();
final byteData = await image?.toByteData(format: ImageByteFormat.png);
final imageBytes = byteData?.buffer.asUint8List();

if (imageBytes != null) {
  final directory = await getApplicationDocumentsDirectory();
  final imagePath = await File('${directory.path}/container_image.png').create();
  await imagePath.writeAsBytes(imageBytes);
}
Enter fullscreen mode Exit fullscreen mode

Once you execute this code an image representation of the widget will be created in the applications document directory of your app. Here's the output I got after I executed the code on an iOS simulator:

PNG Image Representation

By the way you can view the contents of a simulator's document directory using the following command on MacOS:

open `xcrun simctl get_app_container booted [Bundle ID] data`
Enter fullscreen mode Exit fullscreen mode

Some caveats

I'll close this post with a couple of caveats with the approach above:

  • The resulting image might end up pixelated in some cases. You can rectify this by passing the pixelRatio parameter when using await boundary?.toImage(). You might want to combine this with MediaQuery.of(context).devicePixelRatio for the best effect. I learnt about this from the README of an excellent package called screenshot. (The reason I wrote my own implementation is because I already use a lot of packages in the project I was working on and didn't want to add any more...)
  • The generated image will be in PNG format. I'm not sure if you can generate a JPEG image or how you would do it. I might end up investigating this in the future.

Discussion (0)