🔌 Integrating Native Android Features in Flutter – My Real Project Example

🔌 Integrating Native Android Features in Flutter – My Real Project Example

“Flutter is cross-platform, but what if you need something that only Android can do?”

That’s the exact question I faced in one of my recent Flutter projects. Our app needed to monitor battery level and trigger actions when the device was charging — something that’s not directly available via Flutter packages.

So, I rolled up my sleeves and integrated native Android code using platform channels. If you're wondering how to bridge that gap between Flutter and native Android, here’s how I did it — step-by-step.


🚧 The Challenge

Flutter is great, but some device-level features (like battery info, sensors, or background services) still require native code.

We needed to:

  • Read the battery percentage
  • Detect whether the device was charging

There were some packages, but they didn’t cover all our needs — and we wanted more control and efficiency.

🧠 The Solution: Platform Channels

Flutter provides a powerful way to communicate between Dart and native code through platform channels.

📱 Android Native Code (Kotlin)

Step 1: Create a MethodChannel in MainActivity

class MainActivity: FlutterActivity() {
    private val CHANNEL = "battery_channel"

    override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
        super.configureFlutterEngine(flutterEngine)
        
        MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler {
            call, result ->
            if (call.method == "getBatteryLevel") {
                val batteryLevel = getBatteryLevel()

                if (batteryLevel != -1) {
                    result.success(batteryLevel)
                } else {
                    result.error("UNAVAILABLE", "Battery level not available.", null)
                }
            } else {
                result.notImplemented()
            }
        }
    }

    private fun getBatteryLevel(): Int {
        val batteryManager = getSystemService(Context.BATTERY_SERVICE) as BatteryManager
        return batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY)
    }
}        

💻 Flutter (Dart) Side

Step 2: Invoke Native Code from Dart

import 'package:flutter/services.dart';

class BatteryService {
  static const platform = MethodChannel('battery_channel');

  Future<int?> getBatteryLevel() async {
    try {
      final batteryLevel = await platform.invokeMethod<int>('getBatteryLevel');
      return batteryLevel;
    } catch (e) {
      print("Failed to get battery level: $e");
      return null;
    }
  }
}        

✅ How I Used It in UI

final batteryService = BatteryService();

ElevatedButton(
  onPressed: () async {
    final level = await batteryService.getBatteryLevel();
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(content: Text("Battery Level: $level%")),
    );
  },
  child: Text("Check Battery"),
)        

🧪 Bonus: Testing & Safety

  • Always wrap native calls in try-catch.
  • Use null-aware handling in Dart.
  • You can mock the native method for unit testing using MethodChannel.setMockMethodCallHandler().

🎯 Takeaway

Flutter doesn’t limit you — it empowers you to tap into native layers when needed. Whether it’s camera, background tasks, sensors, or deep device features, platform channels let you have the best of both worlds.

If you’re building a real-world app, don’t hesitate to dive into native when Flutter packages fall short. It’s not as scary as it seems!

🔁 Over to you: Have you integrated native code in your Flutter project? Would you like me to share the iOS version too?

Follow me for more real-world Flutter experiences! 🚀

To view or add a comment, sign in

More articles by Bunty Kumar

Explore content categories