Platform integration: plugins, permissions and channels
Evaluate a pub.dev package before depending on it, request permissions at the right moment, and call native code through method and event channels.
Choosing and using plugins
- Check the pub score, the last publish date, and whether the maintainer responds to issues. A package that has not moved in two years is a migration you will own.
- Check the platform support matrix: a plugin that claims six platforms rarely implements all of them well.
- Prefer a plugin that exposes a Dart interface you can wrap, so the dependency stays in one file of your codebase.
- Read the Android manifest additions it requires. Many camera and location plugins need permissions you must also declare yourself.
import 'package:geolocator/geolocator.dart';
Future<Position?> currentPosition() async {
final enabled = await Geolocator.isLocationServiceEnabled();
if (!enabled) return null;
var permission = await Geolocator.checkPermission();
if (permission == LocationPermission.denied) {
permission = await Geolocator.requestPermission();
}
if (permission == LocationPermission.denied ||
permission == LocationPermission.deniedForever) {
return null; // the UI should explain and offer Settings
}
return Geolocator.getCurrentPosition(
locationSettings: const LocationSettings(accuracy: LocationAccuracy.high),
);
}Method and event channels
class BatteryChannel {
static const _method = MethodChannel('app/battery');
static const _events = EventChannel('app/battery/events');
Future<int> level() async {
final value = await _method.invokeMethod<int>('level');
return value ?? -1;
}
Stream<int> watch() => _events
.receiveBroadcastStream()
.map((event) => event as int);
}class MainActivity : FlutterActivity() {
private val methodChannel = "app/battery"
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, methodChannel)
.setMethodCallHandler { call, result ->
when (call.method) {
"level" -> result.success(readLevel())
else -> result.notImplemented()
}
}
}
}💡
Channels are asynchronous and can be called before the engine is ready. Handle
MissingPluginException in Dart — it is what you get when the native side is not registered, usually because the app was hot-restarted after adding a plugin.When to drop to native code
| Task | First choice | Fallback |
|---|---|---|
| Camera and files | A maintained plugin | Platform view |
| Background location | A plugin plus native config | Custom service |
| A proprietary SDK | Thin method channel | Federated plugin |
| High-frequency sensor stream | EventChannel | Platform view for rendering |
Wrap every channel behind a Dart interface with a fake implementation. That single decision lets widget tests run with no device and keeps platform code out of your business logic.
FAQ
Why does the app crash right after adding a permission?
Usually a missing declaration in
AndroidManifest.xml or Info.plist. Requesting a permission the operating system does not know about throws immediately.Do I need a federated plugin?
Only if you intend to publish it or support many platforms. For app-internal native code a plain method channel in the app project is simpler and faster to maintain.
Related
Local persistence: shared_preferences, sqflite, Drift and Hive CI/CD, flavors and store deployment
Last refreshed 2026-09-18.