battery_ffi 0.0.1 copy "battery_ffi: ^0.0.1" to clipboard
battery_ffi: ^0.0.1 copied to clipboard

PlatformAndroid

A comprehensive Flutter plugin for real-time battery monitoring across platforms using FFI/JNI for optimal performance.

Battery FFI #

Battery FFI Logo

pub package License: MIT

A comprehensive Flutter plugin for real-time battery monitoring across platforms using FFI (Foreign Function Interface) and JNI (Java Native Interface) for optimal performance.

🚀 Features #

  • Real-time battery monitoring with live stream updates
  • Cross-platform support (Android, iOS, Windows, macOS, Linux, Web)
  • High-performance FFI/JNI implementation for direct native access
  • Comprehensive battery data including level, state, health, temperature, voltage
  • Charging information with current, speed, and type detection
  • Data source transparency (API vs estimated values)
  • Robust error handling with detailed recovery suggestions
  • Resource management with automatic cleanup
  • Battery saver mode detection
  • Stream-based architecture for reactive programming

📦 Installation #

Basic Setup #

Add this to your package's pubspec.yaml file:

dependencies:
  battery_ffi: ^0.0.1

Then run:

flutter pub get

Android Setup #

Minimum Requirements:

  • Android API level 21+ (Android 5.0)
  • No additional permissions required
  • Standard Android BatteryManager APIs

Optional: Background Monitoring

For continuous battery monitoring when the app is backgrounded or screen is off:

// Acquire wake lock for continuous monitoring
BatteryFFI.instance.acquireWakeLock();

// Your battery monitoring code here...

// Always release wake lock when done to save battery
BatteryFFI.instance.releaseWakeLock();

⚠️ Important: Wake locks prevent device sleep. Always release them promptly to avoid battery drain.

iOS Setup #

No additional setup required. iOS permissions are handled automatically.

Note: iOS provides limited battery information due to platform restrictions.

Desktop Setup (Windows/macOS/Linux) #

No additional setup required. Desktop platforms provide basic battery information where available.

Web Setup #

Limited support using the deprecated Battery Status API. Consider using alternative approaches for web applications.

🔧 Platform-Specific Configuration #

Android Manifest (Optional) #

If you need to declare wake lock usage for Play Store transparency:

<!-- AndroidManifest.xml -->
<uses-permission android:name="android.permission.WAKE_LOCK" />

<!-- Optional: Declare wake lock usage -->
<application>
    <meta-data
        android:name="android.suppressUnsupportedCompileSdk"
        android:value="34" />
</application>

iOS Info.plist (Optional) #

<!-- ios/Runner/Info.plist -->
<key>UIBackgroundModes</key>
<array>
    <string>processing</string>
</array>

Build Configuration #

Ensure your android/app/build.gradle has:

android {
    defaultConfig {
        minSdkVersion 21
        targetSdkVersion 34
    }
}

📚 Quick Start #

What Does initialize() Do? #

The initialize() method performs platform-specific setup to prepare the battery monitoring system:

On Android:

  • Loads JNI (Java Native Interface) libraries
  • Registers Android BroadcastReceiver for battery change events
  • Sets up JNI callback mechanisms for real-time updates
  • Resets any stale state from previous app runs
  • Initializes wake lock management system

On iOS:

  • Sets up system battery monitoring APIs
  • Configures background processing permissions
  • Prepares for battery state change notifications

On Desktop/Web:

  • Initializes platform-specific battery APIs
  • Sets up fallback mechanisms for unsupported features

Why Required:

  • Ensures proper resource allocation before use
  • Prevents crashes from uninitialized JNI callbacks
  • Sets up platform-specific monitoring infrastructure
  • Must be called before accessing any battery properties

Basic Usage #

import 'package:battery_ffi/battery_ffi.dart';

void main() async {
  // Initialize the battery monitor (REQUIRED)
  await BatteryFFI.instance.initialize();

  // Now you can safely access battery information
  final level = BatteryFFI.instance.batteryLevel;
  print('Battery Level: $level%');

  final state = BatteryFFI.instance.batteryState;
  print('Battery State: ${state?.displayName}');

  // Clean up when done
  BatteryFFI.instance.dispose();
}

Real-time Monitoring with Streams #

import 'package:battery_ffi/battery_ffi.dart';

class BatteryMonitorWidget extends StatefulWidget {
  @override
  _BatteryMonitorWidgetState createState() => _BatteryMonitorWidgetState();
}

class _BatteryMonitorWidgetState extends State<BatteryMonitorWidget> {
  late StreamSubscription<BatteryState> _stateSubscription;
  late StreamSubscription<bool> _chargerSubscription;

  BatteryState? _currentState;
  bool _isCharging = false;

  @override
  void initState() {
    super.initState();
    _initializeBatteryMonitoring();
  }

  Future<void> _initializeBatteryMonitoring() async {
    // Initialize battery monitoring
    await BatteryFFI.instance.initialize();

    // Listen to battery state changes
    _stateSubscription = BatteryFFI.instance.onBatteryStateChanged.listen(
      (state) => setState(() => _currentState = state),
    );

    // Listen to charger connection changes
    _chargerSubscription = BatteryFFI.instance.onChargerConnectionChanged.listen(
      (isConnected) => setState(() => _isCharging = isConnected),
    );
  }

  @override
  void dispose() {
    _stateSubscription.cancel();
    _chargerSubscription.cancel();
    BatteryFFI.instance.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Text('Battery State: ${_currentState?.displayName ?? 'Unknown'}'),
        Text('Charging: ${_isCharging ? 'Yes' : 'No'}'),
        Text('Level: ${BatteryFFI.instance.batteryLevel}%'),
      ],
    );
  }
}

Advanced Battery Information #

import 'package:battery_ffi/battery_ffi.dart';

Future<void> printDetailedBatteryInfo() async {
  final battery = BatteryFFI.instance;

  print('=== Battery Information ===');
  print('Level: ${battery.batteryLevel}%');
  print('State: ${battery.batteryState?.displayName}');
  print('Health: ${battery.batteryHealth?.displayName}');
  print('Temperature: ${battery.batteryTemperature}°C');
  print('Voltage: ${battery.batteryVoltage}V');
  print('Charging Current: ${battery.maxChargingCurrent}mA');
  print('Battery Capacity: ${battery.batteryCapacity}μAh');
  print('Charging Speed: ${battery.chargingSpeed}W');
  print('Charging Type: ${battery.chargingType}');

  // Data source indicators
  print('Current from API: ${battery.isChargingCurrentFromApi}');
  print('Capacity from API: ${battery.isBatteryCapacityFromApi}');

  // Battery saver mode (sync)
  final isSaverMode = battery.isInBatterySaveMode;
  print('Battery Saver Mode: $isSaverMode');
}

Error Handling #

import 'package:battery_ffi/battery_ffi.dart';

Future<void> safeBatteryMonitoring() async {
  try {
    await BatteryFFI.instance.initialize();

    final level = BatteryFFI.instance.batteryLevel;
    print('Battery level: $level%');

  } on BatteryException catch (e) {
    print('Battery monitoring error: ${e.message}');
    print('Error type: ${e.type.description}');

    if (e.recoverySuggestion != null) {
      print('Recovery suggestion: ${e.recoverySuggestion}');
    }

    // Handle specific error types
    switch (e.type) {
      case BatteryErrorType.unsupportedPlatform:
        // Show platform not supported message
        break;
      case BatteryErrorType.permissionDenied:
        // Request permissions or show settings
        break;
      case BatteryErrorType.apiUnavailable:
        // Fall back to alternative implementation
        break;
      default:
        // Handle other errors
        break;
    }
  } catch (e) {
    print('Unexpected error: $e');
  } finally {
    BatteryFFI.instance.dispose();
  }
}

Performance Optimization #

import 'package:battery_ffi/battery_ffi.dart';

class OptimizedBatteryWidget extends StatefulWidget {
  @override
  _OptimizedBatteryWidgetState createState() => _OptimizedBatteryWidgetState();
}

class _OptimizedBatteryWidgetState extends State<OptimizedBatteryWidget> {
  Timer? _rapidUpdateTimer;

  @override
  void initState() {
    super.initState();
    _startOptimizedMonitoring();
  }

  void _startOptimizedMonitoring() {
    // For rapid updates, pause streams to prevent overload
    BatteryFFI.instance.pauseStreams();

    // Use timer for controlled updates
    _rapidUpdateTimer = Timer.periodic(Duration(milliseconds: 100), (_) {
      setState(() {
        // Direct property access is faster than streams for rapid updates
        final level = BatteryFFI.instance.batteryLevel;
        final voltage = BatteryFFI.instance.batteryVoltage;
        // Update UI with latest values
      });
    });
  }

  @override
  void dispose() {
    _rapidUpdateTimer?.cancel();
    BatteryFFI.instance.resumeStreams(); // Resume normal streaming
    BatteryFFI.instance.dispose();
    super.dispose();
  }
}

🎯 API Reference #

BatteryFFI (Main Class) #

The main battery monitoring interface with singleton access.

// Get the platform-specific instance
BatteryFFI battery = BatteryFFI.instance;

// Initialize monitoring
await battery.initialize();

// Clean up resources
battery.dispose();

Synchronous Properties #

Property Type Description
batteryLevel int Current battery level (0-100%)
batteryState BatteryState? Current charging state
batteryHealth BatteryHealth? Battery health status
isChargerConnected bool Whether charger is connected
batteryTemperature double Temperature in Celsius
batteryVoltage double Voltage in Volts
maxChargingCurrent int Maximum charging current (mA)
batteryCapacity int Battery capacity (μAh)
chargingSpeed int Current charging speed (W)
chargingType String Charger type (AC, USB, etc.)

Data Source Indicators #

Property Type Description
isChargingCurrentFromApi bool Whether current value is from API
isChargingSpeedFromApi bool Whether speed value is from API
isBatteryCapacityFromApi bool Whether capacity value is from API

Synchronous Properties #

Property Type Description
isInBatterySaveMode bool Battery saver mode status

Stream Properties #

Stream Type Description
onBatteryStateChanged Stream<BatteryState> Battery state changes
onBatterySaverModeChanged Stream<bool> Battery saver mode changes
onBatteryHealthChanged Stream<BatteryHealth> Battery health changes
onChargerConnectionChanged Stream<bool> Charger connection changes

Utility Methods #

Method Description
pauseStreams() Temporarily pause stream updates
resumeStreams() Resume stream updates
acquireWakeLock() Acquire wake lock for continuous monitoring (Android only)
releaseWakeLock() Release wake lock (Android only)
isWakeLockHeld Check if wake lock is currently held (Android only)

BatteryState Enum #

enum BatteryState {
  charging,     // Battery is charging
  discharging,  // Battery is discharging
  notCharging,  // Plugged in but not charging
  full,         // Battery is fully charged
  unknown       // State is unknown
}

// Usage
final state = BatteryFFI.instance.batteryState;
print(state?.displayName);  // "Charging"
print(state?.iconName);     // "battery_charging_full"

BatteryHealth Enum #

enum BatteryHealth {
  good,              // Battery is healthy
  overheat,          // Battery is overheating
  dead,              // Battery is dead
  overVoltage,       // Over voltage condition
  unspecifiedFailure,// Unspecified failure
  cold,              // Battery is cold
  unknown            // Health status unknown
}

// Usage
final health = BatteryFFI.instance.batteryHealth;
print(health?.displayName);  // "Good"
print(health?.colorName);    // "green"
print(health?.isProblematic); // false

BatteryException #

Custom exception class with detailed error information.

try {
  await BatteryFFI.instance.initialize();
} on BatteryException catch (e) {
  print('Error: ${e.message}');
  print('Type: ${e.type.description}');
  print('Recovery: ${e.recoverySuggestion}');
}

🔧 Best Practices #

1. Initialization and Cleanup #

Always initialize the battery monitor and clean up resources:

@override
void initState() {
  super.initState();
  BatteryFFI.instance.initialize();
}

@override
void dispose() {
  BatteryFFI.instance.dispose();
  super.dispose();
}

2. Stream Management #

Cancel stream subscriptions to prevent memory leaks:

late StreamSubscription _subscription;

@override
void initState() {
  super.initState();
  _subscription = BatteryFFI.instance.onBatteryStateChanged.listen((state) {
    // Handle state changes
  });
}

@override
void dispose() {
  _subscription.cancel();
  super.dispose();
}

3. Error Handling #

Implement comprehensive error handling:

try {
  final level = BatteryFFI.instance.batteryLevel;
} on BatteryException catch (e) {
  // Handle battery-specific errors
  switch (e.type) {
    case BatteryErrorType.permissionDenied:
      // Request permissions
      break;
    case BatteryErrorType.unsupportedPlatform:
      // Show fallback UI
      break;
    default:
      // Handle other errors
      break;
  }
}

4. Performance Considerations #

  • Use streams for real-time updates
  • Use direct property access for one-time reads
  • Pause streams during rapid operations
  • Consider battery impact on mobile devices

5. Platform-Specific Behavior #

Different platforms may provide different levels of information:

  • Android: Full battery information via JNI
  • iOS: Limited information due to platform restrictions
  • Desktop: Basic information where available
  • Web: Limited support (deprecated Battery Status API)

🚨 Common Errors & Troubleshooting #

Android Issues #

"Callback invoked after it has been deleted" Crash

Symptoms:

  • App crashes with SIGABRT on startup or restart
  • Error: "Callback invoked after it has been deleted"
  • Fatal signal 6 in JNI layer

Cause: JNI callbacks being invoked after Dart objects are garbage collected.

Solution: This is automatically handled by the plugin. No user action required.

Prevention:

  • Always call BatteryFFI.instance.dispose() in app lifecycle
  • The plugin automatically resets state on app restart

"JNI ERROR (app bug): accessed stale global reference" Warning

Symptoms:

  • Warning messages in Android logs
  • No functional impact but concerning logs

Cause: Improper JNI reference management.

Solution: This is automatically handled by the plugin. The warning is harmless.

Wake Lock Not Working

Symptoms:

  • acquireWakeLock() doesn't prevent app suspension
  • Background monitoring stops when screen turns off

Cause:

  • Wake lock released too early
  • Android battery optimization killing the app
  • Incorrect wake lock usage

Solutions:

// 1. Ensure proper wake lock lifecycle
BatteryFFI.instance.acquireWakeLock();
// ... do background work ...
BatteryFFI.instance.releaseWakeLock();

// 2. Check if wake lock was acquired
if (BatteryFFI.instance.isWakeLockHeld) {
  print('Wake lock active');
}

// 3. For Android 6.0+, check battery optimization
// Go to Settings > Apps > Your App > Battery > Don't optimize

Battery Data Shows -1 or Incorrect Values

Symptoms:

  • Battery level shows -1
  • Temperature/voltage readings are wrong
  • Charging information unavailable

Cause:

  • Device doesn't support certain battery APIs
  • Battery service temporarily unavailable
  • Android version compatibility issues

Solutions:

// Check data source indicators
final battery = BatteryFFI.instance;
print('Current from API: ${battery.isChargingCurrentFromApi}');
print('Capacity from API: ${battery.isBatteryCapacityFromApi}');

// Handle unavailable data gracefully
final level = battery.batteryLevel;
if (level == -1) {
  print('Battery level unavailable');
} else {
  print('Battery level: $level%');
}

iOS Issues #

Limited Battery Information

Symptoms:

  • Only basic battery level available
  • No temperature, voltage, or detailed charging info

Cause: iOS platform restrictions limit battery API access.

Solution: This is expected behavior. iOS provides minimal battery information for privacy reasons.

// iOS will only provide basic information
final level = BatteryFFI.instance.batteryLevel; // ✅ Works
final temp = BatteryFFI.instance.batteryTemperature; // ❌ Limited/unavailable

General Issues #

"Platform not supported" Error

Symptoms:

  • BatteryException with BatteryErrorType.unsupportedPlatform
  • Plugin fails to initialize

Cause: Running on an unsupported platform or missing platform implementation.

Solutions:

try {
  await BatteryFFI.instance.initialize();
} on BatteryException catch (e) {
  if (e.type == BatteryErrorType.unsupportedPlatform) {
    // Show platform not supported message
    print('Battery monitoring not available on this platform');
    // Provide fallback UI or disable battery features
  }
}

Memory Leaks

Symptoms:

  • App memory usage increases over time
  • Performance degradation during long battery monitoring sessions

Cause: Not properly canceling stream subscriptions or disposing resources.

Solutions:

class BatteryMonitor extends State<StatefulWidget> {
  StreamSubscription? _subscription;

  @override
  void initState() {
    super.initState();
    _subscription = BatteryFFI.instance.onBatteryStateChanged.listen((state) {
      // Handle updates
    });
  }

  @override
  void dispose() {
    _subscription?.cancel(); // Always cancel subscriptions
    BatteryFFI.instance.dispose(); // Clean up resources
    super.dispose();
  }
}

Stream Not Updating

Symptoms:

  • Stream listeners not receiving updates
  • UI not refreshing with new battery data

Cause:

  • Streams paused but not resumed
  • Subscription canceled accidentally
  • Platform-specific stream limitations

Solutions:

// Check if streams are paused
BatteryFFI.instance.resumeStreams();

// Recreate subscription if needed
_subscription = BatteryFFI.instance.onBatteryStateChanged.listen((state) {
  setState(() => _currentState = state);
});

// For Android background limitations
if (Platform.isAndroid) {
  // Use wake locks for background monitoring
  BatteryFFI.instance.acquireWakeLock();
}

Compilation Errors

Symptoms:

  • Build fails with JNI-related errors
  • Missing symbol errors
  • Gradle build issues

Common Causes & Solutions:

  1. Missing JniGen Generation:
# Regenerate JNI bindings
flutter pub run jnigen --config jnigen.yaml
  1. Android NDK Issues:
// android/app/build.gradle
android {
    ndkVersion "25.1.8937393" // Use compatible NDK version
}
  1. ProGuard/R8 Issues:
// android/app/build.gradle
buildTypes {
    release {
        minifyEnabled true
        proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
    }
}
# proguard-rules.pro
-keep class com.example.battery_ffi.** { *; }
-keep class battery_ffi.** { *; }

Performance Issues #

High CPU Usage

Symptoms:

  • Battery drains faster than expected
  • Device gets hot during monitoring
  • App uses excessive CPU

Solutions:

// 1. Pause streams during rapid operations
BatteryFFI.instance.pauseStreams();
// Perform rapid updates...
BatteryFFI.instance.resumeStreams();

// 2. Use direct property access instead of streams for one-time reads
final level = BatteryFFI.instance.batteryLevel; // Faster than streams

// 3. Limit update frequency
Timer.periodic(Duration(seconds: 30), (_) {
  // Update every 30 seconds instead of real-time
  final level = BatteryFFI.instance.batteryLevel;
  setState(() => _batteryLevel = level);
});

UI Freezing

Symptoms:

  • App becomes unresponsive during battery operations
  • Main thread blocked

Cause: Heavy operations on main thread.

Solutions:

// Use async operations off main thread
Future<void> _updateBatteryInfo() async {
  final level = await compute((_) => BatteryFFI.instance.batteryLevel, null);
  setState(() => _batteryLevel = level);
}

❓ FAQ #

Q: Why do I need to call initialize()? #

A: The initialize() method sets up platform-specific resources (JNI on Android, system APIs on other platforms). It ensures proper resource allocation and prepares the monitoring system.

Q: Can I use this plugin in background services? #

A: On Android, yes - use wake locks for continuous monitoring. On iOS and other platforms, background monitoring is limited by platform restrictions.

Q: Why are some values -1 or unavailable? #

A: Some battery information may not be available on certain devices or Android versions. The plugin provides data source indicators to show when values are estimated vs from APIs.

Q: How often do streams update? #

A: Streams update when Android's ACTION_BATTERY_CHANGED broadcast is fired, typically when battery level changes by 1% or charging state changes.

Q: Is this plugin compatible with Flutter's null safety? #

A: Yes, the plugin is fully null-safe and requires Dart 2.12+.

Q: Can I use multiple instances simultaneously? #

A: No, use the singleton BatteryFFI.instance. Multiple instances would conflict with JNI resources.

Q: What happens if I forget to call dispose()? #

A: Resources may not be cleaned up properly, potentially causing memory leaks or continued battery drain from wake locks.

Q: Why are wake locks Android-only? #

A: Wake locks are an Android-specific power management feature. Other platforms handle background processing differently.

Q: Can I monitor battery temperature? #

A: Yes, on supported devices. Check BatteryFFI.instance.batteryTemperature (returns Celsius).

Q: How accurate are the charging current readings? #

A: Accuracy depends on device and Android version. Use isChargingCurrentFromApi to check if the value comes from device APIs or is estimated.

🏗️ Architecture #

The library uses a platform-specific implementation pattern:

BatteryFFI (Abstract Interface)
├── AndroidBatteryMonitor (Android - JNI)
├── StubBatteryMonitor (Fallback)
└── WebBatteryMonitor (Web - Limited)

📋 Platform Support #

Platform Status Implementation Features
Android ✅ Complete JNI with BroadcastReceiver Full battery monitoring
iOS 🚧 Planned IOKit framework Limited monitoring
Windows 🚧 Planned Windows API Basic monitoring
macOS 🚧 Planned IOKit framework Basic monitoring
Linux 🚧 Planned UPower/DBus Basic monitoring
Web ⚠️ Limited Battery Status API Minimal support

🤝 Contributing #

Contributions are welcome! Please feel free to submit a Pull Request.

📄 License #

This project is licensed under the MIT License.

Copyright (c) 2025 Shreeman Arjun Sahu

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

See the LICENSE file for the complete license text.

4
likes
160
points
110
downloads

Publisher

verified publishershreeman.dev

Weekly Downloads

A comprehensive Flutter plugin for real-time battery monitoring across platforms using FFI/JNI for optimal performance.

Homepage
Repository (GitHub)
View/report issues

Topics

#battery #battery-monitoring #power-management #ffi #jni

Documentation

API reference

License

MIT (license)

Dependencies

flutter, jni, plugin_platform_interface

More

Packages that depend on battery_ffi

Packages that implement battery_ffi