Wer plattformübergreifend Apps mit Flutter programmieren wollte, musste sich bisher mit der Programmiersprache Dart anfreunden. Mit Flet gibt es jetzt eine alternative Portierung für Python.
Mit dem beliebten Framework Flutter [1] von Google lassen sich Apps für den Desktop, das Smartphone und das Web auf Grundlage einer einzigen Codebasis programmieren. Bisher musste man dafür aber die Sprache Dart lernen. Das neue Open-Source-Framework Flet [2] erlaubt es jetzt, statt Dart das gängigere Python zu verwenden. Die resultierenden Apps sehen ebenso ansprechend aus wie Flutter-Apps, da Flet vollständig auf Flutter basiert und auf dieselben Widgets zurückgreift. Um Flet einzusetzen, genügen ein paar Grundkenntnisse in Python.
Flutter selbst kam bereits vor einiger Zeit im Linux-Magazin zur Sprache [3]. Als Beispiel diente damals eine App, die einen Wetterbericht ausgab. Die aktuellen Wetterdaten stammten vom Onlinedienst OpenWeatherMap. Die App bereitete sie auf und zeigte sie in einem kleinen Dialog an. Genau dieselbe Anwendung muss auch diesmal wieder herhalten, nun aber mit Flet und Python. So können Sie beide Ansätze leicht vergleichen.
TIPP
Ein PDF des Flutter-Artikels aus LM 03/2022 sowie den Python-Code für das Projekt finden Sie bei Interesse im Download-Bereich zu diesem Artikel.
Vorbereitungen
Um mit Flet programmieren zu können, benötigen Sie Python. Die meisten Linux-Distributionen bringen es bereits mit, sodass Sie dafür in der Regel nichts zu tun brauchen. Die installierte Python-Version überprüfen Sie, indem Sie auf der Kommandozeile »python3 –version« ausführen. Sollte Python nicht installiert sein, ziehen Sie es unter Debian, Ubuntu und deren Derivaten schnell nach (Listing 1, erste Zeile).
Um komfortabel arbeiten zu können, sollten Sie einen passenden Editor zur Hand haben. Empfehlenswert ist das kostenfreie Visual Studio Code (VS Code [4]) von Microsoft. Diese IDE installieren Sie unter Ubuntu beispielsweise als Snap-Paket (Zeile 3). Vorher richten Sie gegebenenfalls noch Snap selbst ein (Zeile 2).
Stehen Sie Canonicals Paketformat Snap eher skeptisch gegenüber oder nutzen Sie Linux Mint, spielen Sie VS Code stattdessen als Flatpak ein, entweder über die Softwareverwaltung der genutzten Distribution oder via Flathub. Ein Vorteil der Anwendungen im Snap- oder Flatpak-Format: Sie sind oft aktueller als die Versionen aus den Distro-Repos.
Alternativ beziehen Sie VS Code über die Paketquellen der jeweiligen Distribution. Eine weitere Möglichkeit besteht darin, ein DEB-Paket für Debian- und Ubuntu-basierte Systeme oder ein RPM-Paket für Red-Hat- und Fedora-basierte Systeme von der VS-Code-Homepage herunterzuladen.
Nützlich ist zudem die Installation der VS-Code-Extension für Python. Dazu starten Sie VS Code, drücken [Strg]+[Umschalt]+[X] und geben “Python” ins Suchfeld ein. Die weiteren Schritte sind selbsterklärend.
Listing 1
Flet und Tools einrichten
$ sudo apt-get install python3 $ sudo apt install snapd $ sudo snap install code --classic $ sudo apt install python3-venv $ sudo apt install python3-pip $ mkdir weather-app $ cd weather-app $ python3 -m venv .venv $ source .venv/bin/activate $ pip install flet
Flet-Projekt
Als Erstes legen Sie am besten eine virtuelle Python-Umgebung an, damit sich verschiedene Python-Versionen und -Module nicht ins Gehege kommen. Falls noch nicht vorhanden, spielen Sie dafür das Paket python3-venv ein (Listing 1, Zeile 4) und installieren den Python-Paketmanager Pip (Zeile 5).
Dann erstellen Sie ein Verzeichnis für das Flet-Projekt und wechseln dorthin (Listing 1, Zeile 6 und 7). Hier richten Sie eine virtuelle Umgebung namens ».venv« ein und aktivieren sie (Zeile 8 und 9). Dass das geklappt hat, erkennen Sie daran, dass nun der Name der Umgebung am Anfang des Prompts steht. Zu guter Letzt installieren Sie Flet in der virtuellen Umgebung (Zeile 10).
Sollte das zu einer Fehlermeldung führen, versuchen Sie als Workaround, mit »pip install flet-desktop-light« eine eingeschränkte Flet-Version einzurichten. Sie verzichtet auf Audio- und Video-Controls, die unter Linux noch Probleme verursachen könnten. Zuvor müssen Sie gegebenenfalls noch »pip uninstall flet« ausführen und den versteckten Ordner ».flet/« aus Ihrem Home-Verzeichnis löschen.
Mit »flet –version« lässt sich die installierte Version von Flet prüfen. Jetzt kann es endlich mit der Flet-Programmierung losgehen.
Wetter-App
Für die Wetter-App mit Flet genügen 115 Zeilen Python-Code – in der älteren Flutter-Version mit Dart waren es dagegen rund 150 Zeilen. Das Beispiel umfasst lediglich zwei Python-Dateien.
Zuerst legen Sie mit dem Befehl »flet create« im Ordner »weather-app/« die Projektstruktur für die Flet-App an (Abbildung 1). Danach löschen Sie den Inhalt der Datei »main.py« im Unterverzeichnis »src/« und kopieren den Quellcode aus Listing 2 in die nun leere Datei. Im Ordner »src/« erstellen Sie eine zweite Python-Datei namens »homepage.py«, die Sie mit dem Code aus Listing 3 befüllen.
Listing 2
main.py
import flet as ft from homepage import HomepageView def main(page: ft.Page): home = HomepageView(page) page.add(home) page.update() ft.app(target=main)
Listing 3
homepage.py
import flet as ft
import json
import sys
from datetime import datetime
if sys.platform == "android" or sys.platform == "emscripten" or sys.platform == "ios":
import pyodide
elif sys.platform == "linux" or sys.platform == "win32" or sys.platform == "darwin":
import urllib.request
import ssl
class HomepageView(ft.Column):
def __init__(self, page):
super().__init__()
self.page = page
async def open_dlg(self, e):
self.api_key_field.value = await self.page.client_storage.get_async("api_key")
self.page.overlay.append(self.dlg)
self.dlg.open = True
self.page.update()
async def save_api_key(self, e):
await self.page.client_storage.set_async("api_key", self.api_key_field.value)
self.dlg.open = False
self.page.update()
async def delete_api_key(self, e):
await self.page.client_storage.remove_async("api_key")
self.api_key_field.value = ""
self.page.update()
async def get_weather(self, e):
try:
if sys.platform == "android" or sys.platform == "emscripten" or sys.platform == "ios":
with pyodide.http.open_url(
"https://api.openweathermap.org/data/2.5/weather?q=" +
self.city_field.value.replace(" ", "+").translate(
str.maketrans({"ä": "ae", "ö": "oe", "ü": "ue", "ß": "ss"})) + "&appid=" +
await self.page.client_storage.get_async("api_key") + "&lang=de") as response:
self.data = json.loads(response.read())
elif sys.platform == "linux" or sys.platform == "win32" or sys.platform == "darwin":
with urllib.request.urlopen(
"https://api.openweathermap.org/data/2.5/weather?q=" +
self.city_field.value.replace(" ", "+").translate(
str.maketrans({"ä": "ae", "ö": "oe", "ü": "ue", "ß": "ss"})) +
"&appid=" + await self.page.client_storage.get_async("api_key") +
"&lang=de", context=ssl._create_unverified_context()) as response:
self.data = json.loads(response.read())
self.temperature.value = "Temperatur: " + str(round((self.data["main"]["temp"]) - 273.15, 2)) + " Grad Celsius"
self.weather.value = "Wetter: " + self.data["weather"][0]["description"]
self.pressure.value = "Luftdruck: " + str(self.data["main"]["pressure"]) + " hPa"
self.wind_speed.value = "Windgeschwindigkeit: " + str(self.data["wind"]["speed"]) + " km/h"
self.humidity.value = "Luftfeuchtigkeit: " + str(self.data["main"]["humidity"]) + "%"
self.last_update.value = "Zuletzt aktualisiert: " + datetime.fromtimestamp(self.data["dt"]).strftime('%d/%m/%Y %H:%M:%S')
self.page.update()
except Exception:
self.page.overlay.append(self.error_dlg)
self.error_dlg.open = True
self.page.update()
def build(self):
self.page.title = "Wetter-App"
self.page.theme_mode = ft.ThemeMode.LIGHT
self.page.horizontal_alignment = ft.CrossAxisAlignment.CENTER
self.page.appbar = ft.AppBar(title=ft.Text("Wetter-App", color=ft.Colors.WHITE), bgcolor=ft.Colors.BLUE)
self.page.floating_action_button = ft.FloatingActionButton(
icon=ft.Icons.ADD,
bgcolor=ft.Colors.BLUE,
text="API key",
shape=ft.RoundedRectangleBorder(radius=40),
hweight=45, tooltip="API key angeben",
on_click=self.open_dlg)
self.api_key_field = ft.TextField(hint_text="Bitte API key angeben")
self.dlg = ft.AlertDialog(
title=ft.Text("API key angeben"),
content=self.api_key_field,
actions=[ft.Row(controls=[
ft.TextButton("OK", on_click=self.save_api_key),
ft.TextButton("CANCEL", on_click=lambda _: self.page.close(self.dlg)),
ft.TextButton("DELETE KEY", on_click=self.delete_api_key)
])
],
actions_alignment=ft.MainAxisAlignment.CENTER
)
self.error_dlg = ft.AlertDialog(content=ft.Text("Bitte Stadt und API-Key angeben!", size=18),
actions=[ ft.Row(controls=[ft.TextButton("OK",
on_click=lambda _: self.page.close(self.error_dlg)),],
alignment=ft.MainAxisAlignment.CENTER)])
self.image = ft.Image(src="weather_icon.png", width=100, height=100)
self.headline
= ft.Text("Aktuelles Wetter", weight=ft.FontWeight.BOLD, size=24, text_align=ft.TextAlign.CENTER)
self.city_field = ft.TextField(hint_text="Bitte eine Stadt angeben", label="Stadt", on_submit=self.get_weather)
self.button = ft.ElevatedButton("Wetter anzeigen", height=35, on_click=self.get_weather)
self.temperature = ft.Text("Temperatur:", size=18)
self.weather = ft.Text("Wetter:", size=18)
self.pressure = ft.Text("Luftdruck:", size=18)
self.wind_speed = ft.Text("Windgeschwindigkeit:", size=18)
self.humidity = ft.Text("Luftfeuchtigkeit:", size=18)
self.last_update = ft.Text("Zuletzt aktualisiert:", size=18)
self.controls.append(
ft.ResponsiveRow(
[ self.image, self.headline, self.city_field, self.button, self.temperature, self.weather, self.pressure, self.wind_speed, self.humidity, self.last_update ],
alignment=ft.MainAxisAlignment.CENTER,
)
)
Außerdem müssen Sie die Grafik »weather_icon.png« im Unterverzeichnis »assets/« ablegen. Grafiken und Bilder sollten im Format PNG, JPEG, BMP oder WEBP vorliegen. Die beiden schon existierenden PNG-Dateien im Verzeichnis »assets/« können Sie löschen.
Damit VS Code das installierte Modul flet findet, müssen Sie den korrekten Python-Interpreter auswählen, also den mit dem Namen der virtuellen Python-Umgebung (hier: ».venv«). Dazu drücken Sie in VS Code [Strg]+[Umschalt]+[P] und geben “Python Select Interpreter” ins Suchfeld ein. In der sich daraufhin öffnenden Liste wählen Sie den Python-Interpreter mit dem Namen der virtuellen Umgebung aus (Abbildung 2).
Einsprungpunkt
Die Datei »main.py« (Listing 2) dient lediglich als Einsprungpunkt der Flet-Applikation. In der ersten Zeile wird über das gängige Alias »ft« das Python-Modul flet importiert. In der zweiten Zeile erfolgt der Import der Klasse »HomepageView« aus der Datei »homepage.py«. Diese Klasse definiert die Oberfläche der Flet-Applikation und enthält die Programmlogik.
Die Funktion »main()« ab Zeile 3 dient als Hauptfunktion der App und wird beim Start zuerst ausgeführt. Sie fügt die Widgets hinzu, die man in Flet Controls nennt. Dabei handelt es sich um nichts anderes als gewöhnliche Python-Klassen, denen man (benannte) Parameter übergeben kann. In Flet lassen sich verschiedene Controls wie in einem Baum verschachteln, wobei das Page-Control das oberste Element darstellt (Abbildung 3).
Eine zentrale Methode einer Flet-App und der Klasse »HomepageView« ist die Build-Methode in Zeile 55 von Listing 3. Sie initialisiert die Flet-Controls und fügt sie mit der Anweisung »self.controls.append()« aus Zeile 93 dem Page-Control hinzu. Der in Zeile 56 gesetzte Titel des Anwendungsfensters lautet “Wetter-App”. Die nächste Zeile legt das Theme mit dem Enum »ThemeMode« auf »LIGHT« fest. Dadurch erscheint die Flet-Anwendung in einer hellen Ansicht. Daneben gibt es noch die Konstante »DARK«, mit der das App-Fenster dunkel dargestellt wird.
In der Zeile 58 wird der Inhalt des Anwendungsfensters mit »horizontal_alignment« und »ft.CrossAxisAlignment.CENTER« zentriert. Danach erfolgt die Konfiguration der Leiste im oberen Bereich der App. Dafür wird dem Flet-Control »AppBar« als Parameter »title« verschachtelt das Control »ft.Text()« für den Titel mitgegeben. Es enthält als ersten Parameter den String “Wetter-App”, als zweiten Parameter »color« die Farbe Weiß für den Text und zuletzt als Hintergrund »bgcolor« die Farbe Blau.
Zeile 4 von »main.py« (Listing 2) erzeugt ein Objekt der Klasse »HomepageView« und fügt es in der nächsten Zeile mit »page.add(home)« dem Container-Control »page« hinzu. Anschließend aktualisiert »page.update()« die Oberfläche. Die letzte Zeile der Datei »main.py« initialisiert das Programm mit »ft.app(target=main)« und führt die Funktion »main« aus.
Grafische Oberfläche
Die Datei »homepage.py« (Listing 3) erzeugt die eigentliche grafische Oberfläche als individuelles Control. Das wird in der Datei »main.py« dem Control »page« hinzugefügt. Die erste Zeile importiert auch hier das Modul flet mit dem Namen »ft«.
Den Zeitpunkt, zu dem die Wetterdaten zuletzt aktualisiert wurden, lässt sich einem Unix-Zeitstempel im Format der koordinierten Weltzeit UTC [5] entnehmen. Um diese Zeitangabe in das in Deutschland gebräuchliche Format umzurechnen (Listing 3, Zeile 49), bindet Zeile 4 von Listing 3 das Python-Modul »datetime« ein.
Der Code ab Zeile 5 prüft, unter welchem Betriebssystem die Anwendung läuft. Je nachdem werden entweder das Modul pyodide oder die Module urrlib.request und ssl importiert. Mit dem Modul sys.platform [6] lässt sich ermitteln, ob es sich beim Betriebssystem um Linux, Windows, MacOS, Android oder iOS handelt oder ob die App im Webbrowser läuft (»emscripten«).
Das ist notwendig, da Flet, sofern die App als statische Website ausgeführt wird, Pyodide [7] nutzt, eine Python-Distribution für den Browser und Node.js. Mittels Pyodide lässt sich Python-Code asynchron im Browser ausführen. Zudem kann man mit Flet dynamische Websites erstellen. Läuft die App lokal in einem Client, nutzt sie das Modul urrlib.request. Das Modul funktioniert ausschließlich als Desktop-App, nicht aber im Browser als Web-App.
Sowohl urrlib.request als auch pyodide dienen demselben Zweck: der Abfrage der Wetterdaten im Datenformat JSON [8]. Um den Rückgabewert der API-Anfragen als JSON-Daten einzulesen, kommt in beiden Fällen das Modul json zum Zug. Das Modul ssl dient dazu, später eine Verbindung zu einem Server auch ohne gültiges Zertifikat aufbauen zu können.
Die Klasse »HomepageView« aus Zeile 10 muss von einem Container-Control erben, wie hier zum Beispiel von »ft.Column«. Das hat zur Folge, dass sich alle Child-Controls vertikal anordnen. Es ist auch möglich, eine Flet-App funktional – das heißt ohne Klassen – in Python zu programmieren. Ich habe mich jedoch für den objektorientierten Ansatz mit einer Klasse entschieden. Auf diese Weise ist es möglich, eigene, isolierte und wiederverwendbare Controls zu erschaffen, die aus verschiedenen vorhandenen Controls bestehen.
Dazu definiert Zeile 11 einen Konstruktor. Damit lässt sich das Objekt »page« aus der Datei »main.py« über den Parameter »page« des Konstruktors durchreichen (Listing 2, Zeile 4). Zeile 13 von Listing 3 weist mit »self.page = page« den Wert des Parameters »page« dem Attribut »page« der Klasse »HomepageView« zu. Das ermöglicht, die grafische Oberfläche als eigenständiges Control »HomepageView« dem Page-Objekt in der Datei »main.py« hinzuzufügen.
Um Zugriff auf die Methoden und Eigenschaften des vererbenden Controls »ft.Column« zu erhalten, führt Zeile 12 die Anweisung »super().__init__()« aus. Um die Flet-Controls der GUI hinzuzufügen, kommt, wie schon erwähnt, die Life-Cycle-Methode »build« zum Einsatz, die die grafischen Elemente aufbaut (Zeile 55).
Daten ein- und ausgeben
Diese Methode wird aufgerufen, wenn zur Laufzeit das dazugehörige Control entsteht. Sie definiert zum Beispiel die Text-Controls »ft.Text()«, die die Wetter-Daten anzeigen (Zeile 87 bis 92). Der erste Parameter gibt den darzustellenden String an, der zweite legt die Schriftgröße fest. Darüber hinaus werden hier die beiden Textfelder erzeugt. Das erste Textfeld (Zeile 85) dient in der grafischen Oberfläche zur Eingabe der Stadt, für die der Wetterbericht gelten soll. Das zweite Textfeld (Zeile 67) wird später in einen Flet-Dialog eingebettet und zur Angabe des API-Keys von OpenWeatherMap [9] benutzt.
Die Flet-Textfelder »ft.TextField« verfügen unter anderem über die Parameter »hint_text«, »label« und »on_submit«. Der Parameter »hint_text« verlangt einen kurzen Hinweis als String, der im jeweiligen Textfeld vor der Eingabe erscheint. Über »label« legen Sie eine Zeichenkette fest, die im Rahmen des Textfelds erscheint. Der Parameter »on_submit« definiert eine Funktion, die ausgeführt wird, wenn der Benutzer die Eingabetaste betätigt.
Die Methode »get_weather()« ab Zeile 27 ruft die Wetterdaten ab und stellt sie dar. Zeile 86 erzeugt einen »ElevatedButton« für die GUI, den der Anwender betätigen kann, um die Wetterdaten mittels der erwähnten Methode »get_weather()« anzufordern. Der erste Parameter gibt die Beschriftung an, die auf dem Button stehen soll. Der nächste Parameter »height« legt die Höhe der Schaltfläche fest. Der letzte Parameter »on_click« definiert die Funktion, die ausgeführt wird, wenn der Anwender auf den Button klickt.
Am Anfang der Methode »get_weather()« wird wieder ermittelt, in welcher Umgebung die App läuft. Im Web heißt sie »emscripten«, und es kommt das erwähnte Modul pyodide ins Spiel. Dasselbe gilt bei den Versionen der App für die mobilen Betriebssysteme Android und iOS.
Falls die App lokal auf einem Rechner läuft, kommt hingegen das Modul urrlib.request zum Einsatz. Mittels urrlib.request und pyodide lassen sich wie angesprochen JSON-Daten aus einer API beziehen. Damit erhält die App Zugriff auf externe Ressourcen, hier zum Beispiel auf JSON-Daten aus der URL von OpenWeatherMap (Zeile 30 und 37).
In beiden Fällen kommt ein sogenannter Kontextmanager zum Einsatz, den in Python das Schlüsselwort »with« einleitet. Kontextmanager vereinfachen ein für den Zugriff auf externe Ressourcen wie Dateien oder in diesem Fall eine API notwendiges »try«-»finally«-Konstrukt. Die »finally«-Klausel stellt sicher, dass die entsprechende Ressourcenvariable immer ordnungsgemäß geschlossen wird. Ein Kontextmanager macht das überflüssig: Er schließt die Variable automatisch im Hintergrund, selbst wenn eine Exception auftritt.
Das Ergebnis der Requests mit dem Schlüsselwort »as« nimmt die Variable »response« auf. Die Struktur eines JSON-Strings ähnelt der eines Dictionary in Python, was die Verarbeitung recht komfortabel macht. In allen Fällen – ob lokal, auf dem Server oder auf einem mobilen Endgerät – erhalten die entsprechenden Methoden als Parameter die URL, von der aus die Wetterdaten im JSON-Format bezogen werden (Zeile 31 und 38).
Im vorliegenden Beispiel wird die URL aus mehreren Substrings zusammengesetzt. Beim ersten handelt es sich um die Basis-URL von OpenWeatherMap. Beim zweiten Substring wird das Textfeld »city_field« der grafischen Oberfläche ausgelesen, um die gewünschte Stadt zu bestimmen. Die Methode »replace« entfernt an dieser Stelle alle in das Textfeld eingegebenen Leerzeichen und ersetzt sie durch Pluszeichen (für den Fall, dass der Name der Stadt aus mehreren Wörtern besteht).
Ergänzend werden die deutschen Umlaute und das scharfe S durch entsprechende Strings wie »ae«, »oe«, »ue« und »ss« ersetzt. Darüber erhält der Parameter »&appid=« den API-Key von OpenWeatherMap zugewiesen, indem der Client Storage [10] der Flet-App mit ausgelesen wird (Zeilen 15 und 34). Das erfolgt asynchron, wie man am führenden Schlüsselwort »await« erkennt. Daher muss vor dem Methodennamen »get_weather()« das Schlüsselwort »async« stehen (Zeile 27). Da auch die anderen Methoden der Klasse »HomepageView« den API-Key asynchron speichern, auslesen oder löschen, müssen Sie auch dort dem Methodennamen jeweils ein »async« voranstellen.
Der API-Key wird in der Beispiel-App mit dem Client-Storage-Feature von Flet persistent gespeichert. Über die Client-Storage-API lassen sich Daten als Schlüssel-Wert-Paare persistent ablegen, sodass sie sogar einen Neustart der App überleben. Dazu nutzt Flet das Flutter-Package shared_preferences [11]. Schließlich wird ganz am Ende des Strings mit »&lang=de« die Sprache auf Deutsch eingestellt. Mit dieser konkatenierten URL fragen die Zeilen 35 und Zeile 43 via »json.loads(response.read())« die Wetterdaten im JSON-Format ab und weisen sie der Variablen »data« zu.
Nun kommt das eingangs erwähnte Modul SSL zum Einsatz. Zeile 42 weist dem Parameter »context« des Aufrufs »urlopen« den Wert »ssl._create_unverified_context()« zu. Das birgt ein gewisses Sicherheitsrisiko, das man aber vernachlässigen kann, da keine sensiblen Daten übertragen werden.
Die folgenden Zeilen bis Zeile 49 lesen die verschiedenen Wetterelemente aus dem JSON-String aus und zeigen sie in den Text-Controls an. Damit das Text-Control den jeweiligen Wert übernimmt, muss man ihn der Eigenschaft »value« des Text-Controls zuweisen, zum Beispiel mit »temperature.value = Wert«. Außerdem gilt es, am Ende mit »self.page.update()« noch die Anzeige zu aktualisieren (Zeile 50), sonst würden die Änderungen nicht sichtbar. Der Abruf der Wetterdaten von OpenWeatherMap erfolgt innerhalb eines Try-Except-Blocks, um mögliche Fehler abzufangen.
Tritt eine Exception auf, zeigt der »ft.AlertDialog()« dem Benutzer alle Ausnahmen an (Zeile 52). Dazu wird der Flet-Dialog »self.error_dlg« aus Zeile 79 über die Oberfläche der App gelegt und mit »self.error_dlg.open = True« geöffnet. Der Aufruf »self.page.update()« aktualisiert die GUI (Zeile 54). Man könnte die Ausnahmebehandlung durchaus noch verfeinern. Der Einfachheit halber reagiert die Beispiel-App nur allgemein auf eine Exception, wie sie zum Beispiel eintritt, wenn der Anwender eine ungültige Stadt angibt oder der API-Key fehlt.
Eingabedialog
Die Zeile 60 definiert einen »FloatingActionButton«, den die grafische Oberfläche der Flet-App meist rechts unten darstellt (Abbildung 4). In diesem Fall legt »ft.Icons.ADD« als Icon jenes mit dem Pluszeichen fest. Darauf folgen die Einstellung der Hintergrundfarbe (Blau) und die Beschriftung mit »text=”API key”«. Schließlich bestimmt »shape=ft.RoundedRectangleBorder(radius=40)« eine abgerundete Form des Buttons.
In dieser Wetter-App dient der »FloatingActionButton« dazu, den zuvor generierten API-Key einzugeben. Der letzte Parameter »on_click« legt auch hier fest, welche Funktion es auszuführen gilt, wenn der Anwender auf diesen Button klickt. Hier ist es die Methode »open_dlg()« aus Zeile 14. Sie ruft einen Dialog auf, der den API-Key entgegennimmt.
Nun gehen wir etwas näher auf die Methode »open_dlg()« ein, die ein Klick auf »FloatingActionButton« aufruft. Diese Methode dient zur Anzeige und zum Ändern des API-Keys. Dafür erzeugt Zeile 68 erneut einen Dialog mit »ft.AlertDialog()«, den die Methode »open_dlg()« in Zeile 17 öffnet. Der erste Parameter des Dialogs, »title«, definiert den am oberen Rand angezeigten Titel. Der zweite, »content«, bezieht sich auf den Inhalt des Dialogs. In diesem Beispiel handelt es sich dabei um das Textfeld »api_key_field«. Der Parameter »actions« sorgt für die Integration der Schaltflächen OK, CANCEL und DELETE KEY (Abbildung 5).
Diese Schaltflächen werden jeweils mit dem Control »ft.TextButton()« generiert. Dessen Event-Handler »on_click« legt die Methoden fest, die es auszuführen gilt, sobald der Anwender den Button betätigt. Der OK-Button startet die Methode »save_api_key« aus Zeile 19. Ein Klick darauf speichert den API-Key (Zeile 20). An dieser Stelle wird in der Methode »save_api_key()« dem Schlüssel »api_key« im Client-Storage der Wert aus dem Textfeld »api_key_field« zugewiesen. Danach schließt »dlg.open = False« den Dialog wieder und »self.page.update()« aktualisiert die Oberfläche (Zeile 21 und 22).
Die Schaltfläche DELETE KEY entfernt entsprechend den API-Key aus dem Client-Storage der App. Dafür wird die Methode »delete_api_key« in Zeile 23 genutzt. Sie löscht zuerst den Schlüssel aus dem Client-Storage (Zeile 24). Danach leert sie das Textfeld (Zeile 25) und aktualisiert schließlich die Anzeige (Zeile 26).
Die dritte Funktion des »ft.AlertDialog« gehört zur Schaltfläche CANCEL. Dieser Button führt mittels eines Lambda-Ausdrucks die Methode »self.page.close(self.dlg)« aus, die lediglich den angegebenen Dialog wieder schließt (Zeile 73). Die Anweisung »actions_alignment=ft.MainAxisAlignment.CENTER« in Zeile 77 zentriert schließlich alle Buttons des Dialogs.
Das Auslesen des API-Keys im Client-Storage geschieht immer mit dem Ausdruck »await self.page.client_storage.get_async(“api_key”)«, beispielsweise in der Methode »get_weather()« zum Abruf der Wetterdaten (ab Zeile 27) oder in der Methode »open_dlg()« (Zeile 15).
Wurde noch kein API-Key angegeben, bleibt in dieser Methode das entsprechende Textfeld leer: Bevor man mit »client_storage« und der Methode »get_async()« den Wert auslesen kann, muss man analog dazu einen Wert mit der Methode »set_async()« speichern. Das erfolgt in der Methode »save_api_key()« in Zeile 20. Danach legt »self.page.overlay.append(dlg)« den Öffnen-Dialog über das Page-Control, und »dlg.open = True« öffnet ihn. Damit der Anwender das auch zu sehen bekommt, müssen Sie hier mit »self.page.update()« ebenfalls die grafische Oberfläche aktualisieren.
Grafische Oberfläche
Um ein Bild in die grafische Oberfläche einzubinden, verwenden Sie das Flet-Control »ft.Image« (Zeile 83). Mit dem Parameter »src« referenzieren Sie den Pfad der einzubindenden Grafik. Hier genügt es, den Dateinamen anzugeben, sofern Sie die Bilddatei im Ordner »assets/« gespeichert haben. Mit »width« und »height« spezifizieren Sie dann Breite und Höhe. Zeile 84 legt via »ft.Text« die Überschrift fest. Die nachfolgenden Text-Controls dienen wie bereits geschildert der Anzeige der Wetterdaten.
Am Ende der obligatorischen Methode »build« ab Zeile 55 fügen Sie die erzeugten Controls mit »self.controls.append()« der eigentlichen grafischen Oberfläche hinzu (ab Zeile 93). An dieser Stelle werden ein Control »ft.ResponsiveRow()« und darin als Liste verschachtelt die zuvor erzeugten Controls in die GUI eingebunden.
Wie der Name schon sagt, ist das Control »ft.ResponsiveRow()« responsiv, passt sich also dem Display des ausführenden Geräts (Smartphone, Desktop, Webbrowser) an (Abbildung 6). Standardmäßig fügt es 10 Pixel Abstand zwischen den einzelnen Controls hinzu. Der letzte Parameter »alignment« zentriert mit »ft.MainAxisAlignment.CENTER« alles horizontal (Zeile 96).
Deployment
Um die Flet-App auszuführen, genügt das Kommando »flet run« im Unterverzeichnis »src/« des Projektverzeichnisses, beziehungsweise »flet run Name.py«, falls Sie die Datei »main.py« anders benannt haben. Alternativ wählen Sie in VS Code die entsprechende Datei aus und betätigen den Ausführen-Knopf rechts oben.
Da es sich bei Flet um ein plattformübergreifendes Framework handelt, können Sie die erstellte App ohne Veränderungen an der Codebasis für verschiedene Endgeräte generieren, ohne dass auf der Zielplattform Python oder andere Module installiert sein müssen. Die Anpassung erfolgt seit der Version 0.26.0 von Flet vollautomatisch. Beim Flutter-SDK musste man dazu noch manuell die passende Version für den Desktop, das Smartphone oder das Web installieren.
Bevor Sie den Build-Prozess starten, müssen Sie noch einige Pakete installieren (Listing 4). Wenn Sie das erste Mal mit »flet build« eine ausführbare Datei erzeugen oder eine Web-App erstellen, kann der Build-Prozess einen Augenblick dauern, da dazu Flutter heruntergeladen und installiert werden muss. Danach können Sie die Flet-App in eine ausführbare Datei packen, wobei Sie über die Option »–project« den Namen für die ausführbare Datei angeben (Listing 5, erste Zeile). Die App kann damit auf allen Linux-Distributionen unabhängig laufen.
Listing 4
Build vorbereiten
$ sudo apt-get update -y
$ sudo apt-get upgrade -y
$ sudo apt-get install -y \
curl git unzip xz-utils \
zip libglu1-mesa
$ sudo apt-get install \
clang cmake git ninja-build \
pkg-config libgtk-3-dev \
liblzma-dev libstdc++-12-dev
Listing 5
Build unter Linux
$ flet build linux --project weather-app $ pip install pyinstaller $ flet pack main.py --add-data "assets:assets" --name weather-app $ flet build web --base-url weather-app $ python3 -m http.server --directory build/web
Um die erstellte Flet-App kompakt in eine einzelne ausführbare Datei zu bündeln, behelfen Sie sich mit dem Programm PyInstaller, das Sie vorab installieren müssen (Listing 5, zweite Zeile). Danach generieren Sie mit dem Befehl aus der dritten Zeile im Unterordner »src/« die ausführbare Datei. Etwas performanter als eine mit »flet pack« erstellte App fällt übrigens eine ausführbare Datei aus, die Sie über »flet build linux« generieren.
Unabhängig davon, ob Sie die ausführbare Version der App mit »flet build« oder »flet pack« erstellen, lässt sie sich nur für das jeweilige Betriebssystem bauen, unter dem Sie gerade arbeiten. So kann man Linux-Apps nur unter Linux erzeugen, Windows-Apps nur unter Windows und Apps für MacOS nur unter dem Apple-Betriebssystem. Die Tabelle “Build-Optionen” fasst zusammen, was Sie mit welchem Betriebssystem generieren können.
|
»flet build« |
»apk«/»aad« |
»ipa« |
»linux« |
»macos« |
»windows« |
»web« |
|---|---|---|---|---|---|---|
|
Linux |
ja |
– |
ja |
– |
– |
ja |
|
MacOS |
ja |
ja |
– |
ja |
– |
ja |
|
Windows |
ja |
– |
WSL |
– |
ja |
ja |
Möchten Sie eine statische Webanwendung erstellen, verwenden Sie dazu den Aufruf aus der vierten Zeile von Listing 5. Dabei entsteht das Verzeichnis »build/web/«, dessen Inhalt Sie dann auf einen Webserver hochladen. Als Ziel dient ein Ordner im Stammverzeichnis, dessen Namen Sie mit der Option »–base-url« angeben, im Beispiel »weather-app«. Alternativ lässt sich die Webanwendung lokal mit dem HTTP-Server prüfen, den Python standardmäßig mitbringt. Dazu führen Sie im Projektunterverzeichnis »src/« den Befehl aus der letzten Zeile von Listing 5 aus. So können Sie unter http://localhost:8000 die App als Webanwendung testen.
Mit dem Befehl »flet run –web main.py« öffnen Sie die Flet-App bei Bedarf mit dem Standard-Webbrowser. Außerdem ist es möglich, mit »flet build apk –product weather-app« eine APK-Datei zu erstellen. Beim erstmaligen Generieren einer solchen Datei werden gegebenenfalls noch das Java Development Kit und das Android SDK installiert. Die Resultate finden sich alle im Ordner »build/« oder »dist/« im Projektverzeichnis.
Sollten beim Build-Prozess Probleme auftreten, hat es sich bewährt, vor der Kompilierung die Ordner »build/« und »dist/« vorher zu löschen.
Eine weitere Möglichkeit, die Flet-App auf ein Smartphone zu bekommen, sind sogenannte Progressive Web Apps (PWAs). Dabei wird die App auf dem Smartphone mit dem Browser aufgerufen, ohne dass man sie installieren muss. Ausführliche Informationen dazu finden Sie bei Interesse in der Flet-Doku [12].
Zu guter Letzt gibt es die Option, die Flet-App bei einem Hosting-Anbieter hochzuladen, der Flet-Applikationen unterstützt, zum Beispiel Cloudflare [13]. Wie das funktioniert, beschreibt eine Schritt-für-Schritt-Anleitung in der Flet-Doku [14].
Fazit
Mit Flet wird Flutter auch für Softwareentwickler interessant, die sich nicht in die neue Programmiersprache Dart einarbeiten möchten. Aktuell unterstützt Flet nur Python. Man merkt dem Framework an, dass es noch in der Entwicklung steckt und es unter der Haube noch die eine oder andere Baustelle gibt. Da Python vergleichsweise einfach zu erlernen und weitverbreitet ist, fällt die Einstiegshürde in die Flutter-Entwicklung mit Flet niedrig aus. (jcb/jlu)
Infos
- Flutter: https://flutter.dev
- Flet: https://flet.dev
- Flutter: Markus Hoffmann, “Eine Quelle, viele Apps”, LM 03/2022, S. 78, https://www.lm-online.de/47011
- Visual Studio Code: https://code.visualstudio.com
- Koordinierte Weltzeit: https://de.wikipedia.org/wiki/Koordinierte_Weltzeit
- Python-Modul sys.platform: https://docs.python.org/3/library/sys.html#sys.platform
- Pyodide: https://pyodide.org
- JSON: https://www.json.org/json-de.html
- OpenWeatherMap: https://openweathermap.org
- Client-Storage von Flet: https://flet.dev/docs/cookbook/client-storage
- Flutter-Package shared_preferences: https://pub.dev/packages/shared_preferences
- Progressive Web Apps: https://flet.dev/docs/publish/web/dynamic-website/#pwa
- Cloudflare: https://www.cloudflare.com
- Flet-Anleitung für Cloudflare: https://flet.dev/docs/publish/web/static-website/hosting/cloudflare













Vielen Dank für das interessante Beispiel.
Leider ist es heute (Juli ’26) nicht mehr lauffähig, da sich einige Sprachkonstrukte im Laufe der Zeit verändert haben.
(Falls Interesse besteht, kann ich Ihnen die angepassten Sourcen gern zur Verfügung stellen).
Grüße
rügi