Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unable to take screenshot in flutter

Tags:

flutter

package:flutter/src/rendering/proxy_box.dart': Failed assertion: line 2813 pos 12: '!debugNeedsPaint': is not true.

I am trying to take screenshot in flutter, but I am getting exception. I visited many links but nothing worked.

Future<Uint8List> _capturePng() async {
    try {
        print('inside');
        RenderRepaintBoundary boundary = _globalKey.currentContext.findRenderObject();
        ui.Image image = await boundary.toImage(pixelRatio: 3.0);
        ByteData byteData = await image.toByteData(format: ui.ImageByteFormat.png);
        var pngBytes = byteData.buffer.asUint8List();
        var bs64 = base64Encode(pngBytes);
        print(pngBytes);
        print(bs64);
        setState(() {});
        return pngBytes;
    } catch (e) {
        print(e);
    }
}
like image 339
xbadal Avatar asked Aug 25 '19 09:08

xbadal


People also ask

How do you secure the Flutter app?

Since user data is highly sensitive, you cannot store it like common preferences. There are various ways to store data locally in Flutter. However, our safest bet would be to use secure native storage such as IOS Keychain Services and Android KeyStore System.


2 Answers

You can find official toImage example here. But it looks like it doesn't work without delay between button tap and toImage call.

There is an issue in the official repository: https://github.com/flutter/flutter/issues/22308

The cause for this: your tap initializes animation for a button and RenderObject.markNeedsPaint gets called recursively including parents, so you should wait while debugNeedsPaint will be false again. toImage function just throws assertion error in this case:

  Future<ui.Image> toImage({ double pixelRatio = 1.0 }) {
    assert(!debugNeedsPaint);
    final OffsetLayer offsetLayer = layer;
    return offsetLayer.toImage(Offset.zero & size, pixelRatio: pixelRatio);
  }

https://github.com/flutter/flutter/blob/f0553ba58e6455aa63fafcdca16100b81ff5c3ce/packages/flutter/lib/src/rendering/proxy_box.dart#L2857

  bool get debugNeedsPaint {
    bool result;
    assert(() {
      result = _needsPaint;
      return true;
    }());
    return result;
  }

https://github.com/flutter/flutter/blob/0ca5e71f281cd549f1b5284e339523ad93544c60/packages/flutter/lib/src/rendering/object.dart#L2011

Actually assert function is used only in development, so as you can see you will not have the trouble with the error in production. But I don't know what kind of troubles you can get instead, probably no ).

The next code is not great but it works:

class _MyHomePageState extends State<MyHomePage> {
  GlobalKey globalKey = GlobalKey();

  Future<Uint8List> _capturePng() async {
    RenderRepaintBoundary boundary = globalKey.currentContext.findRenderObject();

    if (boundary.debugNeedsPaint) {
      print("Waiting for boundary to be painted.");
      await Future.delayed(const Duration(milliseconds: 20));
      return _capturePng();
    }

    var image = await boundary.toImage();
    var byteData = await image.toByteData(format: ImageByteFormat.png);
    return byteData.buffer.asUint8List();
  }

  void _printPngBytes() async {
    var pngBytes = await _capturePng();
    var bs64 = base64Encode(pngBytes);
    print(pngBytes);
    print(bs64);
  }

  @override
  Widget build(BuildContext context) {
    return RepaintBoundary(
      key: globalKey,
      child: Center(
        child: FlatButton(
          color: Color.fromARGB(255, 255, 255, 255),
          child: Text('Capture Png', textDirection: TextDirection.ltr),
          onPressed: _printPngBytes
        ),
      ),
    );
  }
}
like image 153
exxbrain Avatar answered Sep 21 '22 08:09

exxbrain


Your code is already fine, you shouldn't face any problem in release mode because from docs:

debugNeedsPaint: Whether this render object's paint information is dirty.

This is only set in debug mode. In general, render objects should not need to condition their runtime behavior on whether they are dirty or not, since they should only be marked dirty immediately prior to being laid out and painted.

It is intended to be used by tests and asserts.

It is possible (and indeed, quite common) for debugNeedsPaint to be false and debugNeedsLayout to be true. The render object will still be repainted in the next frame when this is the case, because the markNeedsPaint method is implicitly called by the framework after a render object is laid out, prior to the paint phase.

However, if you still need a solution, you can try this:

Future<Uint8List> _capturePng() async {
  try {
    print('inside');
    RenderRepaintBoundary boundary = _globalKey.currentContext.findRenderObject();
    
    // if it needs repaint, we paint it.
    if (boundary.debugNeedsPaint) {
      Timer(Duration(seconds: 1), () => _capturePng());
      return null;
    }
    
    ui.Image image = await boundary.toImage(pixelRatio: 3.0);
    ByteData byteData = await image.toByteData(format: ui.ImageByteFormat.png);
    var pngBytes = byteData.buffer.asUint8List();
    var bs64 = base64Encode(pngBytes);
    print(pngBytes);
    print(bs64);
    setState(() {});
    return pngBytes;
  } catch (e) {
    print(e);
    return null;
  }
}
like image 44
CopsOnRoad Avatar answered Sep 22 '22 08:09

CopsOnRoad