MySQL wird mobil

Firebase und MySQL: ein ideales Paar für mobile Apps
Keine Kommentare

Mobile Anwendungen werden heutzutage oft auf Basis einer Mobile-Backend-as-a-Service-Lösung (MBaaS) erstellt. Diese bieten auf den ersten Blick Rundumsorglospakete an, die einen schnellen Einstieg in die Entwicklung ermöglichen sollen. In vielen Fällen werden mobile Anwendungen aber nicht auf der grünen Wiese erstellt, sondern müssen in bestehende Anwendungslandschaften eingebettet werden. Google Firebase ist eine MBaaS-Lösung, die eine einfache Anbindung externer System ermöglicht.

Im Jahr 2016 hat Google bei seiner Entwicklerkonferenz Google I/O angekündigt, Firebase zu einer universellen Plattform für die Entwicklung von Anwendungen für Android, iOS und das Web zu machen. Nach dieser Ankündigung erwachte die 2014 gekaufte Plattform aus ihrem Dornröschenschlaf und wird seither regelmäßig durch neue Features erweitert. Die Firebase-Dienste werden von Google gehostet und als Mobile Backend as a Service (MBaaS) zur Verfügung gestellt. Firebase-basierte Anwendungen laufen somit vollständig in der Cloud, man benötigt keine eigene Hardware. Für Firebase gibt es drei verschiedene Preismodelle , wobei man auch mit der kostenlosen Version schon vernünftig arbeiten kann. Außerdem bietet Firebase eine Vielzahl leicht zu nutzender APIs an und ist deshalb immer dann ein geeigneter Kandidat, wenn man schnell und mit geringen Einstiegskosten einen Proof of Concept (PoC) für eine mobile Anwendung erstellen möchte. Bei der produktiven Nutzung sorgt dann die zugrunde liegende Google-Infrastruktur für eine hohe Verfügbarkeit und eine gute Skalierbarkeit.

Bei der Entwicklung einer mobilen Anwendung steht man oft vor der Aufgabe, auch auf Daten aus einer schon bestehenden Anwendungslandschaft zuzugreifen. Abbildung 1 zeigt schematisch eine mögliche Problemstellung: Eine schon existierende Anwendung speichert ihre Daten in einer MySQL-Datenbank, und von einem mobilen Endgerät aus soll auf diese Daten zugegriffen werden.

Abb. 1: Die Anbindung einer mobilen Anwendung an eine MySQL-Datenbank als Problemstellung

Abb. 1: Die Anbindung einer mobilen Anwendung an eine MySQL-Datenbank als Problemstellung

Abb. 2: Der Lösungsansatz für das in Abbildung 1 dargestellte Problem

Abb. 2: Der Lösungsansatz für das in Abbildung 1 dargestellte Problem

Der Lösungsansatz (Abb. 2) beruht darauf, dass die Firebase-Datenbank sowohl Daten speichern als auch angebundene Clients in Echtzeit über Änderungen in der Datenbank informieren kann. Für die Entwicklung des Android-Clients und des Firebase-Adapters für die MySQL-Datenbank wird Java als Programmiersprache zusammen mit den entsprechenden Java-Bibliotheken für Firebase genutzt. Da die für Firebase erhältlichen SDKs jedoch alle ein identisches API-Design verwenden, können die hier vorgestellten Ideen auch auf andere Programmiersprachen übertragen werden.

Eine App mit MySQL-Zugriff

Als Beispiel für die Nutzung der gezeigten Idee wollen wir uns eine Android-App anschauen, die auf eine lokal betriebene MySQL-Datenbank zugreift. Die App ist sehr einfach gehalten, eignet sich aber gut, um alle wichtigen Aspekte der Anbindung zu demonstrieren. Abbildung 3 zeigt die App nach dem Start auf einem Nexus-6-Smartphone.

Abb. 3: Eine einfache Test-App

Abb. 3: Eine einfache Test-App

Im oberen Bereich gibt es drei Radio-Buttons, über die man den Typ der auszuführenden Datenbankabfrage vorgibt. Man kann zwischen einem einfachen SQL Statement, einem JDBC Prepared Statement und einer Stored Procedure wählen. Unter dem Bereich erscheinen dann das aktuelle Statement sowie zwei Eingabefelder für gegebenenfalls benötigte Eingabeparameter. Wird ein Radio-Button ausgewählt, so werden die drei Textfelder automatisch mit sinnvollen Testdaten gefüllt. Das dann folgende Feld mit dem Namen „Message“ wird während des Aufrufs der Datenbankabfrage aktualisiert. Unter dem Message-Feld befindet sich eine Liste in Form einer Android ListView, die mit dem Resultat der Abfrage gefüllt wird. Durch den Submit-Button am unteren Ende wird die Datenbankabfrage an den Server geschickt. Als Beispieldatenbank wurde der Einfachheit halber eine auf MySQL portierte Version der Microsoft-Northwind-Datenbank verwendet.

Abb. 4: Die Resultate zweier Datenbankabfragen in der App

Abb. 4: Die Resultate zweier Datenbankabfragen in der App

Abbildung 4 zeigt die App einmal nach der Ausführung des einfachen SQL Statements und einmal nach dem Aufruf der Stored Procedure. Die Resultate der Abfrage sind jeweils als JSON-Objekte (JavaScript Object Notation) formatiert und werden in der ListView dargestellt. Bei der ersten Abfrage erhält man die Anzahl der Einträge in der Tabelle CUSTOMERS, die zweite liefert eine Liste der Angestellten aus London. Man kann somit prinzipiell beliebige Datenbankabfragen von einem mobilen Endgerät an eine MySQL-Datenbank abschicken. Da die dahinterstehende Implementierung eng mit der von Firebase zur Verfügung gestellte Realtime- oder Echtzeitdatenbank verbunden ist, wollen wir zunächst einen Blick auf diese Datenbank werfen.

Die Firebase-Realtime-Datenbank

Die Firebase-Datenbank stellt dem Entwickler drei verschiedene Dienste zur Verfügung:

  1. Speicherung der Daten in der Cloud.
  2. Asynchrone Durchführung von Lese- und Schreiboperationen bei gleichzeitiger Zwischenspeicherung in einer lokalen Datenbank
  3. Benachrichtigung bei Änderungen an der Datenbank in Echtzeit

Punkt 1 stellt die Kernaufgabe einer Datenbank dar. Firebase nutzt dabei eine JSON-basierte NoSQL-Datenbank. Der zweite Punkt vereinfacht dem Entwickler das Leben ungemein, da für ihn die Online-/Offlineproblematik nicht mehr besteht. Er muss sich in seinem Quelltext nicht mehr darum kümmern, ob die Cloud-Datenbank erreichbar ist bzw. ob die Netzwerkverbindung gut oder schlecht ist. Eine Schreiboperation erfolgt sofort in die lokale Datenbank, und die verwendete Firebase-Bibliothek kümmert sich darum, dass, sobald eine Netzwerkverbindung zur Verfügung steht, die Daten an die Firebase-Datenbank in der Cloud weitergereicht werden. Dieses Feature steht nur für die mobilen Versionen des Firebase SDK zur Verfügung.

Erfasst eine App zum Beispiel die von einem Servicetechniker bei der Wartung eines Fahrzeugs durchgeführten Arbeiten, so kann die Erfassung offline in einem Raum ohne Internetanbindung erfolgen. Sobald die App wieder eine Verbindung hat, werden die ausstehenden Schreiboperationen durchgeführt. Analog werden Leseoperationen behandelt. Besteht keine Netzwerkverbindung, so werden zunächst die lokal gespeicherten Daten zurückgegeben. Durch die Benachrichtigung bei Änderungen ist sichergestellt, dass man immer möglichst aktuelle Daten auf seinem Endgerät hat.

Da Firebase seine Daten in Form von JSON-Objekten speichert, gibt es im Vergleich zu einer SQL-Datenbank keine Tabellen und keine Views. Die Datenbank ist ein riesiger JSON-Baum, in dessen Knoten die Daten gespeichert sind. Abbildung 5 zeigt den Baum für die im Artikel verwendete Datenbank.

Abb. 5: JSON-Baum der verwendeten Firebase-Datenbank

Abb. 5: JSON-Baum der verwendeten Firebase-Datenbank

Unter dem Knoten db_statement speichert man Vorlagen für SQL-Abfragen, während der Knoten queue Informationen über abzuarbeitende Anfragen an die Datenbank enthält. Die Daten der Anwender sind unter user_data zu finden, wobei für die Knoten auf der folgenden Ebene dann die User-ID (in diesem Fall gFkm2CB0UjSh2J3qYzFtw95bQHa2) des Nutzers verwendet wird. Die nächste Ebene beinhaltet schließlich das JSON-Objekt für die Datenbankabfrage. Der Knoten user_rights wird für die Verwaltung von Benutzerrechten genutzt. Dadurch, dass jeder User für seine Daten einen eigenen Teilbaum in der Datenbank hat, kann man durch die Firebase Security Rules serverseitig sicherstellen, dass jeder User nur auf seine eigenen Daten zugreifen darf.

Authentifizierung

Firebase liefert eine vollständige Benutzerverwaltung, die verschiedene Authentifizierungsoptionen bietet. Man kann zwischen einer „klassischen“ Anmeldung (per Nutzername in Kombination mit Passwort) und der Nutzung anderer Provider wählen, wobei zur Zeit Google, Facebook, Twitter und GitHub unterstützt werden. Die Bestätigung eines Users kann wahlweise per SMS oder per Mail erfolgen.

Sicherheit durch Security Rules

Die Berechtigungen für das Lesen und Schreiben der Daten werden durch in JSON-Form definierte Sicherheitsregeln (Security Rules) definiert. Die Regeln werden serverseitig gespeichert, und man kann auch Regeln vorgeben, die den Inhalt von Daten vorgeben bzw. validieren. Jeder Lese- und Schreibvorgang wird nur dann durchgeführt, wenn mindestens eine Regel existiert, die den Zugriff auf einen Knoten erlaubt. Listing 1 zeigt einen Ausschnitt der Regeln, die bei der hier genutzten Datenbank Verwendung finden.

"rules": {
  "user_data" :
    {
      ".read" : "auth.uid === 'mysql-service'",        #1
      ".write" : "auth.uid === 'mysql-service'",       #2
      "$uid": {                                        #3
        ".read" : "$uid === auth.uid",                 #4
        ".write": "$uid === auth.uid && root.child('user_rights').child(auth.uid).child('sql').val() == true"   #5
     }
   }, 
   ... weitere Definitionen
}

Die Zeilen #1 und #2 erlauben dem User mysql-service, jeden beliebigen Knoten unterhalb von user_data zu lesen und zu editieren. In Zeile #3 wird die Möglichkeit genutzt, in den Regeln auf vordefinierte Variablen zuzugreifen. In diesem Fall beginnen dort die Regeln für Knoten, die der User-ID des angemeldeten Benutzers entsprechen, wobei auf die User-ID über die Variable $uid zugegriffen wird. Zeile #4 vergibt die Leserechte für den Knoten und Zeile #5 die Schreibrechte für alle User, die unter dem Knoten user_rights einen entsprechenden Eintrag unter dem Knoten /user_rights/$uid/sql besitzen.

Die Regeln sind ein leistungsfähiges Mittel, um den Schutz und die Integrität der Daten in der Datenbank sicherzustellen, aber sie sind manchmal sehr trickreich in ihrer Definition. Es gibt deshalb einen Simulator, mit dem man Lese- und Schreiboperationen für verschiedene User testen kann. Dem Simulator kann man auch entnehmen, welche Regeln zu dem Resultat geführt haben, und ist somit das Mittel der Wahl bei der Entwicklung der Security Rules. Abbildung 6 zeigt den Simulator bei der Arbeit. Er befindet sich in der webbasierten Verwaltungsumgebung, die von Google für jede Firebase-Datenbank zur Verfügung gestellt wird.

Abb. 6: Die Testumgebung für Security Rules

Abb. 6: Die Testumgebung für Security Rules

Das Programmiermodell für Datenbankzugriffe

Das Programmiermodell für die Firebase-Datenbank unterscheidet sich prinzipiell von dem Request-Response-Modell einer SQL-Datenbank. Dabei gibt es zwei unterschiedliche Aspekte zu berücksichtigen. Da es in der Firebase-Datenbank keine Tabellen gibt, müssen Java-Objekte vor dem Schreiben in eine JSON-Darstellung konvertiert und nach dem Lesen der Daten wieder in ein Java-Objekt umgewandelt werden. Die Regeln, die von den von Google zur Verfügung gestellten Bibliotheken für die Konvertierung verwendet werden, findet man im Internet auf der Webseite von Firebase . In unserer Beispielanwendung werden für die zu speichernden Daten einfache Klassen definiert, die alle Member als public definieren und keine weiteren Methoden besitzen. In diesem Fall liefert der Firebase Serializer/Deserializer genau das benötigte Verhalten. Als Beispiel soll hier die später verwendete Klasse SQLRequest dienen. Wie die Definition der Klasse aussieht, ist in Listing 2 zu sehen.

public class SQLRequest {
  public String dbStatementName;
  public String message;
  public boolean executionFailed;
  public List<String> result;
  public final List<Parameter>;
}

Abbildung 7 zeigt ein in JSON konvertiertes Objekt der Klasse SQLRequest in der Firebase-Datenbank. Hat man besondere Anforderungen an die Serialisierung/Deserialisierung, so kann man über Annotationen das Standardverhalten übersteuern.

Abb. 7: Ein in JSON konvertiertes Java-Objekt in der Datenbank

Abb. 7: Ein in JSON konvertiertes Java-Objekt in der Datenbank

Das Schreiben von Daten

Lesen, Anlegen, Ändern und Löschen von Daten erfolgt prinzipiell immer nach dem gleichen Muster. Man erzeugt eine Datenbankreferenz, die den gewünschten JSON-Knoten beschreibt, und ruft dann für die Datenbankreferenz die gewünschte Methode auf. Da alle Schreib- und Leseoperationen auf der Firebase-Datenbank asynchron durchgeführt werden, erhält man nach dem Aufruf der Operation keinen Rückgabewert, der den Erfolg oder Misserfolg der Operation beschreibt bzw. die gelesenen Daten liefert. Bei Firebase erhält man Rückgabewerte prinzipiell dadurch, dass man der aufgerufenen Methode zusätzlich ein Objekt übergibt, dessen Klasse ein durch das Firebase-API vorgegebenes Interface implementiert. Die Rückmeldung von der Firebase-Datenbank erfolgt dann dadurch, dass zu dem Zeitpunkt, an dem die Operation tatsächlich ausgeführt wird, eine oder mehrere Methoden des Interface aufgerufen werden. Versetzt man sein Smartphone in den Flugmodus und führt dann eine Schreiboperation durch, dann wird diese Operation in der lokalen Firebase-Datenbank vorgemerkt. Der Aufruf der Callback-Funktion wird erst dann durchgeführt, wenn man den Flugmodus wieder verlässt und die Daten tatsächlich in der Cloud-Datenbank gelandet sind. Das folgende Codebeispiel zeigt das zu implementierende Interface für das Schreiben von Daten:

public interface CompletionListener {
  void onComplete(final DatabaseError error, final DatabaseReference ref);
}

Listing 3 bietet eine minimale Implementierung einer Schreiboperation, in der das Interface durch eine anonyme Klasse implementiert wird.

String path = "mein_pfad_in_der_datenbank";

DatabaseReference dbRef = FirebaseDatabase.getInstance().getReference(path);

dbRef.setValue(requestToProcess, new DatabaseReference.CompletionListener() {
  @Override
  public void onComplete(DatabaseError error, DatabaseReference ref) {
    if ( error == null ) {
      // Alles Ok, aktualisiere z.B. das UI
    } else {
      // Hier kommt die Fehlerbehandlung hin
    }
  }
});

Das Lesen von Daten

Möchte man die Daten lesen, die unter einem vorgegebenen Knoten der Datenbank liegen, so erzeugt man eine Datenbankreferenz für den Knoten und fügt an die Datenbankreferenz ein Listener-Objekt. Die Klasse des Listener-Objekts muss entweder das Interface ChildEventListener oder das Interface ValueEventListener implementieren. Das Resultat der Abfrage erhält man dann wiederum asynchron dadurch, dass für den Listener verschiedene Callback-Funktionen aufgerufen werden.

Schaut man sich die in Listing 4 dargestellten Methoden des Interface ChildEventListener an, so sieht man, dass es detaillierte Information zu allen möglichen Operationen auf den JSON-Knoten liefert, die unterhalb des Knotens liegen, der durch die Datenbankreferenz beschrieben wird.

public interface ChildEventListener {
  void onChildAdded(DataSnapshot snapshot, String previousChildName);
  void onChildChanged(DataSnapshot snapshot, String previousChildName);
  void onChildRemoved(DataSnapshot snapshot);
  void onChildMoved(DataSnapshot snapshot, String previousChildName);
  void onCancelled(DatabaseError error);
}

Die zu dem zugehörigen Knoten gehörigen JSON-Daten werden in einem Objekt der Klasse DataSnapshot zurückgegeben. Wie der Name der Klasse DataSnapshot suggeriert, liefert ein Objekt der Klasse eine Momentaufnahme der unter einem Knoten in der Datenbank liegenden JSON-Daten. Auf die Daten in dem Snapshot kann mit den in Listing 5 dargestellten Methoden zugegriffen werden.

public boolean exists() ...
public DataSnapshot child(String path) ...
public boolean hasChild(String path) ...
public boolean hasChildren() ...
public long getChildrenCount() ... 
public DatabaseReference getRef() ...
public String getKey() ...
public <T> T getValue(Class<T> valueType)

Von besonderem Interesse ist hier die generische Methode <T> T getValue(Class<T>), die das Lesen von Objekten sehr komfortabel macht. Kennt man die Klasse des Objekts, das in JSON-Form vorliegt, so erhält man mit dem Aufruf einer einzelnen Methode das vollständig deserialisierte Objekt der passenden Klasse. Für das Lesen erzeugt man zuerst eine Datenbankreferenz und ein Objekt, das das gewünschte Interface implementiert. Dann wird der Listener an die Referenz gefügt:

DatabaseReference dbRef = FirebaseDatabase.getInstance().getReference("queue");
ChildEventListener listener = new QueueChildEventListener();
dbRef.addChildEventListener( listener );

Nach dem Hinzufügen des Listener-Objekts wird zuerst für alle unter dem Knoten der Datenbankreferenz liegenden Subknoten die Methode onChildAdded aufgerufen, und man erhält so Informationen über den aktuellen Datenbestand. Alle später folgenden Änderungen triggern dann den Aufruf der zugehörigen Callback-Funktion. Der Aufruf der Callback-Funktionen wird erst dann wieder beendet, wenn der ChildEventListener durch den Aufruf von dbRef.RemoveChildEventListener( listener ) von der Datenbankreferenz entfernt wird.

Die zweite Möglichkeit für das Lesen von Daten in der Firebase-Datenbank ist durch das Interface ValueEventListener definiert, das einfacher als das Interface ChildEventListener gehalten ist:

public interface ValueEventListener {
  void onDataChange(DataSnapshot snapshot);
  void onCancelled(DatabaseError error);
}

ValueEventListener kann man mit den Methoden addListenerForSingleEvent und addValueEventListener an eine Datenbankreferenz hinzufügen. Bei der ersten Methode wird genau einmal ein DataSnapshot-Objekt zurückgegeben, bei der zweiten Methode werden nach dem ersten Aufruf des Callbacks auch weitere Änderungen an das Callback-Objekt geliefert.

Kostenlos: Das iJS React Cheat Sheet

Sie wollen mit React durchstarten?
Unser Cheatsheet zeigt Ihnen alle wichtigen Snippets auf einen Blick.
Jetzt kostenlos herunterladen!

Download for free

 

API Summit 2018

From Bad to Good – OpenID Connect/OAuth

mit Daniel Wagner (VERBUND) und Anton Kalcik (business.software.engineering)

Die Programmierung der Datenbank per Listener und Callback-Funktionen funktioniert nach einer Zeit der Eingewöhnung sehr intuitiv. Die wichtigste Aufgabe besteht darin, die Struktur der Datenbank so zu definieren, dass man sowohl die Anzahl der aufgerufenen Callback-Funktionen als auch gleichzeitig die in den Snapshots übergebenen Daten klein hält.

Die Details

Nachdem nun die Programmierung der Firebase-Datenbank im Schnelldurchlauf gezeigt wurde, werden jetzt die Details der Implementierung des Zugriffs auf die MySQL-Datenbank dargestellt. Abbildung 8 skizziert den Ablauf einer Anfrage an die Datenbank. Die einzelnen Aktionen sind nummeriert und mit # versehen. Schreiboperationen auf die Firebase-Datenbank haben eine grüne Nummer, Leseoperationen eine blaue Nummer. Im ersten Schritt (#1) schreibt die mobile Anwendung die Daten des SQLRequests (Listing 2) in die Datenbank.

Abb. 8: Der Zugriff auf die SQL-Datenbank

Abb. 8: Der Zugriff auf die SQL-Datenbank

Zusätzlich wird noch ein Objekt der Klasse QueueItem (Listing 6) unter dem Pfad /queue in die Datenbank geschrieben. Die QueueItem-Klasse ist sehr einfach aufgebaut, wie das folgende Beispiel zeigt:

public class QueueItem {
  public boolean isProcessed;
  public String dbRefToProcess;
}

Wie man sieht, besitzt die Klasse ein Flag, das zeigt, ob das QueueItem schon verarbeitet wurde und speichert den Pfad des zu verarbeitenden Requests. Durch das Schreiben der QueueItem-Objekte an einer zentralen Stelle in der Datenbank braucht der MySQL-Adapter nur auf ChildEvents für den Pfad /queue horchen.

Das Schreiben des QueueItem-Objekts triggert bei dem MySQL-Adapter den Aufruf einer Callback-Funktion (#2), die wiederum das zugehörige SQLRequest-Objekt liest (#3). Der Adapter generiert aus dem SQLRequest-Objekt einen JDBC-Aufruf in der MySQL-Datenbank (#4) und schreibt das Resultat zurück in die Datenbank (#5). Der mobile Client wird dann per Callback (#6) über die Änderung seines SQLRequest-Objekts informiert und aktualisiert das User Interface.

Die Android-App

Nachdem die Programmierung der Firebase-Echtzeitdatenbank vorgestellt wurde, kommen wir nun zur eigentlichen App. Die Entwicklung erfolgte mit Android Studio 3.0, das den Build-Prozess über Gradle steuert. Damit innerhalb des Android-Projekts die benötigten Firebase-Bibliotheken zur Verfügung stehen, fügt man in der Datei build.gradle des Projekts im Bereich dependencies folgende Einträge hinzu:

compile 'com.google.firebase:firebase-database:11.0.4'
compile 'com.google.firebase:firebase-auth:11.0.4'
compile 'com.firebaseui:firebase-ui-auth:2.3.0'

Die Implementierung der App lehnt sich an das MVP-Pattern an. Es ergibt sich somit die in Abbildung 9 gezeigte Gliederung der Anwendung in die drei Komponenten View, Presenter und Model, wobei Model für die Kommunikation mit den Firebase-Services zuständig ist. View und Presenter sind in der Beispielanwendung sehr einfach gehalten, sodass hier nur ein Blick auf die Modelkomponente und den Zugriff auf die Firebase-Services geworfen werden.

Abb. 9: Gliederung der App in View, Presenter und Model

Abb. 9: Gliederung der App in View, Presenter und Model

Damit Firebase die Möglichkeit der persistenten lokalen Speicherung nutzt, muss vor dem allerersten Aufruf einer Firebase-Funktion die Methode setPersistenceEnabled(true) der Klasse FirebaseDatabase ausgeführt werden. Dabei ist zu beachten, dass dieser Aufruf nur einmal erfolgen darf. Am einfachsten erreicht man dies, indem man für die App eine von android.app.Application abgeleitete Klasse anlegt und den Aufruf in der onCreate()-Methode der Klasse implementiert (Listing 6). Gleichzeitig wird die Klasse auch genutzt, um die benötigten Informationen zur Authentifizierung zu erhalten und das benötigte Presenter-Objekt anzulegen.

public class M4FApplication extends Application implements FirebaseAuth.AuthStateListener, ValueEventListener, M4FModel {
  @Override
  public void onCreate() {
    super.onCreate();
FirebaseDatabase.getInstance().setPersistenceEnabled(true);
FirebaseAuth.getInstance().addAuthStateListener(this);
presenter = new M4FPresenter(this); }
  ...
}

Die Nutzung der App ist nur dann sinnvoll, wenn sich der Anwender authentifiziert hat und auf die Firebase-Datenbank zugreifen kann. Über das in der Klasse FirebaseAuth definierte Interface AuthStateListener erhält man per Callback die benötigten Informationen. Das Interface definiert die Methode void onAuthStateChanged(FirebaseAuth auth), die über den Methodenparameter alle benötigten Informationen zum Authentifizierungsstatus liefert. In der Methode onAuthStateChanged werden dann die benötigten Datenbankreferenzen zur Firebase-Datenbank angelegt (#1, #2) und das Model als Listener für das SQLRequest-Objekt registriert (#3). Dadurch wird bei jeder Änderung des SQLRequest-Objekts die Methode onDataChange aufgerufen, die das geänderte Objekt an den Presenter übergibt (Listing 7, Listing 8). Der Presenter aktualisiert dann das User Interface.

public void onAuthStateChanged(@NonNull FirebaseAuth firebaseAuth) {
  Log.i(TAG,"authStateChanged()");
  this.firebaseUser = firebaseAuth.getCurrentUser();
  if( this.firebaseUser == null ) {
    if ( dbReferenceRequest != null ) {
      dbReferenceRequest.removeEventListener(this);
    }
  } else {
    FirebaseDatabase.getInstance().getReference("db_statement").keepSynced(true);
    dbRefQueueItem = FirebaseDatabase.getInstance().getReference("queue"); //#1
    dbReferenceRequest = FirebaseDatabase.getInstance().getReference("user_data/" + firebaseUser.getUid() + "/sql_request"); //#2
    dbReferenceRequest.addValueEventListener(this); //#3
  }
  presenter.authStateChanged( this.firebaseUser != null  );
}
public void onDataChange(DataSnapshot dataSnapshot) {
  SQLRequest request = dataSnapshot.getValue(SQLRequest.class);
  this.presenter.setSQLRequest(request);
}

Der Firebase-MySQL-Adapter

Der MySQL-Adapter von Firebase ist ein Standalone-Java-Programm, das in diesem Fall auf dem gleichen Rechner läuft, auf dem auch die MySQL-Datenbank installiert ist. Das Programm ist sehr einfach gehalten. Nach dem Start erstellt es eine Datenbankreferenz auf den Knoten queue in der Firebase-Datenbank und hängt ein Objekt der Klasse QueueListener an die Datenbankreferenz. Fügt ein mobiler Client jetzt ein Objekt der Klasse QueueItem unter dem Knoten queue ein, so wird die Ausführung der Methode onChildAdded der Klasse QueueListener angestoßen. Damit kann man jetzt von einem mobilen Client aus eine serverseitige Methode aufrufen. Die Klasse QueueItem beinhaltet nur den Pfad des Objekts, das bei dem Methodenaufruf modifiziert werden soll. Die eigentliche Funktionalität für das Ansprechen der MySQL-Datenbank per JDBC (Java Database Connectivity) ist in der Klasse QueueEventListener innnerhalb der Methode onChildAdded implementiert (Listing 9).

@Override
public void onChildAdded(DataSnapshot snapshot, String previousChildName) {

#1 QueueItem queueItem = snapshot.getValue(QueueItem.class); 
#2 final DatabaseReference dbReference = FirebaseDatabase.getInstance().getReference(queueItem.dbRefToProcess);
#3 dbReference.addListenerForSingleValueEvent( new ValueEventListener() {

@Override
public void onDataChange(DataSnapshot snapshot) {
#4 SQLRequest request = snapshot.getValue(SQLRequest.class);
  try {
    request.message = "Try to connect ...";
    dbReference.setValue(request);
    #5  Connection con = MySqlProcessor.connect();
    #6  MySqlProcessor.executeStatement(FirebaseMySQLClient.dbStatements, request, con);
FirebaseDatabase.getInstance().getReference(request.dbPath).setValue(request);
if (!request.executionFailed) {
request.message = "Success!";
}
} catch (Throwable e) {
request.message = e.getMessage();
request.executionFailed = true;
logger.error(e.getMessage(), e);
} finally {
dbReference.setValue(request);
DatabaseReference dbQueueItem = FirebaseDatabase.getInstance().getReference(queueItem.dbRef);
dbQueueItem.removeValue();
}
}

Im ersten Schritt (#1) holt man sich das QueueItem-Objekt, das den Aufruf der Methode verursacht hat. Danach kommt das SQLRequest-Objekt, auf das das QueueItem-Objekt zeigt (#2, #3 und #4). Nach dem Öffnen einer Datenbankverbindung wird das SQLRequest-Objekt mit dem Aufruf der statischen Methode executeStatement der Klasse MySqlProcessor verarbeitet. In der Methode wird in Abhängigkeit vom Typ des auszuführenden Befehls die entsprechende JDBC-Funktionalität ausgeführt. Erhält man als Ergebnis ein JDBC-ResultSet, so werden die einzelnen Zeilen in eine JSON-Darstellung konvertiert und als Ergebnis dem SQLRequest-Objekt übergeben. Das geänderte SQLRequest-Objekt wird wieder in die Firebase-Datenbank geschrieben. Da die App einen Listener für den Knoten des SQLRequests registriert hat, wird das UI der App sofort aktualisiert, wenn sich die lokale Datenbank mit der Firebase-Datenbank synchronisieren kann.

Fazit

Dieser Artikel hat einen Überblick über die Programmierung der Firebase-Echtzeitdatenbank gegeben und gezeigt, wie man mit einfachen Mitteln eine MySQL-Datenbank an eine mobile Anwendung anbinden kann. Mit den zur Verfügung gestellten Quellen der Anwendung auf GitHub (MySQL4FirebaseApp , MySQL4Firebase) und der sehr guten Firebase-Dokumentation sollten sich die wichtigsten Ideen problemlos in eine eigene Anwendung übernehmen lassen.

Entwickler Magazin

Entwickler Magazin abonnierenDieser Artikel ist im Entwickler Magazin erschienen.

Natürlich können Sie das Entwickler Magazin über den entwickler.kiosk auch digital im Browser oder auf Ihren Android- und iOS-Devices lesen. In unserem Shop ist das Entwickler Magazin ferner im Abonnement oder als Einzelheft erhältlich.

Unsere Redaktion empfiehlt:

Relevante Beiträge

Hinterlasse einen Kommentar

Hinterlasse den ersten Kommentar!

avatar
400
  Subscribe  
Benachrichtige mich zu:
X
- Gib Deinen Standort ein -
- or -