Production Concerns
Catch every error
Section titled “Catch every error”In production you want to hear about crashes before your users complain. Flutter errors arrive through two channels, and you need both:
void main() { // 1. Errors thrown inside the Flutter framework (build, layout, paint). FlutterError.onError = (details) { FlutterError.presentError(details); reportToCrashService(details.exception, details.stack); };
// 2. Errors outside the framework (async gaps, callbacks) — a guarded zone. runZonedGuarded(() { runApp(const MyApp()); }, (error, stack) { reportToCrashService(error, stack); });}FlutterError.onError catches errors the framework raises; runZonedGuarded catches everything else (uncaught async errors, isolate errors via PlatformDispatcher.instance.onError). You can also replace the red ErrorWidget with a friendly fallback so a single widget failure doesn’t show users a crash screen.
Crash reporting and logging
Section titled “Crash reporting and logging”Wire those handlers to a service — Sentry, Firebase Crashlytics — so real-device crashes come back with a stack trace, device info, and breadcrumbs. Two rules that matter:
- Upload symbols/debug info for release builds (AOT strips symbols; without the mapping, stack traces are unreadable obfuscated addresses).
- Log deliberately.
printis stripped/ignored in release; use a real logger and scrub anything sensitive before it leaves the device.
Profiling jank
Section titled “Profiling jank”Smoothness is a budget: ~16ms per frame for 60fps, ~8ms for 120fps. Miss it and the frame “janks.” Diagnose in profile mode (never debug):
flowchart LR build["build (your widgets)"] --> layout["layout"] --> paint["paint"] --> raster["raster (GPU)"] raster --> budget["must fit ~16ms or the frame janks"]
DevTools’ timeline shows per-frame cost split into UI (Dart: build/layout/paint) and raster (GPU). Common culprits: rebuilding too much (missing const, state too high in the tree), expensive work in build, big un-cached images, and missing RepaintBoundary around constantly-animating subtrees.
Automate the pipeline so quality isn’t a matter of memory: on every push, run flutter analyze, flutter test, then flutter build for each platform, and ship to TestFlight / Play internal tracks. Tools like Codemagic, Fastlane, and GitHub Actions handle the Flutter-specific signing and store upload.
A production checklist:
- Global error handling wired (
FlutterError.onError+runZonedGuarded) to a crash service, with symbols uploaded. - A friendly
ErrorWidgetfallback in release. - Profiled in profile mode; no obvious jank on the target devices.
- Flavors for dev/staging/prod; no secrets in source (
--dart-define/ secure storage). - CI running analyze + tests + builds on every change.