Neben den neueren FFI Plugins, die Darts ffi Interface nutzen um Shared Libraries zuladen, bietet es auch eine Schnittstelle über Method Channels. Diese nutzen eine bidirektionale Verbindung...
...in der binäre Daten versendet werden können. Dabei kann mit einem Codec das de/kodieren der Nachricht automatisiert werden.
Flutter selbst ist größtenteils plattformagnostisch mittels C++ und Dart realisiert. Es besteht aus mehreren Schichten, die die Abstraktion vom Betriebssystem gewährleisten. Dabei stellt der Embedder die unterste Schicht und gleichzeitig das Bindeglied zwischen der Engine und den Framework auf der einen Seite und den Betriebssystem auf der anderen Seite da.
Jede Plattform hat ihre eigens auf sie abgestimmte Implementation des Embedder. Die verwendete Sprache ist dabei abhängig vom Betriebssystem. Bei Windows ist und Linux ist C++, auf dem Mac OS ist es Objective C, auf iOS Swift und auf Android Kotlin. Für Web als Zielplattform wird eine Kombination aus Web Assembly und JavaScript genutzt.
Im Normalfall nutzt die erstellte Web App für Desktop Systeme die nach Web Assembly kompilierte Skia Engine und für Mobile Dart2Html. Dadurch kann sich die Anwendung unterschiedlich auf Mobile und Desktop Systemen verhalten. Des weiteren werden nicht alle Features im Webbereich unterstützt. Beispielsweise muss man auf den Einsatz von Shadern verzichten. In den meisten Fällen sollte aber eine Web-Implementation dank Web Assembly keine Probleme darstellen.
Plugin Projekt anlegen
Flutter bietet ein Command Line Interface (CLI) zur Verwaltung des Frameworks und Projekten. Um ein neues Projekt aus dem Plugin Template zu erstellen muss folgender Befehl in die Kommandozeile eingegeben werden.
Als Argument wird der Pfad in dem das Projekt erstellt werden soll erwartet.
Option
Beschreibung
-t plugin
Als Template wurde Plugin gewählt.
--platforms
Die unterstützten Plattformen.
--org
Der Name der Organisation. Für App IDs und Package Namen.
--project-name
Der Name des Projekts.
Damit wird ein Projekt mit allen nötigen Boilerplate Code zur Kommunikation zwischen Dart Kontext und den in nativen Code implementierten Embedder geschaffen. Wir beschränken uns erst einmal auf Windows und werden uns ggf. auch noch die Linux Variante anschauen.
Das Verzeichnis example/ enthält eine sehr ähnlich Verzeichnis - Struktur wie die obere nocheinmal. Allerdings ohne eigenes example/ Verzeichnis.
Da beim Build-Prozess das Plugin in eine Shared Library kompiliert wird, benötigen wir diese Beispiel - Anwendung um die Shared Library später leichter debuggen zu können. Zudem ist nach dem ersten Kompilieren der Example Anwendung, ein neuer Pfad vorhanden. (example/build/windows/)
Dort ist dann ein Visual Studio Projekt drin mit allen nötigen Projekten um die Anwendung erstellen zu können. Dieses werden wir zur Entwicklung des Windows-Parts nutzen.
Die Dart Implementation
Im Ordner native_file_lib/lib/ sind drei Dart Dateien zu finden. Zum einen wäre dort die abstrakte Klasse NativeFileLibPlatform. Sie stellt die Schnittstelle zwischen platformunabhängigen Dart Source Code und plattformabhängigen dar. Sie definiert alle Methoden, die du in deinem Interface zur Verfügung stellen möchtest. Jede einzelne davon wirft allerdings beim Aufruf einen NotImplementedError. Jetzt können wir theoretisch für jede einzelne Plattform eine weitere Klasse mit für die Plattform spezifischen Code aus der NativeFileLibPlatform Klasse ableiten. In der Praxis wird dies an der Stelle aber wohl eher eine Klasse für Web und einzig eine weitere Klasse für jede andere Plattform abgeleitet, da wir uns noch in den Grenzen von Flutter bewegen und dieses, wie Oben schon erwähnt, plattformagnostisch ist. Die Web Schnittstellen und Laufzeitumgebung weicht jedoch von den anderen Implementationen derart ab, dass spezifischer Code nötig sein kann. Dort kann man dann ohne Warnungen zu erhalten JavaScript Interop Module nutzen.
Die NativeFileLibMethodChannel Klasse stellt eine Implementation der abstrakten Klasse NativeFileLibPlatform dar, die auf Method Channel aufsetzt. Ein Method Channel ist ein Kanal eines bidirektionalen binären Stream, der von Flutter automatisch de-/kodiert werden kann aber nicht muss.
Die Dart Schnittstelle bildet letztendlich die Klasse NativeFileLib. In ihr sollte nur plattformunabhängiger Dart Code sein. Wenn für die ausführende Plattform keine Implementation zur Verfügung steht, so wird von der abstrakten Elternklasse die Methode aufgerufen in der dann ein UnimplementedError geworfen wird.
Die native Implementation
Die Sprache hier ist abhängig vom Betriebssystem. Für Windows und Linux ist es C++. Allerdings kann je nach dem welche APIs verwendet werden der Code stark unterscheiden.
Wenn du im Unterordner example/ in der Kommandozeile die Flutter CLI mit folgendem Argumenten ausführen, steht dir im Ordner example/build/windows ein Visual Studio Projekt, das du zur Bearbeitung verwenden kannst, zur Verfügung.
flutter build windows
Mit einem Rechtsklick auf das Projekt native_file_lib_example öffnest du in Visual Studio ein Kontextmenü in dem du den Menüpunkt "Als Startprojekt festlegen" findest. Klick auf ihn. Der Name der Projektmappe native_file_lib_example sollte nun fettgedruckt dargstellt werden. Dadurch kannst du die Anwendung aus Visual Studio nun mit und ohne Debugger starten.
Nun benötigen wir zwei zusätzliche Methoden Signaturen in der Klassen-Header-Datei windows/native_file_lib_plugin.h
Die Implementation beider Methoden ist in ein paar Zeilen erledigt. Wir greifen dafür auf Klassen der stdlib zurück und werden noch im scope des native_file_lib Namespace untergebracht.
Die NativeFileLibPlugin::HandleMethodCall Methode benötigt zwei neue Else-If-Zweige. In diesen wollen wir die Übergabeparameter prüfen, konvertieren, die entsprechende Methode aufrufen und je nach dem ein Ergebnis senden oder einen Fehler melden.
Der gleiche Code sollte ohne größere Umstände auch für Linux kompilieren. Vorrausgesetzt man tauscht die Windows spezifischen Includes mit die Linux Varianten aus.
Flutter Beispiel
Das Standard Beispiel passen wir ein wenig an um unsere Native Implementation testen zu können.