Problem
Flutter ist ein Cross Compilation Framework, welches es ermöglicht, Apps für verschiedene Betriebssysteme auf einer gemeinsamen Codebasis zu entwickeln. Dabei muss man manchmal feststellen, dass benötigte Funktionalitäten nicht auf allen Betriebssystemen (oder auf keinem) zur Verfügung stehen. Dies ist insbesondere dann der Fall, wenn man betriebssystemnahe Funktionen benötigt. Für solche Fälle gibt es dann in der Regel ein Flutter–Package, welches die gewünschte Funktionalität zur Verfügung stellt.
Die Erfahrung in unserem Projekt hat gezeigt, dass Packages für Desktop–Betriebssysteme (Windows, macOS, Linux) den Packages für mobile Betriebssysteme (Android, iOS) oftmals in puncto Funktionalität hinterhängen. Es gibt aktuell z.B. kein fehlerfreies und performantes Package für ein Flutter–integriertes Webview–Widget unter Windows.
Lösung
Flutter bietet die Möglichkeit, Plugins zu entwickeln. Mithilfe von Plugins kann betriebssystemspezifischer Code ausgeführt werden, z. B. C++ unter Windows. Dabei kann entweder ein eigenes Plugin entwickelt werden oder man erweitert ein bereits vorhandenes Plugin als Fork.
Das Grundkonzept ist auf allen Betriebssystemen das Gleiche: Es gibt eine Flutter–App (Client), die betriebssystemspezifischen Code auf der Host–Platform ausführen soll. Beide Prozesse sind über den Message Channel, ein asynchrones Message Passing Interface, verbunden.
Message Calls können in beide Richtungen verschickt und asynchron beantwortet werden. Der Message Channel muss auf beiden Seiten eingerichtet und bei der Flutter Engine registriert werden. Dabei einigen sich Client und Host auf einen gemeinsamen Namen für den Message Channel.
Die Dokumentation des Method Channels für die Desktop–Betriebssysteme ist bisher noch sehr dürftig. Die Unit Tests der Flutter Engine (Link unten) bieten hier hilfreiche Beispiele über den korrekten Aufbau eines Method Channels.
Beispiel
import 'package:flutter/services.dart';
...
static const MethodChannel channel = MethodChannel( 'Battery_Plugin');
final int result = await channel.invokeMethod('getBatteryLevel');
#include <flutter/method_channel.h>
...
MethodChannel channel(registrar_windows-›messenger(), "Battery_Plugin",
&StandardMethodCodec: : GetInstance());
channel.SetMethodCallHandler(
[](const flutter: :MethodCall<>& call,
std: :unique_ptr<flutter: :MethodResult<>> result) {
if (call.method_name() == "getBatteryLevel") {
int battery_level = GetBatteryLevel();
if (battery_level != -1) {
result->Success(battery_level);
} else {
result->Error("UNAVAILABLE", "Battery level not available.");
}
} else {
result->NotImplemented);
}});
Weiterführende Aspekte:
-
Flutter FFI (Foreign Function Interface) ermöglicht es, C–APIs direkt aus Dart heraus aufzurufen — ohne den Umweg über Plugins und Message Channels
- Unit Tests der Flutter Engine