Flutter integration with native code (Android and iOS) enables you to use platform-specific APIs and functions that are not immediately accessible via Flutter. This technique entails developing platform-specific code (Kotlin/Java for Android, Swift/Objective-C for iOS) and then executing it from Flutter over platform channels.
Here’s a step-by-step approach for Flutter integration and native code:
1. Setting Up a Platform Channel
A platform channel facilitates communication between Flutter and native code.
Flutter side:
- Define a MethodChannel:
import 'package:flutter/services.dart';
class NativeIntegration {
static const platform = MethodChannel('com.example/native');
// Method to invoke native code
static Future<String> invokeNativeMethod(String method, [dynamic arguments]) async {
try {
final result = await platform.invokeMethod(method, arguments);
return result;
} on PlatformException catch (e) {
print("Failed to invoke: '${e.message}'.");
return "Error: ${e.message}";
}
}
}
- Invoke the Native Method:
// Example usage
void _getBatteryLevel() async {
final batteryLevel = await NativeIntegration.invokeNativeMethod('getBatteryLevel');
print("Battery level: $batteryLevel");
}
Android Side:
- Set Up the MethodChannel in the MainActivity:
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
class MainActivity: FlutterActivity() {
private val CHANNEL = "com.example/native"
override fun configureFlutterEngine(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 batteryLevel: Int
val batteryManager = getSystemService(BATTERY_SERVICE) as BatteryManager
batteryLevel = batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY)
return batteryLevel
}
}
IOS Side:
- Set Up the MethodChannel in AppDelegate:
import UIKit
import Flutter
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
let controller = window?.rootViewController as! FlutterViewController
let batteryChannel = FlutterMethodChannel(name: "com.example/native",
binaryMessenger: controller.binaryMessenger)
batteryChannel.setMethodCallHandler { (call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in
if call.method == "getBatteryLevel" {
self.receiveBatteryLevel(result: result)
} else {
result(FlutterMethodNotImplemented)
}
}
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
private func receiveBatteryLevel(result: FlutterResult) {
let device = UIDevice.current
device.isBatteryMonitoringEnabled = true
if device.batteryState == UIDevice.BatteryState.unknown {
result(FlutterError(code: "UNAVAILABLE",
message: "Battery level not available.",
details: nil))
} else {
result(Int(device.batteryLevel * 100))
}
}
}
2. Handling Arguments and Returning Results
- Passing Arguments: You can pass arguments from Flutter to native code by using the invokeMethod function.
final result = await
NativeIntegration.invokeNativeMethod('methodName', {'arg1': 'value1'});
- Returning Results: Native code can send results back to Flutter. This can refer to a simple data type (such as a text or integer) or a more complicated data structure (such as a map).
3. Handling Errors
On the Flutter side, use PlatformException to catch and handle exceptions.
try {
final result = await NativeIntegration.invokeNativeMethod('methodName');
} on PlatformException catch (e) {
print("Failed to invoke: '${e.message}'.");
}
Native Side: Use result.error to return errors to Flutter.
result.error("ERROR_CODE", "Error message", null)
Example Use Cases
- Accessing Platform-Specific APIs: You can use platform channels to manage Bluetooth, access device sensors, and so on.
- Interacting with Existing Native Code: Flutter allows you to call existing native code (libraries or modules).
- Optimizing performance-critical code: Write performance-critical code in a native language and call it with Flutter.
In conclusion, Flutter integration with native code via platform channels is a powerful technique that allows you to leverage platform-specific functionalities and APIs that are not directly accessible through Flutter. By setting up a communication bridge between Flutter and native platforms like Android and iOS, you can invoke native methods, handle arguments, return results, and manage errors efficiently. This approach is beneficial for accessing platform-specific features, interacting with existing native code, and optimizing performance-critical tasks, ensuring your Flutter app can fully utilize the underlying capabilities of the device