Daten auf Android lesen und schreiben

Dieses Dokument behandelt die Grundlagen zum Lesen und Schreiben von Firebase-Daten.

Feuerbasis Daten werden auf eine schriftlichen FirebaseDatabase Referenz und abgerufen durch einen asynchronen Zuhörer auf die Referenz befestigen. Der Listener wird einmal für den Anfangszustand der Daten und jedes Mal, wenn sich die Daten ändern, getriggert.

(Optional) Prototyp und Test mit Firebase Local Emulator Suite

Bevor wir darüber sprechen, wie Ihre App aus der Echtzeitdatenbank liest und in sie schreibt, stellen wir eine Reihe von Tools vor, mit denen Sie die Funktionalität von Echtzeitdatenbanken testen und testen können: Firebase Local Emulator Suite. Wenn Sie verschiedene Datenmodelle ausprobieren, Ihre Sicherheitsregeln optimieren oder nach der kostengünstigsten Möglichkeit suchen, mit dem Back-End zu interagieren, kann es eine gute Idee sein, lokal zu arbeiten, ohne Live-Dienste bereitzustellen.

Ein Echtzeit-Datenbank-Emulator ist Teil der Local Emulator Suite, die es Ihrer App ermöglicht, mit Ihren emulierten Datenbankinhalten und -konfigurationen sowie optional mit Ihren emulierten Projektressourcen (Funktionen, andere Datenbanken und Sicherheitsregeln) zu interagieren.

Die Verwendung des Echtzeit-Datenbank-Emulators umfasst nur wenige Schritte:

  1. Hinzufügen einer Codezeile zur Testkonfiguration Ihrer App, um eine Verbindung zum Emulator herzustellen.
  2. Von der Wurzel Ihrer lokalen Projektverzeichnis, laufen firebase emulators:start .
  3. Tätigen von Aufrufen aus dem Prototypcode Ihrer App wie gewohnt mit einem Realtime Database Platform SDK oder mit der Realtime Database REST API.

Eine detaillierte Komplettlösung beteiligt Realtime - Datenbank und Cloud - Funktionen zur Verfügung. Sie sollten auch einen Blick auf die haben Local Emulator Suite Einführung .

Holen Sie sich eine Datenbank-Referenz

Zum Lesen oder Schreiben von Daten aus der Datenbank, müssen Sie eine Instanz von DatabaseReference :

Java

private DatabaseReference mDatabase;
// ...
mDatabase = FirebaseDatabase.getInstance().getReference();

Kotlin+KTX

private lateinit var database: DatabaseReference
// ...
database = Firebase.database.reference

Daten schreiben

Grundlegende Schreibvorgänge

Für grundlegende Schreiboperationen, können Sie setValue() Daten zu einer bestimmten Referenz zu speichern, alle vorhandenen Daten auf diesem Weg zu ersetzen. Sie können diese Methode verwenden, um:

  • Übergeben Sie Typen, die den verfügbaren JSON-Typen entsprechen, wie folgt:
    • String
    • Long
    • Double
    • Boolean
    • Map<String, Object>
    • List<Object>
  • Übergeben Sie ein benutzerdefiniertes Java-Objekt, wenn die Klasse, die es definiert, über einen Standardkonstruktor verfügt, der keine Argumente akzeptiert und über öffentliche Getter für die zuzuweisenden Eigenschaften verfügt.

Wenn Sie ein Java-Objekt verwenden, wird der Inhalt Ihres Objekts automatisch verschachtelt auf untergeordnete Speicherorte abgebildet. Die Verwendung eines Java-Objekts macht Ihren Code normalerweise auch lesbarer und einfacher zu warten. Zum Beispiel, wenn Sie eine Anwendung mit einem einfachen Benutzerprofil haben, Ihr User könnte wie folgt aussehen:

Java

@IgnoreExtraProperties
public class User {

    public String username;
    public String email;

    public User() {
        // Default constructor required for calls to DataSnapshot.getValue(User.class)
    }

    public User(String username, String email) {
        this.username = username;
        this.email = email;
    }

}

Kotlin+KTX

@IgnoreExtraProperties
data class User(val username: String? = null, val email: String? = null) {
    // Null default values create a no-argument default constructor, which is needed
    // for deserialization from a DataSnapshot.
}

Sie können einen Benutzer mit hinzufügen setValue() wie folgt:

Java

public void writeNewUser(String userId, String name, String email) {
    User user = new User(name, email);

    mDatabase.child("users").child(userId).setValue(user);
}

Kotlin+KTX

fun writeNewUser(userId: String, name: String, email: String) {
    val user = User(name, email)

    database.child("users").child(userId).setValue(user)
}

Mit setValue() auf diese Weise überschreibt Daten an der angegebenen Position, einschließlich aller untergeordneten Knoten. Sie können jedoch weiterhin ein untergeordnetes Objekt aktualisieren, ohne das gesamte Objekt neu zu schreiben. Wenn Sie Benutzern erlauben möchten, ihre Profile zu aktualisieren, können Sie den Benutzernamen wie folgt aktualisieren:

Java

mDatabase.child("users").child(userId).child("username").setValue(name);

Kotlin+KTX

database.child("users").child(userId).child("username").setValue(name)

Daten lesen

Daten mit persistenten Listenern lesen

Um Daten auf einem Pfad zu lesen und für Änderungen hören, verwenden Sie die addValueEventListener() Methode ein hinzuzufügen ValueEventListener zu einem DatabaseReference .

Hörer Ereignisrückruf Typische Verwendung
ValueEventListener onDataChange() Lesen und hören Sie auf Änderungen am gesamten Inhalt eines Pfads.

Sie können die Verwendung onDataChange() Methode eine statische Momentaufnahme der Inhalte zu einem bestimmten Pfad zu lesen, da sie zum Zeitpunkt der Veranstaltung bestanden. Diese Methode wird einmal ausgelöst, wenn der Listener angehängt wird, und jedes Mal, wenn sich die Daten, einschließlich der untergeordneten Elemente, ändern. Dem Ereignisrückruf wird ein Snapshot übergeben, der alle Daten an diesem Speicherort enthält, einschließlich der untergeordneten Daten. Wenn keine Daten vorhanden sind, wird der Snapshot zurückkehren false , wenn Sie anrufen exists() und null , wenn Sie anrufen getValue() auf sie.

Das folgende Beispiel zeigt eine Social-Blogging-Anwendung, die die Details eines Beitrags aus der Datenbank abruft:

Java

ValueEventListener postListener = new ValueEventListener() {
    @Override
    public void onDataChange(DataSnapshot dataSnapshot) {
        // Get Post object and use the values to update the UI
        Post post = dataSnapshot.getValue(Post.class);
        // ..
    }

    @Override
    public void onCancelled(DatabaseError databaseError) {
        // Getting Post failed, log a message
        Log.w(TAG, "loadPost:onCancelled", databaseError.toException());
    }
};
mPostReference.addValueEventListener(postListener);

Kotlin+KTX

val postListener = object : ValueEventListener {
    override fun onDataChange(dataSnapshot: DataSnapshot) {
        // Get Post object and use the values to update the UI
        val post = dataSnapshot.getValue<Post>()
        // ...
    }

    override fun onCancelled(databaseError: DatabaseError) {
        // Getting Post failed, log a message
        Log.w(TAG, "loadPost:onCancelled", databaseError.toException())
    }
}
postReference.addValueEventListener(postListener)

Der Hörer erhält eine DataSnapshot , die die Daten an der angegebenen Stelle in der Datenbank zum Zeitpunkt des Ereignisses enthält. Der Aufruf getValue() auf einem Schnappschuss kehrt die Java - Objekt Darstellung der Daten. Wenn existiert keine Daten an der Stelle, ruft getValue() kehrt null .

In diesem Beispiel ValueEventListener definiert auch die onCancelled() Methode , die , wenn die gelesene aufgerufen wird abgebrochen. Ein Lesevorgang kann beispielsweise abgebrochen werden, wenn der Client keine Berechtigung zum Lesen von einem Firebase-Datenbankspeicherort hat. Diese Methode wird übergeben ein DatabaseError Objekt angibt , warum der Fehler aufgetreten ist .

Daten einmal lesen

Einmal lesen mit get()

Das SDK wurde entwickelt, um Interaktionen mit Datenbankservern zu verwalten, unabhängig davon, ob Ihre App online oder offline ist.

Im Allgemeinen sollten Sie die Verwendung ValueEventListener oben beschriebenen Techniken , Daten zu lesen , um die Daten aus dem Backend über Aktualisierungen benachrichtigt werden. Die Listener-Techniken reduzieren Ihre Nutzung und Abrechnung und sind optimiert, um Ihren Benutzern die beste Erfahrung zu bieten, wenn sie online und offline gehen.

Wenn Sie die Daten nur einmal benötigen, können Sie get() eine Momentaufnahme der Daten aus der Datenbank zu erhalten. Wenn aus irgendeinem Grund get() nicht in der Lage ist , den Server - Wert zurückzukehren, wird der Client den lokalen Speicher - Cache - Sonde und einen Fehler zurück , wenn der Wert noch nicht gefunden wird.

Unnötige Verwendung von get() kann Nutzung der Bandbreite und führt zu Leistungsverlust erhöhen, die unter Verwendung eines Echtzeit - Hörers verhindert werden kann , wie oben gezeigt.

Java

mDatabase.child("users").child(userId).get().addOnCompleteListener(new OnCompleteListener<DataSnapshot>() {
    @Override
    public void onComplete(@NonNull Task<DataSnapshot> task) {
        if (!task.isSuccessful()) {
            Log.e("firebase", "Error getting data", task.getException());
        }
        else {
            Log.d("firebase", String.valueOf(task.getResult().getValue()));
        }
    }
});

Kotlin+KTX

mDatabase.child("users").child(userId).get().addOnSuccessListener {
    Log.i("firebase", "Got value ${it.value}")
}.addOnFailureListener{
    Log.e("firebase", "Error getting data", it)
}

Einmal mit einem Hörer lesen

In einigen Fällen möchten Sie möglicherweise, dass der Wert aus dem lokalen Cache sofort zurückgegeben wird, anstatt auf dem Server nach einem aktualisierten Wert zu suchen. In diesen Fällen können Sie verwenden addListenerForSingleValueEvent die Daten sofort aus dem lokalen Festplatten - Cache zu erhalten.

Dies ist nützlich für Daten, die nur einmal geladen werden müssen und sich nicht häufig ändern oder eine aktive Überwachung erfordern. Die Blogging-App in den vorherigen Beispielen verwendet diese Methode beispielsweise, um das Profil eines Benutzers zu laden, wenn er mit der Erstellung eines neuen Beitrags beginnt.

Aktualisieren oder Löschen von Daten

Spezifische Felder aktualisieren

Um gleichzeitig auf bestimmte Kinder eines Knotens zu schreiben , ohne andere untergeordnete Knoten zu überschreiben, verwenden Sie die updateChildren() Methode.

Beim Aufruf updateChildren() , können Sie auf niedrigerer Ebene Kind Werte aktualisieren , indem Sie einen Pfad für den Schlüssel angeben. Wenn Daten an mehreren Orten gespeichert ist , besser zu skalieren, können Sie alle Instanzen dieser Daten aktualisieren , die Daten Fanout . Zum Beispiel könnte ein soziales Blogging App eine hat Post Klasse wie folgt:

Java

@IgnoreExtraProperties
public class Post {

    public String uid;
    public String author;
    public String title;
    public String body;
    public int starCount = 0;
    public Map<String, Boolean> stars = new HashMap<>();

    public Post() {
        // Default constructor required for calls to DataSnapshot.getValue(Post.class)
    }

    public Post(String uid, String author, String title, String body) {
        this.uid = uid;
        this.author = author;
        this.title = title;
        this.body = body;
    }

    @Exclude
    public Map<String, Object> toMap() {
        HashMap<String, Object> result = new HashMap<>();
        result.put("uid", uid);
        result.put("author", author);
        result.put("title", title);
        result.put("body", body);
        result.put("starCount", starCount);
        result.put("stars", stars);

        return result;
    }
}

Kotlin+KTX

@IgnoreExtraProperties
data class Post(
    var uid: String? = "",
    var author: String? = "",
    var title: String? = "",
    var body: String? = "",
    var starCount: Int = 0,
    var stars: MutableMap<String, Boolean> = HashMap()
) {

    @Exclude
    fun toMap(): Map<String, Any?> {
        return mapOf(
                "uid" to uid,
                "author" to author,
                "title" to title,
                "body" to body,
                "starCount" to starCount,
                "stars" to stars
        )
    }
}

Um einen Beitrag zu erstellen und ihn gleichzeitig auf den aktuellen Aktivitätsfeed und den Aktivitätsfeed des postenden Benutzers zu aktualisieren, verwendet die Blogging-Anwendung folgenden Code:

Java

private void writeNewPost(String userId, String username, String title, String body) {
    // Create new post at /user-posts/$userid/$postid and at
    // /posts/$postid simultaneously
    String key = mDatabase.child("posts").push().getKey();
    Post post = new Post(userId, username, title, body);
    Map<String, Object> postValues = post.toMap();

    Map<String, Object> childUpdates = new HashMap<>();
    childUpdates.put("/posts/" + key, postValues);
    childUpdates.put("/user-posts/" + userId + "/" + key, postValues);

    mDatabase.updateChildren(childUpdates);
}

Kotlin+KTX

private fun writeNewPost(userId: String, username: String, title: String, body: String) {
    // Create new post at /user-posts/$userid/$postid and at
    // /posts/$postid simultaneously
    val key = database.child("posts").push().key
    if (key == null) {
        Log.w(TAG, "Couldn't get push key for posts")
        return
    }

    val post = Post(userId, username, title, body)
    val postValues = post.toMap()

    val childUpdates = hashMapOf<String, Any>(
            "/posts/$key" to postValues,
            "/user-posts/$userId/$key" to postValues
    )

    database.updateChildren(childUpdates)
}

In diesem Beispiel wird push() einen Beitrag in dem Knoten für alle Benutzer enthält Beiträge erstellen /posts/$postid und gleichzeitig den Schlüssel mit abrufen getKey() . Der Schlüssel kann dann verwendet werden , um einen zweiten Eintrag in den Benutzern Beiträge zu erstellen /user-posts/$userid/$postid .

Mit Hilfe dieser Pfade können Sie gleichzeitige Aktualisierungen auf mehrere Standorte im JSON - Struktur mit einem einzigen Aufruf auszuführen updateChildren() , wie , wie dieses Beispiel in den beiden Standorten den neuen Beitrag erstellt. Gleichzeitige Aktualisierungen, die auf diese Weise durchgeführt werden, sind atomar: Entweder sind alle Aktualisierungen erfolgreich oder alle Aktualisierungen schlagen fehl.

Hinzufügen eines Abschlussrückrufs

Wenn Sie wissen möchten, wann Ihre Daten festgeschrieben wurden, können Sie einen Abschluss-Listener hinzufügen. Sowohl setValue() und updateChildren() nimmt einen optionalen Abschluss Listener, der aufgerufen wird , wenn der Schreib erfolgreich in die Datenbank übernommen worden. Wenn der Aufruf nicht erfolgreich war, wird dem Listener ein Fehlerobjekt übergeben, das angibt, warum der Fehler aufgetreten ist.

Java

mDatabase.child("users").child(userId).setValue(user)
        .addOnSuccessListener(new OnSuccessListener<Void>() {
            @Override
            public void onSuccess(Void aVoid) {
                // Write was successful!
                // ...
            }
        })
        .addOnFailureListener(new OnFailureListener() {
            @Override
            public void onFailure(@NonNull Exception e) {
                // Write failed
                // ...
            }
        });

Kotlin+KTX

database.child("users").child(userId).setValue(user)
        .addOnSuccessListener {
            // Write was successful!
            // ...
        }
        .addOnFailureListener {
            // Write failed
            // ...
        }

Daten löschen

Die einfachste Art und Weise zu löschen Daten aufrufen removeValue() auf einem Verweis auf den Speicherort der Daten.

Sie können auch durch Angabe löschen null als Wert für eine weitere Schreiboperation wie setValue() oder updateChildren() . Sie können diese Technik mit Verwendung updateChildren() , um mehrere Kinder in einem einzigen API - Aufruf zu löschen.

Hörer trennen

Rückrufe werden durch den Aufruf der entfernt removeEventListener() Methode auf Firebase Datenbank Referenz.

Wenn einem Datenspeicherort ein Listener mehrmals hinzugefügt wurde, wird er für jedes Ereignis mehrmals aufgerufen, und Sie müssen ihn genauso oft trennen, um ihn vollständig zu entfernen.

Der Aufruf removeEventListener() auf einem übergeordneten Hörer nicht automatisch Hörer entfernen auf seinen untergeordneten Knoten registriert ist ; removeEventListener() muss auch auf alle untergeordneten Hörer aufgerufen werden , um den Rückruf zu entfernen.

Daten als Transaktionen speichern

Bei der Arbeit mit Daten , die durch die gleichzeitigen Änderungen beschädigt werden könnten, wie Inkrementalzähler, können Sie einen verwenden Transaktion Betrieb . Sie geben dieser Operation zwei Argumente: eine Update-Funktion und einen optionalen Abschluss-Callback. Die Update-Funktion nimmt den aktuellen Zustand der Daten als Argument und gibt den neuen gewünschten Zustand zurück, den Sie schreiben möchten. Wenn ein anderer Client in die Position schreibt, bevor Ihr neuer Wert erfolgreich geschrieben wurde, wird Ihre Update-Funktion erneut mit dem neuen aktuellen Wert aufgerufen und der Schreibvorgang wird wiederholt.

In der Beispiel-App für soziales Bloggen könnten Sie beispielsweise Benutzern erlauben, Beiträge zu markieren und die Markierung aufzuheben und wie folgt zu verfolgen, wie viele Sterne ein Beitrag erhalten hat:

Java

private void onStarClicked(DatabaseReference postRef) {
    postRef.runTransaction(new Transaction.Handler() {
        @Override
        public Transaction.Result doTransaction(MutableData mutableData) {
            Post p = mutableData.getValue(Post.class);
            if (p == null) {
                return Transaction.success(mutableData);
            }

            if (p.stars.containsKey(getUid())) {
                // Unstar the post and remove self from stars
                p.starCount = p.starCount - 1;
                p.stars.remove(getUid());
            } else {
                // Star the post and add self to stars
                p.starCount = p.starCount + 1;
                p.stars.put(getUid(), true);
            }

            // Set value and report transaction success
            mutableData.setValue(p);
            return Transaction.success(mutableData);
        }

        @Override
        public void onComplete(DatabaseError databaseError, boolean committed,
                               DataSnapshot currentData) {
            // Transaction completed
            Log.d(TAG, "postTransaction:onComplete:" + databaseError);
        }
    });
}

Kotlin+KTX

private fun onStarClicked(postRef: DatabaseReference) {
    // ...
    postRef.runTransaction(object : Transaction.Handler {
        override fun doTransaction(mutableData: MutableData): Transaction.Result {
            val p = mutableData.getValue(Post::class.java)
                    ?: return Transaction.success(mutableData)

            if (p.stars.containsKey(uid)) {
                // Unstar the post and remove self from stars
                p.starCount = p.starCount - 1
                p.stars.remove(uid)
            } else {
                // Star the post and add self to stars
                p.starCount = p.starCount + 1
                p.stars[uid] = true
            }

            // Set value and report transaction success
            mutableData.value = p
            return Transaction.success(mutableData)
        }

        override fun onComplete(
                databaseError: DatabaseError?,
                committed: Boolean,
                currentData: DataSnapshot?
        ) {
            // Transaction completed
            Log.d(TAG, "postTransaction:onComplete:" + databaseError!!)
        }
    })
}

Die Verwendung einer Transaktion verhindert, dass die Sternzählung falsch ist, wenn mehrere Benutzer denselben Beitrag gleichzeitig markieren oder der Kunde veraltete Daten hat. Wenn die Transaktion abgelehnt wird, gibt der Server den aktuellen Wert an den Client zurück, der die Transaktion mit dem aktualisierten Wert erneut ausführt. Dies wiederholt sich, bis die Transaktion akzeptiert wird oder zu viele Versuche unternommen wurden.

Atomare serverseitige Inkremente

Im obigen Anwendungsfall schreiben wir zwei Werte in die Datenbank: die ID des Benutzers, der den Beitrag markiert/deaktiviert, und die inkrementierte Sternzahl. Wenn wir bereits wissen, dass der Benutzer den Beitrag markiert, können wir anstelle einer Transaktion eine atomare Inkrementierungsoperation verwenden.

Java

private void onStarClicked(String uid, String key) {
    Map<String, Object> updates = new HashMap<>();
    updates.put("posts/"+key+"/stars/"+uid, true);
    updates.put("posts/"+key+"/starCount", ServerValue.increment(1));
    updates.put("user-posts/"+uid+"/"+key+"/stars/"+uid, true);
    updates.put("user-posts/"+uid+"/"+key+"/starCount", ServerValue.increment(1));
    mDatabase.updateChildren(updates);
}

Kotlin+KTX

private fun onStarClicked(uid: String, key: String) {
    val updates: MutableMap<String, Any> = HashMap()
    updates["posts/$key/stars/$uid"] = true
    updates["posts/$key/starCount"] = ServerValue.increment(1)
    updates["user-posts/$uid/$key/stars/$uid"] = true
    updates["user-posts/$uid/$key/starCount"] = ServerValue.increment(1)
    database.updateChildren(updates)
}

Dieser Code verwendet keine Transaktionsoperation, daher wird er nicht automatisch erneut ausgeführt, wenn ein Update widersprüchlich ist. Da die Erhöhungsoperation jedoch direkt auf dem Datenbankserver erfolgt, besteht keine Möglichkeit eines Konflikts.

Wenn Sie anwendungsspezifische Konflikte erkennen und ablehnen möchten, z. B. wenn ein Benutzer einen Beitrag markiert, den er bereits zuvor markiert hat, sollten Sie benutzerdefinierte Sicherheitsregeln für diesen Anwendungsfall schreiben.

Offline mit Daten arbeiten

Wenn ein Client seine Netzwerkverbindung verliert, funktioniert Ihre App weiterhin ordnungsgemäß.

Jeder Client, der mit einer Firebase-Datenbank verbunden ist, verwaltet seine eigene interne Version aller Daten, für die Listener verwendet werden oder die mit dem Server synchronisiert werden sollen. Beim Lesen oder Schreiben von Daten wird zuerst diese lokale Version der Daten verwendet. Der Firebase-Client synchronisiert dann diese Daten mit den Remote-Datenbankservern und mit anderen Clients nach dem „Best-Effort“.

Daher lösen alle Schreibvorgänge in die Datenbank sofort lokale Ereignisse aus, noch bevor eine Interaktion mit dem Server stattfindet. Das bedeutet, dass Ihre App unabhängig von Netzwerklatenz oder Konnektivität reaktionsschnell bleibt.

Sobald die Konnektivität wiederhergestellt ist, empfängt Ihre App die entsprechenden Ereignisse, sodass der Client mit dem aktuellen Serverstatus synchronisiert wird, ohne benutzerdefinierten Code schreiben zu müssen.

Wir werden mehr über Offline - Verhalten sprechen in Erfahren Sie mehr über Online - und Offline - Funktionen .

Nächste Schritte