Automatically capture frame drops (jank) and isolate rendering bottlenecks on the UI and Raster threads directly in your dev console
Every Flutter developer knows the pain of subtle micro-stuttering
- A slight visual hiccup when navigating routes.
- Scroll stutters on content-heavy feed lists.
- Sluggish transitions when pulling up custom sheets.
We call this jank when the Flutter rendering pipeline fails to finish producing a frame within the device's hardware refresh budget (typically 16.67ms for 60Hz or 8.33ms for high refresh 120Hz screens). But actively hunting down these frame drops is tedious. You're forced to keep a browser tab open with Flutter DevTools, maintain active Socket connections, or install heavy analytics SDKs that bloat your production bundle.
That's why I built jank_scout. It passively monitors rendering lifecycle timings, analyzes thread bottlenecks, and outputs clean diagnostics directly to your terminal. Best of all, it completely tree-shakes itself out of your production release.
What It Looks Like in Practice
When a rendering timing budget is broken, jank_scout automatically identifies whether the freeze occurred on the UI Thread (CPU) or the Raster Thread (GPU) and displays a diagnostic ASCII report in your console
+----------------------------------------------------------------------+
| TELEMETRY REPORT: [PIPELINE CRITICAL INTERRUPT]
+----------------------------------------------------------------------+
| Target Route: /details
| Budget: 16.67 ms (Target FPS: 60)
| Frame Render Time: 76.50 ms (Overrun: 358.9%, +59.83 ms)
| Thread Breakdowns:
| - UI Thread (CPU Build): 68.20 ms
| - Raster Thread (GPU): 8.30 ms
+----------------------------------------------------------------------+
| Bottleneck Analysis:
| BOTTLENECK: UI Thread (CPU Boundary). Diagnostic: Excessive execution cycle detected on the Dart isolate runtime loop. Remediate by auditing synchronous serialization, unoptimized layout passes, or high-frequency state emissions violating state boundary conditions.
+----------------------------------------------------------------------+
The Architecture Under the Hood
How does it hook into the engine with zero dependencies?
It utilizes Flutter's native SchedulerBinding callback pipeline to receive raw frame timelines asynchronously
SchedulerBinding.instance.addTimingsCallback((List<FrameTiming> timings) {
// Pass timing metrics into the monitor engine
JankScout.processTimings(timings);
});
jank_scout breaks down each frame into
- UI Thread Time: widget tree creation, layout cycles, and drawing calls.
- Raster Thread Time: GPU execution translating the layer tree into pixels.
Safe Production Tree-Shaking
To ensure that the package adds exactly zero bytes and zero background overhead in your production app, jank_scout wraps its entire registration and logging cycles inside assert boundaries and compiler optimizations
assert(() {
// This code is completely stripped out during `flutter build --release`
_initializeFrameMonitor();
return true;
}());
Real-World Case Study: The Synchronous JSON List Freeze
Let's look at a classic local performance bottleneck parsing a massive JSON payload inside your UI widget builds.
X The Bad Code (Stalls UI Thread)
class FeedListScreen extends StatelessWidget {
final String rawJsonString; // Large JSON string from network or local cache
const FeedListScreen({super.key, required this.rawJsonString});
@override
Widget build(BuildContext context) {
// ā CRITICAL JANK: Blocks Dart's main single-threaded event loop
final List<dynamic> decodedData = jsonDecode(rawJsonString);
final List<FeedItem> items = decodedData.map((e) => FeedItem.fromJson(e)).toList();
return ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) => FeedItemCard(item: items[index]),
);
}
}
Since Dart is single-threaded, running jsonDecode synchronously forces the UI thread to freeze while processing. The user experiences a frame drop. jank_scout immediately catches this and flags the UI Thread (CPU Boundary) as the culprit.
The Clean Fix (Background Concurrency)
To restore a butter-smooth 60 or 120 FPS scrolling list, offload the processing to a background thread using Dart isolates.
Option A: Spawning a Temporary Isolate using compute()
Future<List<FeedItem>> loadFeedAsync(String rawJson) async {
// Offloads JSON processing to a temporary worker isolate
return await compute(_parseJsonFeed, rawJson);
}
// Global or top-level function
List<FeedItem> _parseJsonFeed(String rawJson) {
final List<dynamic> decoded = jsonDecode(rawJson);
return decoded.map((e) => FeedItem.fromJson(e)).toList();
}
Option B:Persistent Isolate using Isolate.spawn()
For continuous large payloads, keep a dedicated worker isolate alive to save isolate initialization latency
Future<List<FeedItem>> parseWithManualIsolate(String rawJson) async {
final receivePort = ReceivePort();
final isolate = await Isolate.spawn(
_isolateWorkerEntryPoint,
_IsolatePayload(rawJson, receivePort.sendPort),
);
final List<FeedItem> items = await receivePort.first as List<FeedItem>;
isolate.kill(priority: Isolate.beforeNextEvent);
receivePort.close();
return items;
}
By moving CPU intensive operations off the main isolate, scrolling stays fluid, the event loop remains clear, and jank_scout console output remains silent confirming optimal performance
Get Started in 3 Steps
1. Add dependency
Add the dependency to your pubspec.yaml
dependencies:
jank_scout: ^0.0.3
2. Initialize in main.dart
void main() {
WidgetsFlutterBinding.ensureInitialized();
// Set your target device FPS
JankScout.initialize(targetFps: 60.0);
runApp(const MyApp());
}
3. Add Router Route Tracking
Add the navigation observer in your MaterialApp to associate performance degradation reports with specific screens
MaterialApp(
navigatorObservers: [JankScoutObserver()],
initialRoute: '/',
routes: {
'/': (context) => const HomeScreen(),
'/feed': (context) => const FeedListScreen(),
},
);
Try It Out!
Let's make local Flutter development smoother together!
pub.dev: junk_scout
GitHub: Github Url
Website: My Portfolio
If you find jank_scout helpful, drop a ā on GitHub and a like on pub.dev! Have any questions or feature ideas? Let's discuss in the comments below!
Top comments (0)