refresh_rate
Unlock high-refresh-rate Flutter rendering and verify it with live diagnostics.
Qoder
11 sectionsFlutter Package
Overview
refresh_rate helps Flutter apps request the highest usable display rate on supported devices and prove the result with diagnostics, overlays, and benchmark reports. It exists for product teams that care about scroll feel, gesture response, animation smoothness, and measurable performance. Built on pigeon with typed platform bindings and no hand-written MethodChannel boilerplate.
void main() {
RefreshRate.enable(); // that's it — 120 Hz on a 120 Hz device
runApp(const MyApp());
}Platform Support
Platform coverage is explicit: some operating systems allow refresh-rate control, while others expose query and diagnostics. The package makes those differences visible instead of hiding them behind vague support claims.
- Android 6+ (API 23): Unlock, Query, Overlay, Benchmark
- iOS 15+ (ProMotion): Unlock*, Query, Overlay, Benchmark
- macOS 14+ (Sonoma): Unlock, Query, Overlay, Benchmark
- macOS < 14: Query, Overlay, Benchmark
- Windows: Query, Overlay, Benchmark
- Linux: Query, Overlay, Benchmark
Installation
Add to your pubspec.yaml.
dependencies:
refresh_rate: ^1.0.0iOS Setup
Add to ios/Runner/Info.plist — required for > 60 Hz on iPhones with ProMotion. Without this key, iOS caps your app at 60 Hz even on 120 Hz hardware. The plugin detects this at runtime and prints a console warning if missing. iPad Pro does not need this key.
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>Quick Start
One call during app bootstrap enables peak-rate requests where the platform allows it. On devices that cannot exceed 60 Hz, the OS remains in control and the package still exposes diagnostics.
import 'package:refresh_rate/refresh_rate.dart';
void main() {
WidgetsFlutterBinding.ensureInitialized();
RefreshRate.enable(); // unlocks peak rate on every supported device
runApp(const MyApp());
}Diagnostics
Expose the information QA and performance engineers actually need: current rate, max rate, supported rates, low-power state, thermal state, and platform readiness.
final info = RefreshRate.info; // synchronous cached snapshot
print(info.currentRate); // 120.0
print(info.maxRate); // 120.0
print(info.supportedRates); // [60.0, 90.0, 120.0]
print(info.isVariableRefreshRate); // true (LTPO panel)
print(RefreshRate.isLowPowerMode); // false
print(RefreshRate.thermalState); // ThermalState.nominal
print(RefreshRate.isProMotionReady); // true — plist key + hardware both present
await RefreshRate.refresh(); // reload platform cache
RefreshRate.onChanged.listen((info) {
print('Now running at ${info.currentRate} Hz');
});Advanced Control
Control refresh rates dynamically based on content or user interaction. Supports Android 15+ semantic categories and touch boosts.
RefreshRate.preferMax(); // highest available rate
RefreshRate.preferDefault(); // return to OS default
RefreshRate.matchContent(24.0); // sync to 24 fps video (fixes judder)
RefreshRate.boost(const Duration(seconds: 2)); // temporary spike for gestures
// Android 15+
RefreshRate.category(RateCategory.high); // semantic rate category
RefreshRate.setTouchBoost(true); // OS-managed touch boostDebug Overlay
Drop a live performance HUD into debug builds. The overlay compares FPS against the device's actual target rate, not a hard-coded 60 Hz baseline, and shows build/raster timings, frame budget, and thermal state.
if (kDebugMode) RefreshRate.showOverlay();
RefreshRate.showFPS(); // just the FPS counter
RefreshRate.showHz(); // just the Hz badge
RefreshRate.showOverlay(); // full diagnostic panel
RefreshRate.hideOverlay(); // dismissBenchmark Sessions
Record a named performance window and get a structured report. Sessions automatically exclude app backgrounding, resume warmup, Low Power Mode toggles, and thermal state changes.
final session = RefreshRate.startSession('home_scroll');
// ... user interacts ...
final report = await session.end();
print(report.verdict); // Verdict.good / degraded / poor
print(report.avgFps); // 108.4
print(report.onePercentLowFps); // 87.2
print(report.missedFramePercent); // 3.2%
final json = report.toJson(); // export for CI / QA dashboardsHow it works
Technical breakdown of how peak rates are unlocked on each platform using modern SurfaceFlinger and CADisplayLink APIs.
- Android: Calls SurfaceControl.Transaction.setFrameRate() (34+), preferredRefreshRate (30-33), or preferredDisplayModeId (23-29).
- iOS: Sets CADisplayLink.preferredFrameRateRange with the device max. Validates plist key at runtime.
- macOS: NSView.displayLink with preferredFrameRateRange on 14+. Fallback to CGDisplayCopyDisplayMode for query.
- Windows & Linux: Query-only via native display config APIs (QueryDisplayConfig / gdk_monitor_get_refresh_rate).
Why this exists
High refresh rate is a product-quality detail users can feel but teams often cannot measure. refresh_rate gives Flutter teams a practical control surface today while documenting the platform limits and engine-level gaps it works around.