When no plugin exists for a native API or SDK, you have to bridge to Kotlin, Swift or C yourself.
Flutter apps reach native APIs in three ways:
Plugins from pub.dev (camera, url_launcher, shared_preferences...) wrap native code behind a Dart API. Check them first; federated plugins have separate packages per platform.
Platform channels pass asynchronous messages between Dart and the host (Kotlin/Java on Android, Swift/Objective-C on iOS):
- `MethodChannel` — request/response: Dart calls
await channel.invokeMethod<int>('getBattery'); native code registers a handler and replies with a result or an error (which arrives as aPlatformException). - `EventChannel` — a stream of events from native to Dart (sensors, connectivity).
- Messages use the
StandardMessageCodec: null, bools, numbers, strings, byte arrays, lists and maps. - Native handlers run on the platform's main thread by default; don't block it. Use background task queues for heavy work.
Stringly typed channel names and arguments are error-prone, so the Flutter team's Pigeon package generates type-safe Dart, Kotlin and Swift code from a Dart interface definition.
`dart:ffi` calls C functions directly and synchronously, without message passing — ideal for C libraries (SQLite, image codecs, crypto). package:ffigen generates bindings from headers, and build hooks / native assets can compile and bundle the C code. On iOS and macOS, FFI can also reach Objective-C and Swift via ffigen, and Java/Kotlin via jnigen on Android.
Platform views (AndroidView, UiKitView) embed native UI like maps, at some performance cost.
Example
import 'package:flutter/services.dart';
class BatteryService {
static const _channel = MethodChannel('com.example.app/battery');
Future<int?> level() async {
try {
return await _channel.invokeMethod<int>('getBatteryLevel');
} on PlatformException catch (e) {
// e.code / e.message come from the native side's error reply
throw StateError('Battery unavailable: ${e.message}');
} on MissingPluginException {
return null; // no handler registered (e.g. running on web)
}
}
}
// Android (Kotlin), in MainActivity.configureFlutterEngine:
// MethodChannel(flutterEngine.dartExecutor.binaryMessenger, "com.example.app/battery")
// .setMethodCallHandler { call, result ->
// if (call.method == "getBatteryLevel") {
// val bm = getSystemService(BATTERY_SERVICE) as BatteryManager
// result.success(bm.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY))
// } else result.notImplemented()
// }Remember
- MethodChannel for request/response, EventChannel for native event streams
- Use Pigeon for type-safe channels instead of raw strings
- dart:ffi calls C synchronously without channels
Watch out for
- Doing slow work in a native channel handler on the platform main thread, freezing the app.