Create an Image from Widget in Flutter

Apr 24, 2021 . 3 min Muhammed Mukhthar CM Cover Image

Intro

In one of my recent projects, I had a task to create an Image from a Flutter Widget. The App was essentially one to show results for some competitions. I had to make a poster out of each Result. It consists of the program name, its category, the names of participants who got the first 3 places. Anyway, It was the first time I was doing it.

Prerequisites

First of all, we have to add the path_provider package to our pubspec.yaml file.

dependencies:
  flutter:
    sdk: flutter
  path_provider: ^2.0.1

Next, import required packages into our file.

import 'dart:io';
import 'dart:typed_data';
import 'dart:ui' as ui;
import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';
import 'package:path_provider/path_provider.dart';

These all imports are required because we are gonna deal with files and all that Stuff 😉

Flutter Time 😄

First of all, we have to create a RepaintBoundary widget. Next, put the widget we want to convert to Image under this RepaintBoudary Widget. Now Assign a GlobalKey to the key parameter of this widget.

Suppose we have a ListTile with a circleAvatar on leading and a Name on title. Then our Widget will be like,

final GlobalKey genKey = GlobalKey();

RepaintBoundary(
    key: genKey,
    child: ListTile(
        leading: CircleAvatar(),
        title: Text("Your Name"),
    ),
),

After this, calling the below function should save the ListTile as an Image in your Applications document Directory.

Future<void> takePicture() async {
  RenderRepaintBoundary boundary = genKey.currentContext.findRenderObject();
  ui.Image image = await boundary.toImage();
  final directory = (await getApplicationDocumentsDirectory()).path;
  ByteData byteData = await image.toByteData(format: ui.ImageByteFormat.png);
  Uint8List pngBytes = byteData.buffer.asUint8List();
  File imgFile = new File('$directory/photo.png');
  imgFile.writeAsBytes(pngBytes);
}

The above function is self-explanatory. It finds the RepaintBoundary Widget and creates an Image from it using the toImage() method. Then it saves it to the File provided by us referring to the path from getApplicationDocumentsDirectory. You can change the location to save the file as you like.

Don’t forget to assign the Key to RepaintBoundary Widget. Otherwise It will throw The method findRenderObject was called on null error.

Full Code Example

Below you can find the above method implemented in Regular Flutter Starter App.

import 'package:flutter/material.dart';
import 'dart:io';
import 'dart:typed_data';
import 'dart:ui' as ui;
import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';
import 'package:path_provider/path_provider.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  final GlobalKey genKey = GlobalKey();

  Future<void> takePicture() async {
    RenderRepaintBoundary boundary = genKey.currentContext.findRenderObject();
    ui.Image image = await boundary.toImage();
    final directory = (await getApplicationDocumentsDirectory()).path;
    ByteData byteData = await image.toByteData(format: ui.ImageByteFormat.png);
    Uint8List pngBytes = byteData.buffer.asUint8List();
    File imgFile = File('$directory/photo.png');
    await imgFile.writeAsBytes(pngBytes);
    print(imgFile.path);
  }

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return RepaintBoundary(
      key: genKey,
      child: Scaffold(
        appBar: AppBar(
          title: Text(widget.title),
        ),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Text(
                'You have pushed the button this many times:',
              ),
              Text(
                '$_counter',
                style: Theme.of(context).textTheme.headline4,
              ),
              ElevatedButton(
                  onPressed: () async {
                    await takePicture();
                  },
                  child: Text(
                    "Take Picture",
                  ))
            ],
          ),
        ),
        floatingActionButton: FloatingActionButton(
          onPressed: _incrementCounter,
          tooltip: 'Increment',
          child: Icon(Icons.add),
        ),
      ),
    );
  }
}

End

I think you found something useful here.

I’m always open to suggestions!

Let me know your suggestions and opinions in Twitter DM or drop a mail a mukhtharcm@gmail.com.

If you’re feeling too generous, you can support me through Buy Me a Coffee.

Finally, if you found this helpful, please share this within your reach so that more people can benefit from this. And Follow me on Twitter for getting more posts like these 😉.