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);
}
}
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.
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
),
),
);
}
}
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;
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With