flutter_refresh_rate_control 0.0.4+1  flutter_refresh_rate_control: ^0.0.4+1 copied to clipboard
flutter_refresh_rate_control: ^0.0.4+1 copied to clipboard
A Flutter plugin that allows you to request high refresh rate mode on Android and iOS devices.
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 device
- currentRefreshRate: Current refresh rate
iOS-specific fields:
- duration: CADisplayLink duration
- timestamp: Current timestamp
- targetTimestamp: Target timestamp
- preferredFrameRateRange: Frame rate range (iOS 15+)
Android-specific fields:
- supportedModes: List of all supported display modes
- currentMode: Current display mode information
- highRefreshRateEnabled: Whether high refresh rate is currently enabled
- androidVersion: Android API level
- deviceModel: 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 exceptionOnUnsupportedPlatformis enabled to get detailed error messages
Unsupported platform behavior:
- By default: Methods return falseor informational data without throwing
- With exceptions enabled: PlatformExceptionwith 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.