flutter_refresh_rate_control
A Flutter plugin that allows you to request high refresh rate mode on Android and iOS devices. This plugin provides a simple API to attempt to enable the highest possible refresh rate for your Flutter application.
Platform Support
Platform | Support | Min Version | Behavior |
---|---|---|---|
Android | ✅ | API 23 (Android 6.0) | Full functionality |
iOS | ✅ | iOS 10.3 | Full functionality |
Web | ⚠️ | N/A | No-op (returns false/empty) |
Windows | ⚠️ | N/A | No-op (returns false/empty) |
macOS | ⚠️ | N/A | No-op (returns false/empty) |
Linux | ⚠️ | N/A | No-op (returns false/empty) |
Note: Unsupported platforms will gracefully return false
for control methods and empty/default values for info methods. You can optionally enable exceptions for unsupported platforms (see Exception Handling).
Features
- Request the highest available refresh rate on supported devices
- Stop high refresh rate mode to return to normal power consumption
- Get detailed information about device refresh rate capabilities
- Cross-platform support for Android and iOS
- Graceful handling of unsupported platforms (no-op by default)
- Optional exception throwing for unsupported platforms
Important Limitations
⚠️ This plugin only attempts to request the highest possible refresh rate. There are several factors that may prevent achieving high refresh rates:
System-Level Limitations
- Low Battery Mode: Most devices disable high refresh rates when battery is low
- Thermal Throttling: Devices may reduce refresh rate when overheating
- Power Management: System may override refresh rate settings to preserve battery
- Display Hardware: Not all devices support high refresh rates
- App Background State: High refresh rates may be disabled when app is not in foreground
Platform-Specific Behavior
While the plugin will not cause problems on unsupported platforms, the behavior is as follows:
- Android: Depends on device manufacturer implementation and Android version (e.g. 90Hz, 120Hz display with "Smooth Display" or manufacturer equivalent enabled)
- iOS: Requires ProMotion displays (iPhone 13 Pro+, iPad Pro models)
- Adaptive Refresh: Some devices use variable refresh rates based on content
Installation
Add this to your package's pubspec.yaml
file:
dependencies:
flutter_refresh_rate_control: ^0.0.3
Then run:
flutter pub get
iOS Setup
For iOS, ensure you have the following in your Info.plist
to allow high refresh rates: (this should already be included by Flutter)
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
Android Setup
For Android, ensure you have the following in your res/values/styles.xml
:
<style name="frameRatePowerSavingsBalancedDisabled">
<item name="android:windowIsFrameRatePowerSavingsBalanced">false</item>
</style>
Note: This disables Adaptive Refresh Rate (ARR). See: Optimize frame rate with adaptive refresh rate for more information.
Usage
Basic Example
import 'package:flutter_refresh_rate_control/flutter_refresh_rate_control.dart';
final _refreshRateControl = FlutterRefreshRateControl();
// Request high refresh rate
try {
bool success = await _refreshRateControl.requestHighRefreshRate();
if (success) {
print('High refresh rate enabled');
} else {
print('Failed to enable high refresh rate');
}
} catch (e) {
print('Error: $e');
}
// Get refresh rate information
try {
Map<String, dynamic> info = await _refreshRateControl.getRefreshRateInfo();
print('Current refresh rate: ${info['currentRefreshRate']}');
print('Maximum refresh rate: ${info['maximumFramesPerSecond']}');
} catch (e) {
print('Error getting refresh rate info: $e');
}
// Stop high refresh rate mode
try {
bool success = await _refreshRateControl.stopHighRefreshRate();
if (success) {
print('Returned to normal refresh rate');
}
} catch (e) {
print('Error: $e');
}
Complete Example
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_refresh_rate_control/flutter_refresh_rate_control.dart';
class RefreshRateScreen extends StatefulWidget {
@override
_RefreshRateScreenState createState() => _RefreshRateScreenState();
}
class _RefreshRateScreenState extends State<RefreshRateScreen> {
final _refreshRateControl = FlutterRefreshRateControl();
bool _isHighRefreshRate = false;
Map<String, dynamic> _info = {};
@override
void initState() {
super.initState();
_loadRefreshRateInfo();
}
Future<void> _loadRefreshRateInfo() async {
try {
final info = await _refreshRateControl.getRefreshRateInfo();
setState(() {
_info = info;
});
} catch (e) {
print('Error loading refresh rate info: $e');
}
}
Future<void> _toggleRefreshRate() async {
try {
bool success;
if (_isHighRefreshRate) {
success = await _refreshRateControl.stopHighRefreshRate();
} else {
success = await _refreshRateControl.requestHighRefreshRate();
}
if (success) {
setState(() {
_isHighRefreshRate = !_isHighRefreshRate;
});
await _loadRefreshRateInfo();
}
} on PlatformException catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Error: ${e.message}')),
);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Refresh Rate Control')),
body: Padding(
padding: EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Status: ${_isHighRefreshRate ? "High" : "Normal"} Refresh Rate'),
SizedBox(height: 16),
ElevatedButton(
onPressed: _toggleRefreshRate,
child: Text(_isHighRefreshRate ? 'Disable High Refresh Rate' : 'Enable High Refresh Rate'),
),
SizedBox(height: 16),
Text('Device Information:'),
..._info.entries.map((e) => Text('${e.key}: ${e.value}')),
],
),
),
);
}
}
Exception Handling
By default, the plugin gracefully handles unsupported platforms by returning false
for control methods and empty/informational data for info methods. However, you can optionally enable exceptions for unsupported platforms:
Default Behavior (No Exceptions)
final _refreshRateControl = FlutterRefreshRateControl();
// On unsupported platforms, these return false/empty without throwing
bool success = await _refreshRateControl.requestHighRefreshRate(); // Returns false
Map<String, dynamic> info = await _refreshRateControl.getRefreshRateInfo();
// Returns: {'platform': 'web', 'supported': false, 'message': '...'}
Enable Exceptions for Unsupported Platforms
final _refreshRateControl = FlutterRefreshRateControl();
// Enable exceptions for unsupported platforms
_refreshRateControl.exceptionOnUnsupportedPlatform = true;
try {
bool success = await _refreshRateControl.requestHighRefreshRate();
} on PlatformException catch (e) {
if (e.code == 'UNSUPPORTED_PLATFORM') {
print('Refresh rate control not supported on ${e.details}');
}
}
When to Use Each Approach
Use default behavior (no exceptions) when:
- You want your app to work seamlessly across all platforms
- You're building a cross-platform app and want to gracefully degrade features
- You prefer to check return values rather than handle exceptions
Use exception mode when:
- You need to explicitly know when a platform is unsupported
- You want to fail fast during development/testing
- You prefer exception-based error handling
API Reference
Properties
exceptionOnUnsupportedPlatform
Controls whether exceptions should be thrown on unsupported platforms.
Type: bool
Default: false
Usage:
final plugin = FlutterRefreshRateControl();
plugin.exceptionOnUnsupportedPlatform = true; // Enable exceptions
bool isEnabled = plugin.exceptionOnUnsupportedPlatform; // Check current state
Methods
requestHighRefreshRate()
Attempts to enable the highest available refresh rate.
Returns: Future<bool>
- true
if successful, false
otherwise
Throws: PlatformException
if an error occurs
stopHighRefreshRate()
Stops high refresh rate mode and returns to normal refresh rate.
Returns: Future<bool>
- true
if successful, false
otherwise
Throws: PlatformException
if an error occurs
getRefreshRateInfo()
Gets detailed information about the device's refresh rate capabilities.
Returns: Future<Map<String, dynamic>>
containing:
Common fields:
maximumFramesPerSecond
: Maximum refresh rate supported by the devicecurrentRefreshRate
: Current refresh rate
iOS-specific fields:
duration
: CADisplayLink durationtimestamp
: Current timestamptargetTimestamp
: Target timestamppreferredFrameRateRange
: Frame rate range (iOS 15+)
Android-specific fields:
supportedModes
: List of all supported display modescurrentMode
: Current display mode informationhighRefreshRateEnabled
: Whether high refresh rate is currently enabledandroidVersion
: Android API leveldeviceModel
: Device manufacturer and model
Throws: PlatformException
if an error occurs
Platform Implementation Details
Android Implementation
The Android implementation uses the following APIs:
- Display.Mode API (API 23+): For setting preferred display mode
- SurfaceControl.setFrameRate() (API 30+): For fine-grained frame rate control
- WindowManager.LayoutParams: For display mode preferences
Key Android APIs:
Display.Mode
SurfaceControl.Transaction.setFrameRate()
WindowManager.LayoutParams.preferredDisplayModeId
Android Documentation:
iOS Implementation
The iOS implementation uses CADisplayLink for high refresh rate control:
- CADisplayLink: For requesting specific frame rates
- CAFrameRateRange (iOS 15+): For fine-grained frame rate control
- UIScreen.maximumFramesPerSecond: For device capability detection
Key iOS APIs:
iOS Documentation:
Troubleshooting
Common Issues
High refresh rate not working:
- Check if platform is supported (Android/iOS only)
- Check if device supports high refresh rates
- Ensure device is not in low battery mode
- Verify app is in foreground
- Check device temperature (thermal throttling)
Methods returning false/empty on supported platforms:
- Ensure proper permissions (should not be needed for this plugin)
- Check platform compatibility
- Verify device display capabilities
- Check if
exceptionOnUnsupportedPlatform
is enabled to get detailed error messages
Unsupported platform behavior:
- By default: Methods return
false
or informational data without throwing - With exceptions enabled:
PlatformException
with code'UNSUPPORTED_PLATFORM'
is thrown - Use
getRefreshRateInfo()
to check platform support status
Testing
Unit Tests
Run the unit tests to verify platform interface and method channel functionality:
flutter test
Integration Tests
Run the integration tests on a real device or simulator to test platform channel communication and FPS monitoring:
# Android device/emulator
flutter test integration_test/plugin_integration_test.dart
# iOS simulator/device
flutter test integration_test/plugin_integration_test.dart
Integration test features:
- Platform channel communication verification
- FPS monitoring (inspired by liblsl_timing FPSoverlay)
- Exception handling validation
- Stress testing with rapid API calls
- Real refresh rate change detection
- Cross-platform compatibility testing
Manual Testing Tips
- Use a device with high refresh rate support
- Ensure device is charged and not in power saving mode
- Keep app in foreground during testing
- Monitor device temperature
- Test on both supported (Android/iOS) and unsupported platforms
- Try enabling/disabling exception mode to test error handling
Performance Considerations
- High refresh rates consume more battery
- May cause device heating during extended use
- Some devices automatically adjust based on content
- Consider user preferences and battery level in your app
Contributing
Contributions are welcome! Please read our contributing guidelines and submit pull requests to our GitHub repository.
License
This project is licensed under the MIT License - see the LICENSE file for details.
Changelog
See CHANGELOG.md for a detailed history of changes.