firebench
⚠️ Alpha: API may change before 1.0.
Sentry-style automatic per-screen performance tracing for Flutter, reported to the free Firebase Performance backend.
Firebase Performance cannot trace individual Flutter screens (the whole app is one native view). firebench closes that gap with a drop-in NavigatorObserver modeled on SentryNavigatorObserver, while sending everything to Firebase so there is no paid SaaS.
What it captures (per screen visit)
One Firebase Trace named screen_<RouteName> with:
| Metric (int) | Meaning |
|---|---|
ttid_ms |
Time to initial display — push to first frame (automatic) |
ttfd_ms |
Time to full display — push to a fully-displayed signal via currentDisplay() / FirebenchDisplayWidget (opt-in, manual) |
slow_frames |
Frames slower than slowFrameThreshold but faster than frozenFrameThreshold (default: auto from display refresh rate, 16ms fallback). Frozen frames are counted separately, not here. |
frozen_frames |
Frames slower than frozenFrameThreshold (default 700ms) |
total_frames |
Frames rendered while the screen was active |
max_frame_ms |
Slowest single frame |
frames_delay_ms |
Cumulative jank over the slow-frame budget |
Attributes: route, previous_route, display_complete.
App start (_app_start) and HTTP/S traces are captured automatically by firebase_performance itself — no extra setup.
Setup
await Firebase.initializeApp();
await Firebench.init(
config: const FirebenchConfig(enableTtfd: true),
);
MaterialApp(
navigatorObservers: [FirebenchNavigatorObserver()],
);
GoRouter:
GoRouter(observers: [FirebenchNavigatorObserver()]);
Time to full display (opt-in)
For screens that load async content, report when fully rendered. Capture the display handle at screen init (it binds to that screen) and report on the captured handle — never resolve it inside the async callback, or a report that arrives after the user navigated away would land on the wrong screen:
// Capture in initState before the async work, report after:
final display = Firebench.instance.currentDisplay();
await repo.load();
display?.reportFullyDisplayed();
Reporting on a screen the user already left is dropped, never reattributed. The report is also idempotent — calling it twice records a single TTFD.
Or wrap the screen and let stateless children auto-report:
FirebenchDisplayWidget(child: MyScreen());
Custom screen metrics and attributes
Tag the active screen's trace with your own metrics and attributes to segment
performance by cohort (reserved keys route, previous_route,
display_complete always win):
final display = Firebench.instance.currentDisplay();
display?.setMetric('list_size', items.length);
display?.putAttribute('user_tier', tier);
Manual traces
final data = await Firebench.instance.trace('load_dashboard', () => repo.fetch());
final t = Firebench.instance.startTrace('checkout_validation');
t.setMetric('items', cart.length);
await t.stop();
Configuration
FirebenchConfig — enabled, traceInDebug (default false; frame timings are unreliable in debug), tracePrefix, autoFinishAfter, enableTtfd, enableFrameTracking, slowFrameThreshold, frozenFrameThreshold, ignoreRoutes, routeNameExtractor, tagManualTracesWithRoute.
Notes
- Run in profile/release mode for meaningful frame metrics. Tracing is disabled in debug unless
traceInDebug: true. - Firebase metric values are integers, so durations are milliseconds (sub-millisecond timings are floored).
- Each observer tracks only its own navigator. Add a separate
FirebenchNavigatorObserver()to a nested navigator to trace it independently; concurrent screen traces coexist. Frame metrics follow the most recently pushed screen, so per-screen frame counts can be approximate while a nested screen is open. - After a pop, tracking resumes on the next push (the revealed screen is not re-traced).
- Unnamed routes (dialogs, bottom sheets) neither start nor end screen traces — their frames count toward the screen beneath them.
pushReplacementfinalizes the replaced screen's trace even when the new route is unnamed.- All Firebase failures are caught and logged in debug; the SDK never throws into the host app.
- Unlike Sentry, a route pushed from an ignored route is still traced;
ignoreRoutesonly filters destinations.
Contributing
Contributions are welcome — see CONTRIBUTING.md.
License
MIT — see LICENSE.
Libraries
- firebench
- Automatic per-screen performance tracing for Flutter, reported to the free Firebase Performance backend.