Datenspeicherung lokal und in der Cloud
Mobile Apps entwickeln mit Delphi, Teil 4

In diesem letzten Teil der Delphi-Cross-Plattform-Serie betrachten wir die Speicherung der Daten, das heißt die lokale Speicherung auf dem Gerät und die Cloud-Anbindung über Webservices. Bei einer lokaler Datenspeicherung, zum Beispiel via SQLite auf dem Gerät, liegen alle Daten auf dem Endgerät. Das bietet folgende Vorteile beziehungsweise Use Cases:
- Offline-Verfügbarkeit und Performance: Keine Internetverbindung nötig und sehr schnelle lokale Datenzugriffe.
- Einfache Einrichtung: Kein Server erforderlich; SQLite läuft ohne zusätzliche Konfiguration auf mobilen Plattformen.
Die lokale Speicherung hat auch einige Nachteile:
- Kein Abgleich/Backup: Daten bleiben auf einem Gerät, das heißt, es findet weder eine geräteübergreifende Synchronisation noch ein automatisches Backup statt. Bei einem Verlust des Geräts sind die Daten verloren.
- Sicherheitsrisiko: Die lokale DB liegt unverschlüsselt auf dem Gerät, falls man sie nicht mit einem Passwort schützt.
Als Offline-First bezeichnet man eine Strategie, bei der die App auch ohne Netz voll funktionsfähig ist und Daten zwischenspeichert. Diese werden bei einem späteren Verbindungsaufbau automatisch synchronisiert. Dieses Konzept verbessert die Robustheit und Benutzererfahrung deutlich.
Sollen Daten in der Cloud gespeichert oder mit einem Server synchronisiert werden, geschieht dies meist über REST-Webservices. Die App schickt HTTP-Anfragen (GET/POST/PUT/DELETE) an einen Server und erhält typischerweise JSON-Daten zurück. Delphi stellt dafür die Komponenten TRESTClient, TRESTRequest und TRESTResponse bereit (Bild 1).

REST-Client-Komponenten in Delphi für FireMonkey-Apps (Bild 1)
AutorMan konfiguriert den BaseURL des Dienstes am Client und den Resource-Pfad sowie die HTTP-Methode am Request – zum Beispiel eine POST-Anfrage (/upload) mit JSON-Daten im Body – und führt die Anfrage aus. Die REST-Bibliothek unterstützt auch Authentifizierung, zum Beispiel über die Authenticator-Komponenten (THTTPBasicAuthenticator für Basic-Auth, TOAuth2Authenticator für OAuth2), oder man setzt API-Schlüssel und Tokens direkt als HTTP-Header ein.
Neben dem generischen REST-Ansatz gibt es für bestimmte Cloud-Anbieter vorgefertigte Delphi-Komponenten. Beispielsweise für AWS: Die Klasse TAmazonConnectionInfo nimmt die Zugangsdaten (Access Key ID und Secret Key) auf. Mit TAmazonStorageService (Bild 2) kann man dann auf Amazon S3 zugreifen und zum Beispiel eine Datei hochladen.

Nutzung der AWS-Cloud aus einer Delphi-App (eigene Darstellung) (Bild 2)
AutorFür Backend-as-a-Service-Dienste wie Firebase kann man entweder deren REST-API direkt mit den genannten Komponenten ansprechen oder spezielle Delphi-Bibliotheken nutzen. Ein Vorteil solcher BaaS-Angebote ist, dass auf Client-Seite keine eigene Datenbank erforderlich ist – die gesamte Datenhaltung erfolgt über Web-Service-Aufrufe in der Cloud.
Lokale Speicherung: SQLite mit FireDAC
FireDAC ist Delphis universelle Datenzugriffsbibliothek und bietet schnellen, plattformübergreifenden Zugriff auf zahlreiche Datenbanken – darunter auch SQLite (Bild 3). In mobilen Projekten wird der SQLite-Treiber direkt mit FireDAC ausgeliefert.

Arbeitsweise (Architektur) von FireDAC zur Interaktion mit Datenbanken, unter anderem SQLite (Bild 3)
Embarcadero.comFür die Verwendung genügt eine TFDConnection. Die Konfiguration erfolgt über die Parameter DriverID, Database und LoginPrompt. Der Pfad zur Datenbank muss plattformneutral gesetzt werden.
uses System.IOUtils, FireDAC.Comp.Client; procedure TDataModule1.ConnectToDatabase; begin FDConnection1.Params.Values['DriverID'] := 'SQLite'; FDConnection1.Params.Values['Database'] := TPath.Combine(TPath.GetDocumentsPath, 'photoapp.db'); FDConnection1.LoginPrompt := False; FDConnection1.Connected := True; end;
Der Aufruf TPath.GetDocumentsPath sorgt dafür, dass die Datenbank im Dokumentenverzeichnis der App liegt – auf iOS im App-spezifischen Documents-Ordner, auf Android im internen App-Speicher. FireDAC legt die Datei automatisch an, wenn sie nicht existiert.
Ist die Verbindung hergestellt, kann per SQL-Befehl die Tabellenstruktur erzeugt werden. Die Ausführung erfolgt zum Beispiel im AfterConnect-Ereignis:
FDConnection1.ExecSQL( 'CREATE TABLE IF NOT EXISTS PhotoEntries (' + 'ID INTEGER PRIMARY KEY AUTOINCREMENT, ' + 'ImagePath TEXT NOT NULL, ' + 'Latitude REAL, Longitude REAL, ' + 'Comment TEXT, ' + 'TimeStamp REAL)' );
Damit wird die Tabelle nur angelegt, wenn sie noch nicht existiert.
Für das Schreiben von Datensätzen verwendet man ExecSQL, für das Lesen TFDQuery. FireDAC unterstützt parametrisierte SQL-Abfragen, was sowohl sicher als auch performant ist.
procedure TDataModule1.SaveEntry(const APath, AComment: string; const ALat, ALon: Double); begin FDConnection1.ExecSQL( 'INSERT INTO PhotoEntries (ImagePath, Latitude, Longitude, Comment, TimeStamp) ' + 'VALUES (:Path, :Lat, :Lon, :Comment, :Time)', [APath, ALat, ALon, AComment, Now] ); end;
Zum Lesen kann TFDQuery genutzt werden:
FDQuery1.SQL.Text := 'SELECT * FROM PhotoEntries'; FDQuery1.Open; while not FDQuery1.Eof do begin Memo1.Lines.Add(FDQuery1.FieldByName('Comment').AsString); FDQuery1.Next; end;
Einige Hinweise:
- Deployment: Wenn eine SQLite-Datei mit der App ausgeliefert werden soll, muss sie im Bereitstellungsmanager eingetragen werden – bei iOS unter StartUp\Documents, bei Android unter assets\internal.
- LoginPrompt: Immer auf False setzen, da SQLite keine Authentifizierung benötigt.
Mit diesen Schritten lässt sich FireDAC auf mobilen Plattformen einsetzen. Die Kombination mit SQLite ist ideal für Offline-Apps, die Daten lokal verwalten und optional später mit einer Cloud oder einem REST-API synchronisieren.
Datenspeicherung für die Foto-App mit Geolocation
In dieser Beispiel-App wird durch einen Button-Klick ein Foto aufgenommen, während parallel die aktuelle Geo-Position ermittelt wird. Zu jedem Bild kann der Benutzer optional einen Kommentar eingeben. Anschließend werden das Foto, die erfassten Geo-Koordinaten sowie der Kommentar zusammen als Eintrag in einer Liste angezeigt. Diese Daten bleiben zunächst nur im Arbeitsspeicher erhalten. Die Umsetzung wurde in den ersten drei Teilen der Delphi-Cross-Plattform-Serie beschrieben.
Die App soll die Daten nun in einer lokalen SQLite-DB speichern. Dazu sind folgende Schritte notwendig:
- FireDAC einbinden: Im Formular werden die Komponenten TFDConnection (Verbindung zur SQLite-Datenbank), TFDPhysSQLiteDriverLink (Einbindung SQLite-Treiber) und TFDQuery (SQL-Befehle zum Schreiben und Lesen) hinzugefügt.
- Verbindung konfigurieren: Die Datenbankdatei wird im Dokumentenverzeichnis der App angelegt, damit sie auf allen Plattformen nutzbar ist. Beim ersten Start erzeugt FireDAC die Datei automatisch, falls sie noch nicht existiert:
uses System.IOUtils; FDConnection1.Params.Values['DriverID'] := 'SQLite'; FDConnection1.Params.Values['Database'] := TPath.Combine(TPath.GetDocumentsPath, 'PhotoDB.db'); FDConnection1.LoginPrompt := False; FDConnection1.Connected := True;
- Tabelle anlegen: Beim Start der App prüfen wir, ob die Tabelle vorhanden ist, und legen sie gegebenenfalls an. Die Spalten entsprechen den Datenfeldern aus der Datenklasse-Klasse (Bildpfad, Koordinaten, Hinweis, Zeitstempel):
FDConnection1.ExecSQL( 'CREATE TABLE IF NOT EXISTS Photos (' + 'ID INTEGER PRIMARY KEY AUTOINCREMENT, ' + 'ImagePath TEXT NOT NULL, ' + 'Latitude REAL, Longitude REAL, ' + 'Comment TEXT, TimeStamp REAL)' );
- Datensatz speichern: Wenn ein neues Foto aufgenommen wurde (Event TakePhotoFromCameraAction1DidFinishTaking), wird der Eintrag gespeichert. Dazu wird die Methode ExecSQL mit Parametern verwendet:
procedure TDataModule1.SavePhoto(const APhoto: TPhoto); begin FDConnection1.ExecSQL( 'INSERT INTO Photos (ImagePath, Latitude, Longitude, Comment, TimeStamp) ' + 'VALUES (:Path, :Lat, :Lon, :Comment, :Time)', [APhoto.Path, APhoto.Lat, APhoto.Lon, APhoto.Note, APhoto.Timestamp] ); end;
- Daten abrufen: Zum Laden vorhandener Einträge verwenden wir TFDQuery:
FDQuery1.Open('SELECT * FROM Photos ORDER BY TimeStamp DESC'); while not FDQuery1.Eof do begin AddToListView( FDQuery1.FieldByName('ImagePath').AsString, FDQuery1.FieldByName('Latitude').AsFloat, FDQuery1.FieldByName('Longitude').AsFloat, FDQuery1.FieldByName('Comment').AsString ); FDQuery1.Next; end;
- Integration: Beim Start (FormCreate) wird die SQLite-DB initialisiert. Nach jeder neuen Fotoaufnahme ruft man SavePhoto(…) auf. Beim Starten oder Aktualisieren der App werden die gespeicherten Datensätze per TFDQuery geladen und über RefreshListView angezeigt.
Damit wird aus der bisher flüchtigen In-Memory-Sammlung eine persistente Datenspeicherung. Den Projektfortschritt kann man auf GitHub verfolgen und dort auch den Quellcode herunterladen.
Synchronisation, Sicherheit, Strategien
Beachten Sie die folgenden Hinweise bei der Speicherung von Daten (lokal/Cloud) in mobilen Apps:
- Datenabgleich und Konflikte: Wenn Daten bidirektional synchronisiert werden, der Server also Daten ändern kann, die der Client auch offline bearbeitet hat, braucht man eine Strategie zur Konfliktauflösung. Oft wird Last-Write-Wins verwendet (der letzte Stand gewinnt), oder es werden Versionsnummern beziehungsweise Uhrzeiten geprüft, um Änderungen zu erkennen. In komplexen Fällen muss der Nutzer entscheiden, welche Version gilt, oder es werden Änderungen zusammengeführt.
- Sicherheitsaspekte: Sämtliche Datenübertragung sollte verschlüsselt (HTTPS) erfolgen. In der App sollten keine Passwörter im Klartext gespeichert sein. Stattdessen arbeitet man mit Tokens, zum Beispiel OAuth2-Access-Token oder API-Key. Diese werden sicher gespeichert (verschlüsselt) und bei jedem Request gesendet. Geheimnisse wie API-Schlüssel sollten nie im App-Code hinterlegt sein.
- Synchronisationsstrategien: Die Synchronisation kann automatisch im Hintergrund erfolgen oder manuell auf Anforderung. Oft ist ein hybrider Ansatz sinnvoll, das heißt eine periodische Hintergrund-Synchronisierung kombiniert mit einer manuellen Synchronisations-Option.
Mobile Apps sollten in der Regel nicht direkt auf Datenbankserver zugreifen, denn die Stabilität der Netzwerkverbindung genügt meist nicht, und ebenso sprechen die genannten Sicherheitsaspekte dagegen. Eine Zwischenschicht (Middleware) beziehungsweise ein Backend (BaaS) sind besser geeignete Lösungsansätze.
Fazit und Ausblick
Mit der lokalen Speicherung in SQLite und der (optionalen) Cloud-Anbindung über REST-APIs rundet Delphi das Spektrum mobiler App-Entwicklung ab. FireDAC bietet dabei eine Basis für persistente Datenhaltung, während die REST-Komponenten für die Cloud-Speicherung dienen.
Insgesamt demonstriert die vierteilige Artikelreihe, dass Delphi mit FireMonkey eine leistungsfähige und effiziente Entwicklungsumgebung für plattformübergreifende Projekte ist. Entwickler profitieren von nativer Performance, einer einheitlichen Codebasis und klassischen RAD-Stärken.