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

TaskFirst choiceFallback
Camera and filesA maintained pluginPlatform view
Background locationA plugin plus native configCustom service
A proprietary SDKThin method channelFederated plugin
High-frequency sensor streamEventChannelPlatform 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.

Local persistence: shared_preferences, sqflite, Drift and Hive CI/CD, flavors and store deployment

Last refreshed 2026-09-18.