capture static method
Future
capture(
- GlobalKey<
State< key, {StatefulWidget> > - ScreenshotConfig config = const ScreenshotConfig(),
Captures a widget screenshot using the provided key.
Returns String (base64), Uint8List (bytes), or File based on config.
Throws ScreenshotException on failure.
Implementation
static Future<dynamic> capture(
GlobalKey key, {
ScreenshotConfig config = const ScreenshotConfig(),
}) async {
try {
if (config.resultType == ScreenshotResultType.download &&
(config.fileName == null || config.fileName!.isEmpty)) {
throw ScreenshotException(
'fileName is required when resultType is download',
);
}
// Get render object before any async operations to avoid BuildContext issues
final context = key.currentContext;
if (context == null) {
throw ScreenshotException(
'Widget is not yet rendered. Make sure the GlobalKey is attached to a widget in the tree.',
);
}
final renderObject = context.findRenderObject();
if (renderObject == null) {
throw ScreenshotException(
'RenderObject not found. Make sure the widget is visible.',
);
}
if (renderObject is! RenderRepaintBoundary) {
throw ScreenshotException(
'The widget must be wrapped in a RepaintBoundary.',
);
}
if (config.captureDelay != null) {
await Future.delayed(config.captureDelay!);
}
// Wait for the rendering pipeline to fully complete
final boundary = renderObject;
await WidgetsBinding.instance.endOfFrame;
// Capture and immediately extract byte data, then dispose the native image
final image = await boundary.toImage(pixelRatio: config.pixelRatio);
final ByteData? byteData;
try {
byteData = await image.toByteData(
format: config.format == ScreenshotFormat.png
? ui.ImageByteFormat.png
: ui.ImageByteFormat.rawRgba,
);
} finally {
image.dispose();
}
if (byteData == null) {
throw ScreenshotException('Failed to convert image to byte data');
}
// Use precise offset and length to avoid reading beyond actual data
final buffer = byteData.buffer.asUint8List(
byteData.offsetInBytes,
byteData.lengthInBytes,
);
if (kDebugMode && config.shouldShowDebugLogs) {
debugPrint('Screenshot captured: ${buffer.length} bytes');
}
switch (config.resultType) {
case ScreenshotResultType.base64:
// Offload base64 encoding to a separate isolate on native platforms;
// on web, isolates aren't supported so we chunk-encode to avoid
// blocking the main thread (which would freeze animations).
final base64String = kIsWeb
? await _chunkedBase64Encode(buffer)
: await compute(base64Encode, buffer);
if (kDebugMode && config.shouldShowDebugLogs) {
debugPrint('Base64 length: ${base64String.length}');
}
return base64String;
case ScreenshotResultType.bytes:
return buffer;
case ScreenshotResultType.download:
// For download type, return bytes and let downloadScreenshot handle the file saving
return buffer;
}
} catch (e) {
if (kDebugMode) {
debugPrint('Screenshot capture failed: $e');
}
if (e is ScreenshotException) {
rethrow;
}
throw ScreenshotException('Failed to capture screenshot', e);
}
}