Flutter Package

refresh_rate

Unlock high-refresh-rate Flutter rendering and verify it with live diagnostics.

Q

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.

Dart
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.

Dart
dependencies:
  refresh_rate: ^1.0.0

iOS 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.

Dart
<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.

Dart
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.

Dart
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.

Dart
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 boost

Debug 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.

Dart
if (kDebugMode) RefreshRate.showOverlay();

RefreshRate.showFPS(); // just the FPS counter
RefreshRate.showHz(); // just the Hz badge
RefreshRate.showOverlay(); // full diagnostic panel
RefreshRate.hideOverlay(); // dismiss

Benchmark 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.

Dart
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 dashboards

How 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.