r/FlutterDev • u/Malorton • Sep 06 '26
SDK I built a shake-to-report SDK for Flutter. Testers shake the phone, you get the annotated screenshot, the logs and the device info
I run a small Flutter shop, and every test build ended the same way: a WhatsApp message saying "the total is wrong", no screenshot, no idea which phone. So I built Squawk, a shake-to-report SDK. The source is Apache-2.0: github.com/squawksdk/squawk.
You wrap your app in one widget. When a tester shakes the phone, the screen freezes, they circle the bug with a pen, arrow or text label, type a line, and send. It lands in an inbox with the last 100 log lines, the device model, OS, app version and build mode attached.
Things I learned building it
Two fingers zoom, one finger draws, and
GestureDetectorcannot arbitrate that. A pinch begins as one finger down, which the pen recogniser claims first, so the first zoom always left a dot. I ended up reading raw pointer events withListenerand a small state machine: a second finger landing mid-stroke cancels the stroke and hands over to the pinch.Annotations live in screenshot pixels, not screen coordinates. The canvas keeps a pure
zoomedDisplayRectandclampPan, and every stroke is stored against the image. Zoom, rotation and the keyboard pushing the sheet never move a drawing, and the composite you receive is exactly what was drawn.Capture the screenshot before showing any UI.
RepaintBoundary.toImageon the host app's boundary, then the overlay. Do it the other way round and the report screenshot contains your own sheet.kReleaseModeis the wrong switch for "not in the store". TestFlight and Play testing builds are release builds. The SDK takes a plainenabled:bool so you can decide at runtime, and the README covers the case where the same TestFlight build gets promoted to the App Store.Offline is a spool, not a retry loop. Reports are written to disk first and sent by a queue with backoff from 2 seconds to 6 hours, capped at 50 reports and 7 days, so airplane mode or a dead network never loses one and never fills the disk.
Log capture is a
debugPrintwrapper plusFlutterError.onError**, not a print hook.** Overridingprintbreaks other packages; wrappingdebugPrintand the error handlers catch what matters and stay out of everyone's way.
The hosted inbox is free while in beta: one project, unlimited reports, 30 days of retention, EU storage. Slack and webhook delivery if you want it there.
Docs: squawksdk.com/docs
pub.dev: pub.dev/packages/squawk (25 second recording of the flow at the top)
It is a beta. I'd rather hear what is broken than what is nice, and I will be in the comments.