flutter_refresh_rate_control 0.0.1
flutter_refresh_rate_control: ^0.0.1 copied to clipboard
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 [...]
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.1
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.