Webanwendungen als Ersatz für native Anwendungen
Mittlerweile finden wir kaum noch Anwendungen, die für genau eine Plattform konzipiert werden können. Insbesondere die Beschränkung einer Anwendung nur auf dem Desktop verfügbar zu sein, ist mittlerweile nicht mehr tragbar. Viele Delphi Entwickler stehen nun vor der Frage, wie sie ihre Anwendung auf weiteren Plattformen anbieten können. Webanwendungen bringen klare Vorteile, um eine Anwendung auf jeder Plattform mit der selben Quelltextbasis zu entwickeln. Das reduziert die Entwicklungskosten. Auf der Seite des Endkunden ist keine spezielle Software oder Hardware erforderlich. Aktualisierung und Wartung bereits vorhandener Installationen ist einfach möglich. Insbesondere ist keine Installation beim Kunden erforderlich, damit die Software einsatzbereit ist. Selbst auf Mobilgeräten ist die Verwendung von Webanwendungen unproblematisch. Vielmehr gibt es zahlreiche Werkzeuge mit Gestaltungsmöglichkeiten für Benutzeroberflächen, wie z.B. Bootstrap [1], die speziell für Smartphones und Tablets ausgelegt werden.
Einzig und allein ein Hauptgrund behindert den Umstieg auf Webanwendungen: Die Anwendungen müssen auf einem Webserver im Internet (oder zumindest in einem Netzwerk) bereitgestellt werden. Das erfordert eine Verbindung zu dem Netzwerk, in dem der Webserver erreichbar ist, um die Anwendung zu nutzen. Daher kommen oftmals weiterhin native Anwendungen zum Einsatz, die speziell für die entsprechenden Endgeräte erstellt werden. Als Delphi Entwickler hat man hier mit dem FireMonkey Framework weiterhin sehr gute Karten, da man sowohl Software für Microsoft Windows, Apple macOS, Linux, als auch die mobilen Plattformen Apple iOS, iPadOS und Android entwickeln kann, ohne für jede Plattform eigene Varianten zu erstellen. Trotzdem sind insbesondere bei der Auslegung der Benutzeroberfläche viele Hürden zu nehmen, die bei Webanwendungen meist flexibler zu meistern sind.
Progressive Web Apps (PWA, dt. Progressive Webanwendungen) sind eine spezielle Art von Webanwendungen, die versuchen die Vorteile von Web- und nativen mobilen Anwendungen zu kombinieren. So können PWA auch in Offline-Szenarien zum Einsatz kommen. Moderne Webbrowser bieten hier meist eine Anbindung an das ausführende Betriebssystem und binden PWA direkt ein. Auf mobilen Endgräten, wie z.B. dem iPhone sind PWAs schwierig von nativen Anwendungen auf dem Home Screen zu unterscheiden (siehe Abbildung 1). Aber auch während der Ausführung der Anwendungen sind keine Hinweise auf eine Webanwendung direkt erkennbar, da sie im Vollbildmodus angezeigt und Merkmale des Webbrowsers ausgeblendet werden. Insbesondere die URL wird nicht angezeigt. Die Anwendung wird lokal auf dem Gerät zwischengespeichert und kann somit auch ausgeführt werden, wenn es keine Verbindung zum Netzwerk hat. Zudem wird bei bestehender Internetverbindung automatisch geprüft, ob auf dem Webserver eine neue Version bereitgestellt wird. Ist das der Fall, so wird die lokale Version automatisch aktualisiert. Ein weiterer Vorteil von nativen Anwendungen ist, dass sie auf spezielle Gerätefunktionen wie Kameras, Sensoren, Positionsbestimmung und Push- Nachrichten zugreifen können. Auch diese Beschränkung wird durch PWA aufgehoben, ohne dabei die Sicherheit zu kompromittieren. Der Zugriff auf diese Gerätefunktionen ist möglich. Jede PWA wird in einer individuellen Sandbox ausgeführt. Dazu werden alle Dienste nur mit einer gesicherten Verbindung über HTTPS ausgeführt. Zugriffe auf das lokale Dateisystem usw. sind erst nach manueller Bestätigung des Benutzers möglich. All diese Vorgänge müssen insbesondere nicht manuell implementiert werden sondern sind fester Bestandteil einer jeden PWA und stellen somit sicher, dass es sich bei PWA um sichere, gesicherte (im Englischen wird explizit unterschieden zwischen ’safety’ und ’security’) Anwendungen handelt.
Viele Entwickler führen auch die Vorgaben und Lizenzbeschränkungen von App Stores sowie Gebühren bei der Bereitstellung auf diesen Plattformen als Hinderungsgrund an, dass native Anwendungen auf bestimmten Plattformen bereitgestellt werden. Apple hat z.B. strenge Richtlinien wie die Benutzeroberfläche einer nativen Anwendung auszusehen hat und das erfordert meist, dass man die Anwendung jeweils auf die neueste Version von iOS und iPadOS aktualisieren muss. Auch diese Hürde entfällt durch den Einsatz von PWA. Ihre Kunden navigieren im Webbrowser zu einer URL und von dort kann die Anwendung dann komfortabel auf dem Gerät installiert werden. Leider gibt es unterschiedliche Ansätze, die vom verwendeten Gerät abhängen, so dass hier keine universelle Anleitung gegeben werden kann. Auf dem Apple iPhone und iPad wird die Anwendung über die Teilen Funktion, über die man z.B. auch ein Lesezeichen erstellt, zum Home Screen hinzugefügt (siehe Abbildung 2). Auf Android-Geräten hingegen wird direkt eine Dialogbox eingeblendet, dass man die Anwendung auf dem Home Screen installieren kann. Die Anwendung kann meist beliebig benannt werden. Das Symbol wird allerdings immerzu vom Entwickler festgelegt (siehe Abbildung 3).
Webanwendungen in Delphi mit TMS WEB Core
In den vergangenen Ausgaben des Entwickler-Magazins wurde TMS WEB Core bereits als ein Werkzeug zur Entwicklung von Webanwendungen mit Delphi vorgestellt. Insbesondere werden aber auch Funktionen zur einfachen Erstellung von PWAs bereitgestellt, die nicht im Detail vorgestellt wurden. In diesem Artikel werden wir eine solche Anwendung entwickeln, die auch ohne Internetverbindung auf mobilen Geräten funktionieren wird. Wir werden eine kleine Wetterstation erstellen, die immerzu das aktuelle Wetter an unserer Position anzeigt. Dazu werden wir die Geocodingkomponente von TMS WEB Core verwenden. Sollte keine Internetverbindung bestehen, so wird auf den letzten Datenabgleich zurückgegriffen (siehe Abbildung 4).
Als Datenquelle nutzen wir das OpenWeather API (siehe Abbildung 5). Zur Nutzung des APIs ist die Erstellung eines Benutzerkontos erforderlich. Zudem ist ein Zugangsschlüssel zu generieren, der den Entwickler gegenüber des OpenWeather API identifiziert (siehe Abbildung 6). Um das hier gezeigte Beispiel zu reproduzieren, ist die kostenlose Variante ausreichend. Es gibt zahlreiche weitere kostenpflichtige Pakete, die eine höhere Informationstiefe der Wetterinformationen liefern. Für diesen Artikel reicht es allerdings aus, grundlegende Wetterinformationen abzurufen. Der kostenlose Endpunkt stellt bereits Wetterdaten für drei Tage zu zahlreichen Tageszeiten zur Verfügung.
PWA erstellen
TMS WEB Core integriert sich in die RAD Studio IDE. Es werden Vorlagen für verschiedene Anwendungtypen bereitgestellt. Darunter ist auch die Vorlage mit dem Namen TMS WEB PWA Application zu finden, die uns die Grundlage für eine PWA liefert (siehe Abbildung 7). Man erreicht diese Vorlage über File > New > Other… in der Kategorie Delphi > TMS WEB.
Es wird dann der Grundstock für eine PWA erstellt. Es empfiehlt sich direkt nach der Erstellung des Projektes, alle Projektdateien zu speichern (siehe Abbildung 9). Speichern Sie die Dateien in einem neuen Verzeichnis. Sie können die Vorgaben akzeptieren, jedoch empfehle ich die folgenden Anpassungen, die in den folgenden Punkten erläutert werden.
- Symbole (Icons) in verschiedenen Auflösungen im PNG-Grafikformat (IconRes*.png). Sie können diese Dateinamen direkt verwenden und mit einer eigenen Grafik bestücken. Beachten Sie, dass Apple iOS und iPadOS keine transparenten Hintergründe unterstützt und das Symbol dann mit einem schwarzen Hintergrund präsentiert wird. Auch wenn es keine App Richtlinien für PWA gibt, so ist anzuraten, ein Icon zu erstellen, was den Hintergrund voll ausfüllt. Sie können eigene Dateinamen in den Projektoptionen festlegen. Die Auflösung der einzelnen Icons darf nicht verändert werden.
- Der sogenannte PWA Service Worker (js), der die eigentliche Funktionalität der PWA bereitstellt. Durch den Worker wird z.B. sichergestellt, dass Ihre Anwendungen auch offline ausgeführt werden können.
- Anwendungsinformationen in Form eines Manifests im JSON-Format (json). Diese Anwendungsinformationen als auch die Dateinamen für eigene Anwendungssymbole können Sie komfortabel über die Projektoptionen (Project/Options…) vornehmen (siehe Abbildung 8). TMS WEB Core generiert die Manifest Datei dann basierend auf diesen Informationen automatisch. Für unser Beispiel zum Einstieg werden wir keine individuellen Anpassungen vornehmen.
- Das Einstiegs-HTML-Dokument (html), welches die Anwendung startet. Hier empfehle ich von der Vorgabe abzuweichen und index.html zu verwenden. Dadurch ist die Angabe der Datei bei der Bereitstellung auf den meisten Webservern nicht erforderlich. Die meisten Webserver öffnen index.html, wenn kein Dateiname angegeben wird.
- Ein Hauptformular, das nach dem Start der Anwendung angezeigt wird, bestehend aus einer Quelltextdatei mit der Endung PAS und zusätzlichen Designelementen in einer HTML-Datei. Diese Datei habe ich unter pas gespeichert. Die HTML-Datei wird dann von TMS WEB Core automatisch ebenfalls umbenannt. Wir werden in diesem grundlegenden Beispiel nur Design im Formulardesigner der Delphi Entwicklungsumgebung nutzen. Die HTML-Datei darf nicht entfernt werden, wird aber in diesem Beispiel nicht angepasst.
Benutzeroberfläche gestalten
Dem Prinzip der rapiden Anwendungsentwicklung (RAD) bleibt TMS WEB Core treu. Sie können durch die Nutzung von Komponenten, die Sie aus der Werkzeugleiste auswählen und auf dem Formular positionieren, in Windeseile eine grafische Oberfläche erstellen. Die Namen und Funktion der Komponenten sind stark an die VCL von Delphi angelehnt. Die VCL beinhaltet TLabel für Textelemente, TMS WEB Core bietet TWebLabel an. Nahezu jede VCL-Komponente ist als TWeb-Variante mit demselben Namen zu finden. So gelingt es uns schnell mit TWebLabel, TWebImageControl und TWebTableControl die in Abbildung 10 dargestellte Oberfläche zu erstellen. Die verwendeten Komponenten, deren Namen und eine Kurzerklärung wofür sie verwendet werden, finden Sie in Tabelle 1.
Wir können die Anwendung bereits starten. Der Standardwebbrowser auf Ihrem System wird geöffnet und die Komponenten werden mit den zur Entwicklungszeit angegebenen Werten angezeigt.
LUST AUF mehr DELPHI?
Erleben Sie Workshops vom 4. - 6. November 2024 in Düsseldorf
Bevor wir die Komponenten mit Leben füllen können, müssen zwei grundlegenden Funktionen implementiert werden:
- Bestimmung der geographischen Koordinaten (Länge und Breite) der aktuellen Position.
- Abfragen der Wettersituation für die gefundenen Koordinaten.
Beiden Funktionen werden im Web asynchron ausgeführt. Das heißt, wenn wir die aktuelle Position anfragen, kann die Komponente nicht direkt eine Antwort liefern. TMS WEB Core bietet vielfache Möglichkeiten, die Antwort auszulesen. Drei, um genau zu sein:
- Ereignis: Die Komponenten bieten ein Ereignis an, das aufgerufen wird, sobald die Antwort zur Auswertung bereitsteht.
- Anonyme Methode (auch Closure): Zusammen mit dem Methodenaufruf wird eine Prozedur als Parameter übergeben, die bei vorhandener Antwort aufgerufen wird.
- Await: TMS WEB Core erweitert die Object Pascal Sprache, dass man auf asynchrone Operationen warten kann, bevor die nächste Programmzeile ausgeführt wird.
Name | Komponente | Beschreibung |
txtHeader | TWebLabel | Titel der Anwendung |
txtLocationText | TWebLabel | Aktuelle Position |
txtLocationNumbers | TWebLabel | Koordinaten der Position |
txtDescription | TWebLabel | Aktuelle Wetterlage |
Icon | TWebImageControl | Symbol für Wetterlage |
Grid | TWebTableControl | Tabelle mit Kerninformationen |
Tabelle 1: Komponenten des Hauptformulars.
In diesem Beispiel werden wir die Implementierung über ein Ereignis und await vorstellen. Für eine detaillierte Betrachtung der Varianten sei hier auf weiterführende Literatur verwiesen.
Sowohl die Positionsbestimmung als auch die Anbindung an den Webservice sind in einem separatem Datenmodul zu finden (siehe Abbildung 11). Auf dem Datenmodul wurden zwei Komponenten abgelegt:
- TWebGeolocation: Komponente zur Positionsbestimmung. Die Anfrage wird durch Aufruf der Methode GetGeolocation Wurde eine Position ermittelt, so wird das Ereignis OnGeolocation aufgerufen.
- TWebHttpRequest: In einem Satz, können Sie mit dieser Komponente HTTP(S) Anfragen stellen. Es handelt sich hierbei um eine der umfangreichsten Komponenten, die TMS WEB Core zu bieten hat, so dass wir sie hier nur anhand eines Beispiels einführen können. Die Anfragen an das OpenWeather API erfolgen mit GET Requests an einen gesicherten HTTPS Endpunkt. Die Eigenschaft Command wird daher auf httpGET Die Antwort ist im JSON-Format kodiert, was erfordert die Eigenschaft ResponseType mit dem Wert rtJSON zu belegen.
Aktuelle Position bestimmen
Die Methode UpdateLocation des Datenmoduls startet den Prozess.
Listing 1
procedure TWeatherServiceManager.UpdateLocation; begin Geocoder.GetGeolocation; end; procedure TWeatherServiceManager.GeocoderGeolocation( Sender: TObject; Lat, Lon, Alt: Double); begin Location.Latitude := Lat; Location.Longitude := Lon; if Assigned(FOnLocationUpdated) then begin FOnLocationUpdated(Location); end; end;
Sobald eine Position gefunden wird, können wir die Koordinaten aus den Parametern Lat und Lon auslesen. Das Ereignis OnLocationUpdated wird getriggert, falls es zugewiesen wurde. Über diesen Weg kann die Benutzeroberfläche informiert werden. Um das Beispiel nicht zu komplex gestalten, wird das Standardereignis vom Typ TNotifyEvent verwendet.
Listing 2
property OnLocationUpdated : TNotifyEvent read FOnLocationUpdated write FOnLocationUpdated;
Wetterinformationen abrufen
Das OpenWeather API wird mit HTTP Requests angesteuert. In der Dokumentation sind die Endpunkte angegeben und welche Informationen zurückgegeben werden. Die Antworten sind allesamt in JSON kodiert.
Die URL für die Anfrage wird in GetForcastUrlForLocation konstruiert. Sie beinhaltet die geographischen Koordinaten und den API Key, der von OpenWeather bereitgestellt wird. Auf diesem Weg können Sie für beliebige Koordinaten Wetterinformation abrufen.
Listing 3
function TWeatherServiceManager.GetForecastUrlForLocation: String; begin Result := Format( REQ_PATTERN_FORECAST, [ Location.Latitude, Location.Longitude, API_KEY ] ); end; procedure TWeatherServiceManager.GetForecastForCurrentLocation; var LResponse: TJSXMLHttpRequest; begin Request.URL := GetForecastUrlForLocation; LResponse := await( TJSXMLHttpRequest, Request.Perform ); if LResponse.Status = 200 then begin ProcessForecastResult(LResponse.response); end; end;
Die Methode GetForecastForCurrentLocation weist der TWebHttpRequest-Komponente die notwendigen Informationen zu. In diesem Fall müssen wir nur die URL nachreichen, da alle anderen Informationen bereits zur Entwicklungszeit über den Objektinspektor festgelegt wurden. Die Funktion await erfordert zwei Parameter. Zuerst wird der Typ des Rückgabewertes spezifiziert. Anschließend die Methode, auf die gewartet werden soll. Sobald die Methode Request.Perform beendet ist, wird ihr Rückgabewert in LResponse abgelegt. Der spezielle Datentyp, der mehr oder weniger der Antwort von einem HTTP Request in JavaScript nachgebildet wurde, liefert in der Eigenschaft Status den HTTP-Statuscode zurück. Der Wert 200 deutet auf eine erfolgreiche Anfrage hin, die wir dann in der Methode ProcessForecastResult verarbeiten und in ein Objekt des Typs TWeatherForecast schreiben.
Es handelt sich hierbei um einen eigenen Typ, den wir deklarieren, um die Rückgabewerte vom Webservice abzulegen. Es ist denkbar, weitere Eigenschaften zu definieren, um z.B. die Temperatur sowohl in Fahrenheit als auch Celsius abzurufen.
Listing 4
type TWeatherForecast = class public property Dt: TDateTime; property Temperature: Double; property Humidity: Integer; property Description: String; property Icon: String; property PropPrec: Integer; property IconUrl: String read GetIconUrl; property DtReadable: String read GetDtReadable; end;
Neben simplen Eigenschaften, die Werte aus privaten Feldvariablen bereitstellen, gibt es zwei Eigenschaften, die uns das Leben einfacher machen. Wir können über IconUrl die URL auslesen, wo ein Icon zur Wetterlage, die beschrieben wird, zu finden ist.
Listing 5
function TWeatherForecast.GetIconUrl: String; begin Result := Format( URL_ICON, [Icon] ); end;
Mit DtReadable wird der Datumswert des Webservice in eine für uns lesbare Form umgewandelt.
Listing 6
function TWeatherForecast.GetDtReadable: String; var LFormat: TFormatSettings; begin LFormat := TFormatSettings.Create; LFormat.ShortDateFormat := 'ddd mmm d, yyyy'; LFormat.LongTimeFormat := '''@'' ham/pm'; Result := DateTimeToStr( UniversalTimeToLocal(Dt), LFormat ); end;
Der Zeitpunkt wird vom Webservice im universellen Zeitformat übermittelt, so dass wir auf die lokale Zeit mit UniversalTimeToLocal umwandeln müssen. Die Formatierung erfolgt wie in der VCL über TFormatSettings.
JSON auswerten
Die Auswertung der Antwort mit den Wetterinformationen erfolgt mit Hilfe von Klassen, die TMS WEB Core zum Verarbeiten von in JSON kodierten Informationen bereitstellt.
Listing 7
procedure TWeatherServiceManager.ProcessForecastResult( AResponse: JSValue; ADoStore: Boolean = true); var LArray: TJSArray; LObj: TJSObject; LRoot, LCity, LMain, LWeather: TJSObject; i: Integer; LForecast: TWeatherForecast; begin LRoot := TJSObject(AResponse); LCity := TJSObject(LRoot['city']); Location.Name := JS.toString(LCity['name']); Location.Country := JS.toString(LCity['country']); LArray := TJSArray( LRoot['list'] ); FForecasts.Clear; for i := 0 to LArray.Length-1 do begin LObj := TJSObject( LArray[i] ); LMain := TJSObject(LObj['main']); LWeather := TJSObject( TJSArray(LObj['weather'])[0] ); LForecast := TWeatherForecast.Create; LForecast.Dt := UnixToDateTime( JS.toInteger( LObj['dt'] ) ); LForecast.Temperature := JS.toNumber( LMain['temp'] ); LForecast.Humidity := JS.toInteger( LMain['humidity'] ); LForecast.Description := JS.toString( LWeather['description'] ); LForecast.PropPrec := trunc( JS.toNumber( LObj['pop'] ) * 100 ); LForecast.Icon := JS.toString( LWeather['icon'] ); FForecasts.Add(LForecast); end; end;
Das Datenmodul definiert eine generische Liste in der Feldvariablen FForecasts auf der alle gelieferten Wetterinformationen abgelegt werden.
Nachdem alle Informationen in die Datenstruktur übernommen wurden, sind zwei Dinge zu tun:
- Speichern der Informationen, damit die Anwendung auch funktioniert, wenn keine Internetverbindung besteht. Es wird dann die am besten zutreffende Information von der letzten Interaktion mit dem Webservice verwendet.
- Aufrufen des Ereignisses OnForecastUpdated. Über dieses Ereignis werden wir die Benutzeroberfläche aktualisieren.
Listing 8
if (FForecasts.Count>0) then begin if ADoStore then begin StoreLastForecastResponse(AResponse); end; if (Assigned(FOnForecastUpdated)) then begin FOnForecastUpdated(nil); end; end;
Da wir die Methode zum Auswerten der JSON-Daten auch verwenden, um zuvor gespeicherte Daten zu laden, zeigt die Variable ADoStore an, ob die Daten gespeichert werden müssen.
Speichern der Daten
Der Webbrowser hat im Normalfall keinen Zugriff auf das System, auf dem die Webseite angezeigt wird. Als Lösung wird in jedem Webbrowser das Local Storage bereitgestellt. Es funktioniert wie eine INI-Datei, dass einem Schlüssel, der über eine Zeichenkette identifiziert wird, ein Wert zugewiesen werden kann. Für jede Anwendung wird vom Browser ein eigener Speicherbereich eingerichtet. Die Identifizierung erfolgt über die URL der Anwendung.
In unserem Fall müssen wir einen Weg finden, die Antwort des Webservice dort abzulegen. Falls die Anwendung ohne bestehende Internetverbindung ausgeführt wird, können wir so eine vorangegangene Antwort an die soeben vorgestellte Methode zum Auswerten übergeben.
Listing 9
procedure TWeatherServiceManager.StoreLastForecastResponse(AResponse: JSValue); begin TLocalStorage.SetValue( STORAGE_KEY_FORECAST, TJSJSON.stringify(AResponse) ); // as the date/time is only used locally, we can use the local time of the // system TLocalStorage.SetValue( STORAGE_KEY_DT_FORECAST, DateTimeToRFC3339( Now ) ); end;
TMS WEB Core bietet die Klasse TLocalStorage zum Zugriff auf den lokalen Datenspeicher. Die Methode SetValue schreibt einen Wert in einen Schlüssel. Die JSON-Informationen des Webservice können mit einem Trick über stringify als Zeichenkette ebenfalls abgelegt werden. Zudem speichern wir den Zeitpunkt der letzten erfolgreichen Abfrage vom Webservice.
Das Einlesen erfolgt in der Methode LoadLastForecastResponse.
Listing 10
procedure TWeatherServiceManager.LoadLastForecastResponse; var LStoredForecast: String; LJSON: JSValue; begin LStoredForecast := TLocalStorage.GetValue(STORAGE_KEY_FORECAST); if not LStoredForecast.IsEmpty then begin LJSON := TJSJSON.parse(LStoredForecast); // process, but do not store locally - it's already stored ProcessForecastResult(LJSON, False); end; end;
Das Gegenstück zu SetValue ist GetValue zum Einlesen von Daten als String. Wurden keine Daten unter dem angefragten Schlüssel abgelegt, so wird eine leere Zeichenkette returniert. Ansonsten können wir mit parse den JSON-Datentyp rekonstruieren und an die Methode zum Auswerten übergeben. Als zweiten Parameter übergeben wir False, da es sich um lokale Werte und nicht um neue Werte des Webservice handelt.
Benutzeroberfläche mit Bootstrap
Die visuellen Komponenten können mit zusätzlichen CSS-Klassen versehen werden, um das Erscheinungsbild an ein gewünschtes Design anzupassen. Eine populäre Bibliothek, die in TMS WEB Core direkt unterstützt wird, ist Bootstrap. Bibliotheken, die von TMS bereitgestellt werden, können über den Library Manager in ein Projekt eingefügt werden. Durch einen Rechtsklick auf das Projekt ist der Bibliotheksmanager im Kontextmenü über die Funktion Manage JavaScript Libraries… zu erreichen (siehe Abbildung 12). Dort können wir eine der Bootstrap-Versionen auswählen, um unsere grafischen Elemente mit CSS-Klassen aus dieser Bibliothek zu versehen. Jede grafische Komponente beinhaltet die Eigenschaft ElementClassName. So kann das Labels txtHeader z.B. durch Wahl der Klasse display-4 in einer Titelschrift dargestellt werden. Damit TMS WEB Core weiß, ob die Schrifteinstellungen aus dem Objektinspektor oder aus dem CSS-Framework verbindlich sind, ist ElementFont auf efProperty oder efCSS zu setzen. Schauen Sie sich die einzelnen Komponenteneigenschaften im Objektinspektor an, indem Sie auf die einzelnen Komponenten klicken. Bootstrap ist keinesfalls erforderlich, liefert aber gegenüber dem Endbenutzer direkt eine klare Darstellung als Webanwendung. Es ist anzumerken, dass es sich hierbei um einen Zwischenschritt handelt. TMS WEB Core erlaubt auch tiefergehendes, responsives Design mit Bootstrap über die HTML-Datei, die jedem Formular zugeordnet ist. Entsprechende Videos hierzu sind auf dem YouTube-Kanal von TMS oder in der Literatur zu finden [2].
Benutzeroberfläche an Datenmodul anbinden
In der Beschreibung der Methoden des Datenmoduls wurde bereits klar, dass andere Objekte über Ereignisse informiert werden können, sobald z.B. Daten vom Webservice übermittelt wurden. Wir müssen nun bei Erstellung der Anwendung das Datenmodul erzeugen und die Ereignisse mit Methoden des Hauptformulars verbinden.
Listing 11
procedure TFrmMain.WebFormCreate(Sender: TObject); begin txtHeader.Caption := 'Your Weather Report'; FService := TWeatherServiceManager.Create(self); FService.OnLocationUpdated := OnLocationUpdated; FService.OnForecastUpdated := OnForecastUpdated; FService.LoadLastForecastResponse; UpdateLocation; Application.OnOnlineChange := OnOnlineStatusChanged; end;
Bei Erstellung des Hauptformulars erzeugen wir eine neue Instanz vom Datenmodul TWeatherServiceManager und weisen es der Feldvariablen FService zu. Weiterhin weisen wir die Ereignisse OnLocationUpdated und OnForecastUpdates des Datenmoduls den zugehörigen Methoden im Hauptformular zu. Sofern im lokalen Speicher Informationen abgelegt wurden, werden diese direkt geladen. Das ist auch sinnvoll, wenn eine langsame Internetverbindung besteht. Es werden direkt Informationen angezeigt. Erst wenn wir die aktuelle Position bestimmt haben, können wir die Wetterinformationen abfragen. Daher rufen wir die Methode UpdateLocation auf, die die Positionsbestimmung des Datenmoduls aufruft:
Listing 12
procedure TFrmMain.UpdateLocation; begin if Application.IsOnline then begin FService.UpdateLocation; end; end;
Die Positionsbestimmung kann natürlich nur dann erfolgen, wenn eine Internetverbindung besteht, da anschließend eine Abfrage am Webservice erforderlich ist.
Die letzte Zeile des OnCreate Ereignis des Hauptformulars hat eine besondere Bedeutung. Sobald sich der Status der Internetverbindung ändert, möchten wir informiert werden. Besteht nun eine Verbindung, so soll die Position bestimmt werden. Das Applikationsobjekt einer TMS WEB Core PWA besitzt genau dieses Ereignis, dass über einen Wechsel der Internetverbindung informiert. Das Ereignis wird im Hauptformular zugewiesen und ist wie folgt implementiert:
Listing 13
procedure TFrmMain.OnOnlineStatusChanged( Sender: TObject; AStatus: TOnlineStatus); begin if AStatus = osOnline then begin UpdateLocation; end; end;
Der Parameter AStatus signalisiert eine nun bestehende Verbindung mit osOnline. Es kann dann die Positionsbestimmung aufgerufen werden.
Bei einer erfolgreichen Positionsbestimmung muss der Webservice befragt werden. Wir wissen, dass das Datenmodul das Ereignis OnLocationUpdate aufruft. Dort werden wir dann die Abfrage am Webservice auslösen:
Listing 14
procedure TFrmMain.OnLocationUpdated(Sender: TObject); begin UpdateForecast; end; procedure TFrmMain.UpdateForecast; begin if Application.IsOnline then begin FService.UpdateForecast; end; end;
Erneut ist zu beachten, dass eine Internetverbindung ggf. mittlerweile nicht mehr besteht, so dass wir die Abfrage nur bei bestehender Verbindung einleiten. Sobald die Abfrage erfolgreich war und ausgewertet wurde, wird das Ereignis OnForecastUpdated aufgerufen. Hier können wir nun die Benutzeroberfläche im Hauptformular aktualisieren:
Listing 15
procedure TFrmMain.OnForecastUpdated(Sender: TObject); var LForecast: TWeatherForecast; begin LForecast := FService.CurrentForecast; Icon.URL := LForecast.IconUrl; txtDescription.Caption := LForecast.Description; txtLocationText.Caption := FService.Location.Name + ', ' + FService.Location.Country; txtLocationNumbers.Caption := Format( '(Lat: %.2f, Lon: %.2f)', [ FService.Location.Latitude, FService.Location.Longitude ] ); Grid.Cells[ 0, 0 ] := 'Forecast for'; Grid.Cells[ 1, 0 ] := LForecast.DtReadable; Grid.Cells[ 0, 1 ] := 'Temperature'; Grid.Cells[ 1, 1 ] := LForecast.Temperature.ToString + ' C'; Grid.Cells[ 0, 2 ] := 'Humidity'; Grid.Cells[ 1, 2 ] := LForecast.Humidity.ToString + '%'; Grid.Cells[ 0, 3 ] := '% of Precipation'; Grid.Cells[ 1, 3 ] := LForecast.PropPrec.ToString + '%'; end;
Das Datenmodul liefert über CurrentForecast die zutreffenden Wetterinformationen in der selbst definierten Datenstruktur TWeatherForecast zurück. Wir können somit mit bekannten Mitteln die Informationen in ein Grid einfügen und die Labels mit Informationen befüllen. Beachten Sie, dass wir der TWebImageControl-Komponente eine URL zuweisen können und das zugehörige Bildmaterial automatisch aus dem Web geladen wird. Wir stellen auch sicher, dass immer Wetterinformationen geliefert werden. Der Trick ist außerdem, dass wir an dieser Stelle nicht mehr unterscheiden müssen, ob wir online oder offline sind. CurrentForecast liefert Daten aus der Liste des Datenmoduls zurück, die entweder mit den Angaben aus dem lokalen Speicher oder vom Webservice gefüttert werden.
Listing 16
function TWeatherServiceManager.GetCurrentForecast: TWeatherForecast; var LIndex: Integer; LForecast: TWeatherForecast; begin Result := nil; LIndex := 0; // This simple algorithm works because the forecast items are returned in // order. The first index in the list denotes the most current // weather forecast // whereas the last index is the most distant in the future. // For the particular endpoint chosen this means 3 days out. while LIndex < FForecasts.Count do begin LForecast := FForecasts[LIndex]; if UniversalTimeToLocal(LForecast.Dt) > Now then begin Result := LForecast; break; end; Inc(LIndex); end; if Result = nil then begin Result := FForecasts.Last; end; end;
Die aktuelle Vorhersage wird gemäß dem aktuellen Datum zurückgeliefert. Existiert keine aktuelle Angabe, da z.B. sehr lange keine Internetverbindung mehr bestand oder Sie die Anwendung lange nicht mehr gestartet haben, so wird immerzu die letzte Information angezeigt. Die Daten vom Webservice werden in zeitlich aufsteigender Reihenfolge zurückgeliefert, so dass die Elemente in der Liste stets sortiert sind.
Das komplettiert die Beschreibung der Anwendung. Alle anderen Funktionen werden über die PWA-Vorlage bereitgestellt.
Prüfen der Anwendung
Als Kind habe ich bereits gelernt, dass Vertrauen gut ist, aber Kontrolle meist besser. Es ist unmöglich auf allen existierenden Geräten zu prüfen, ob ihre Webanwendung alle PWA-Anforderungen erfüllt. Google Chrome Lighthouse wurde zunächst von Google als eine Erweiterung für den Chrome Webbrowser angeboten, die Anwendungen prüft. Mittlerweile ist diese Funktion sogar in der Entwicklerkonsole in der Kategorie Lighthouse vollständig integriert und erfordert keinen zusätzlichen Download mehr.
Die Prüfung der hier vorgestellten Anwendung verläuft positiv (siehe Abbildung 13).
Zusammenfassung
Dieser Artikel hat Sie in die Welt der Progressive Web Applications (PWA) eingeführt und gezeigt, wie schnell und effizient Sie Anwendungen von diesem Typ mit TMS WEB Core entwickeln können. Das Ausschlusskriterium, dass Webanwendungen nur mit bestehender Internetverbindung genutzt werden können, wird neben vielen weiteren Blockaden aus dem Weg geräumt. Die Anwendung, die hier vorgestellt wurde, ist unter der URL https://www.tmssoftware.com/pwa/weather/ erreichbar. Probieren Sie sie ruhig einmal aus und fügen sie auf einem Ihrer Mobilgeräte hinzu. Den Quelltext finden Sie auf GitHub unter https://github.com/holgerflick/weatherpwa.
Literatur
Als weiterführende Literatur ist zunächst die ausführliche Dokumentation von TMS anzuführen. Dem Produkt liegt ein PDF-Dokument mit über 700 Seiten bei. Dazu habe ich ein Buch geschrieben mit dem Titel „TMS WEB Core: Web Application Development with Delphi: Rapid Application Development for the Web“ [3] in der zweiten Auflage. Mit dem Buch kann jeder Delphi Entwickler anhand von detailliertem, praxisnahem Beispiel die Entwicklung von Webanwendungen in der Tiefe erlernen. Dabei wird der komplette Prozess von der Installation bis zur Veröffentlichung auf Webservern und sogar in Docker Containern vorgestellt.
Links & Literatur
[1] https://getbootstrap.com/
[2] https://www.youtube.com/tmssoftwareTV
[3] https://flixengineering.com/books
Flick_pwa_1.png
Abb. 1: PWA-Anwendungen sind auf mobilen Geräten nicht von nativen Anwendungen auf dem Home Screen zu unterscheiden. Ein Icon kann mit jeder Anwendung in mehreren Auflösungen bereitgestellt werden.
Flick_pwa_2.png
Abb. 2: Auf dem Apple iPhone können PWA-Anwendungen über die Funktion ‘Teilen’ zum Home Screen hinzugefügt werden. Anstatt, dass man ein Lesezeichen im Webbrowser erstellt, wird über die Funktion Add to Home Screen ein Symbol auf dem Home Screen erstellt.
Flick_pwa_3.png
Abb. 3: Progressive Web Apps können benannt werden. Das Symbol wird zusammen mit der Anwendung vom Entwickler festgelegt und kann vom Benutzer nicht verändert werden.
Flick_pwa_4.png
Abb. 4: Webanwendung zur Anzeige des Wetters an der aktuellen Position. Sollte das Gerät keine Internetverbindung haben, wird die zutreffende Prognose aus dem Speicher geladen.
Flick_pwa_5.png
Abb. 5: Das OpenWeather API dient als Datenquelle für die Wetterinformationen der App.
Flick_pwa_6.png
Abb. 6: Zur Datenabfrage über das OpenWeather API ist ein Benutzerkonto erforderlich. Man kann dann einem Zugangsschlüssel generieren, die bei jeder Anfrage am Webservice zu übertragen ist.
Flick_pwa_7.png
Abb. 7: Vorlage zur Erstellung einer neuen PWA in RAD Studio.
Flick_pwa_8.png
Abb. 8: Anwendungsinformationen können komfortabel in den Projektoptionen eingegeben werden. Die Generierung des Manifests der Anwendung erfolgt automatisch.
Flick_pwa_9.png
Abb. 9: Elemente einer PWA im Projektmanager.
Flick_pwa_10.png
Abb. 10: Das Hauptformular der Anwendung. (1) TWebLabel, (2) TWebImageControl, und (3) TWebTableControl.
Flick_pwa_11.png
Abb. 11: Datenmodul mit Komponenten zur Positionsbestimmung und Kommunikation mit einem Webservice.
Flick_pwa_12.png
Abb. 12: Eine Vielzahl von JavaScript-Bibliotheken kann über den Bibliotheksmanager zu Projekten hinzugefügt werden.
Flick_pwa_13.png
Abb. 13: Google Chrome Lighthouse erstellt einen Bericht, ob Ihre Anwendung die Kriterien für eine PWA erfüllt.