Diese Seite wurde von der Cloud Translation API übersetzt.
Switch to English

Lesen und Schreiben von Daten auf Android

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

Firebase-Daten werden in eine FirebaseDatabase Referenz geschrieben und durch Anhängen eines asynchronen Listeners an die Referenz abgerufen. Der Listener wird einmal für den Anfangszustand der Daten und immer wieder ausgelöst, wenn sich die Daten ändern.

(Optional) Prototyp und Test mit Firebase Local Emulator Suite

Bevor wir darüber sprechen, wie Ihre App aus der Echtzeitdatenbank liest und in diese schreibt, stellen wir Ihnen eine Reihe von Tools vor, mit denen Sie die Funktionen der Echtzeitdatenbank prototypisieren 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 ohne Bereitstellung von Live-Diensten arbeiten zu können.

Ein Echtzeit-Datenbankemulator ist Teil der Local Emulator Suite, mit der Ihre App mit Ihrem emulierten Datenbankinhalt und Ihrer Konfiguration sowie optional mit Ihren emulierten Projektressourcen (Funktionen, andere Datenbanken und Sicherheitsregeln) interagieren kann. Beachten Sie, dass die Local Emulator Suite emulierten Cloud-Speicher noch nicht unterstützt.

Die Verwendung des Echtzeitdatenbank-Emulators umfasst nur wenige Schritte:

  1. Hinzufügen einer Codezeile zur Testkonfiguration Ihrer App, um eine Verbindung zum Emulator herzustellen.
  2. Führen Sie im Stammverzeichnis Ihres lokalen Projektverzeichnisses firebase emulators:start .
  3. Anrufe aus dem Prototypcode Ihrer App wie gewohnt mit einem SDK der Echtzeitdatenbankplattform oder mithilfe der REST-API der Echtzeitdatenbank tätigen.

Eine detaillierte Anleitung mit Echtzeitdatenbank- und Cloud-Funktionen ist verfügbar. Sie sollten sich auch die Einführung in die Local Emulator Suite ansehen.

Holen Sie sich eine DatabaseReference

Zum Lesen oder Schreiben von Daten aus der Datenbank benötigen 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 Schreibvorgänge können Sie setValue() , um Daten in einer angegebenen Referenz zu speichern und alle vorhandenen Daten in diesem Pfad zu ersetzen. Mit dieser Methode können Sie:

  • Ü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 untergeordneten Speicherorten zugeordnet. Die Verwendung eines Java-Objekts macht Ihren Code in der Regel auch lesbarer und einfacher zu warten. Wenn Sie beispielsweise eine App mit einem grundlegenden Benutzerprofil haben, sieht Ihr User möglicherweise wie folgt aus:

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 setValue() wie folgt hinzufügen:

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)
}

Wenn Sie setValue() auf diese Weise verwenden, werden Daten am angegebenen Speicherort überschrieben, einschließlich aller setValue() 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

Lesen Sie Daten mit beständigen Listenern

Verwenden Sie die Methode addValueEventListener() , um Daten in einem Pfad zu lesen und auf Änderungen zu ValueEventListener um einer DatabaseReference einen ValueEventListener hinzuzufügen.

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

Mit der Methode onDataChange() Sie einen statischen Snapshot des Inhalts an einem bestimmten Pfad lesen, wie er zum Zeitpunkt des Ereignisses vorhanden war. Diese Methode wird einmal ausgelöst, wenn der Listener angehängt wird, und jedes Mal, wenn sich die Daten, einschließlich untergeordneter Daten, ändern. Dem Ereignisrückruf wird ein Snapshot übergeben, der alle Daten an diesem Speicherort enthält, einschließlich untergeordneter Daten. Wenn keine Daten vorhanden sind, gibt der Snapshot false wenn Sie getValue() aufrufen exists() und null wenn Sie getValue() aufrufen.

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 Listener empfängt einen DataSnapshot , der die Daten zum Zeitpunkt des Ereignisses am angegebenen Speicherort in der Datenbank enthält. getValue() Sie getValue() für einen Snapshot aufrufen, wird die Java-Objektdarstellung der Daten zurückgegeben. Wenn am Speicherort keine Daten vorhanden sind, gibt der Aufruf von getValue() null .

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

Daten einmal lesen

Einmal lesen mit get ()

Das SDK dient zur Verwaltung von Interaktionen mit Datenbankservern, unabhängig davon, ob Ihre App online oder offline ist.

Im Allgemeinen sollten Sie die ValueEventListener beschriebenen ValueEventListener Techniken verwenden, um Daten zu lesen und über Aktualisierungen der Daten vom Backend benachrichtigt zu werden. Die Hörertechniken 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 mit get() einen Snapshot der Daten aus der Datenbank abrufen. Wenn get() aus irgendeinem Grund den get() nicht zurückgeben kann, überprüft der Client den lokalen Speichercache und gibt einen Fehler zurück, wenn der Wert immer noch nicht gefunden wird.

Die unnötige Verwendung von get() kann die Bandbreitennutzung erhöhen und zu Leistungseinbußen führen, die durch die Verwendung eines Echtzeit-Listeners wie oben gezeigt verhindert werden können.

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 Listener 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 addListenerForSingleValueEvent , um die Daten sofort aus dem lokalen Festplatten-Cache addListenerForSingleValueEvent .

Dies ist nützlich für Daten, die nur einmal geladen werden müssen und sich voraussichtlich nicht häufig ändern oder aktives Abhören erfordern. Beispielsweise verwendet die Blogging-App in den vorherigen Beispielen diese Methode, um das Profil eines Benutzers zu laden, wenn dieser mit dem Verfassen eines neuen Beitrags beginnt.

Aktualisieren oder Löschen von Daten

Aktualisieren Sie bestimmte Felder

Verwenden Sie die updateChildren() -Methode, um gleichzeitig auf bestimmte updateChildren() Knoten eines Knotens zu schreiben, ohne andere updateChildren() Knoten zu überschreiben.

Wenn Sie updateChildren() aufrufen, können Sie untergeordnete Werte auf niedrigerer Ebene aktualisieren, indem Sie einen Pfad für den Schlüssel updateChildren() . Wenn Daten an mehreren Orten gespeichert werden, um eine bessere Skalierung zu erreichen, können Sie alle Instanzen dieser Daten mithilfe des Daten-Fan-Outs aktualisieren. Beispielsweise kann eine Social-Blogging-App eine Post Klasse wie die folgende haben:

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 letzten Aktivitäts-Feed und den Aktivitäts-Feed des Posting-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() , um einen Beitrag im Knoten zu erstellen, der Beiträge für alle Benutzer unter /posts/$postid und gleichzeitig den Schlüssel mit getKey() . Der Schlüssel kann dann verwendet werden, um einen zweiten Eintrag in den Posts des Benutzers unter /user-posts/$userid/$postid .

Mithilfe dieser Pfade können Sie mit einem einzigen Aufruf von updateChildren() gleichzeitig Aktualisierungen an mehreren Speicherorten in der JSON- updateChildren() , z. B. wie in diesem Beispiel der neue Beitrag an beiden Speicherorten erstellt wird. Auf diese Weise vorgenommene gleichzeitige Aktualisierungen sind atomar: Entweder sind alle Aktualisierungen erfolgreich oder alle Aktualisierungen schlagen fehl.

Fügen Sie einen Abschlussrückruf hinzu

Wenn Sie wissen möchten, wann Ihre Daten festgeschrieben wurden, können Sie einen Abschluss-Listener hinzufügen. Sowohl setValue() als auch updateChildren() einen optionalen Abschluss-Listener, der aufgerufen wird, wenn der Schreibvorgang erfolgreich in die Datenbank übernommen wurde. 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

Der einfachste Weg, Daten zu löschen, besteht darin, removeValue() für einen Verweis auf den Speicherort dieser Daten removeValue() .

Sie können auch löschen, indem Sie null als Wert für einen anderen Schreibvorgang wie setValue() oder updateChildren() . Sie können diese Technik mit updateChildren() , um mehrere updateChildren() in einem einzigen API-Aufruf zu löschen.

Hörer abnehmen

Rückrufe werden durch Aufrufen der Methode removeEventListener() in Ihrer Firebase-Datenbankreferenz entfernt.

Wenn ein Listener einem Datenspeicherort 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.

Durch Aufrufen von removeEventListener() für einen übergeordneten Listener werden die auf den removeEventListener() Knoten registrierten Listener nicht automatisch entfernt. removeEventListener() muss auch von allen removeEventListener() Listenern aufgerufen werden, um den Rückruf zu entfernen.

Daten als Transaktionen speichern

Wenn Sie mit Daten arbeiten, die durch gleichzeitige Änderungen beschädigt werden könnten, z. B. inkrementelle Zähler, können Sie eine Transaktionsoperation verwenden . Sie geben dieser Operation zwei Argumente: eine Aktualisierungsfunktion und einen optionalen Rückruf zum Abschluss. Die Aktualisierungsfunktion verwendet den aktuellen Status der Daten als Argument und gibt den neuen gewünschten Status zurück, den Sie schreiben möchten. Wenn ein anderer Client an den Speicherort schreibt, bevor Ihr neuer Wert erfolgreich geschrieben wurde, wird Ihre Aktualisierungsfunktion mit dem neuen aktuellen Wert erneut aufgerufen und der Schreibvorgang wird wiederholt.

In der Beispiel-App für soziales Bloggen können Sie Benutzern beispielsweise erlauben, Beiträge zu markieren und zu entfernen und zu verfolgen, wie viele Sterne ein Beitrag wie folgt 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!!)
        }
    })
}

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

Atomic serverseitige Inkremente

Im obigen Anwendungsfall schreiben wir zwei Werte in die Datenbank: die ID des Benutzers, der den Beitrag markiert / aufhebt, und die inkrementierte Anzahl der Sterne. 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 und wird daher bei einem widersprüchlichen Update nicht automatisch erneut ausgeführt. Da die Inkrementierungsoperation jedoch direkt auf dem Datenbankserver ausgeführt wird, besteht keine Möglichkeit eines Konflikts.

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

Arbeiten Sie offline mit Daten

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

Jeder mit einer Firebase-Datenbank verbundene Client verwaltet seine eigene interne Version aller Daten, für die Listener verwendet werden oder die als mit dem Server synchronisiert gekennzeichnet sind. Beim Lesen oder Schreiben von Daten wird zuerst diese lokale Version der Daten verwendet. Der Firebase-Client synchronisiert diese Daten dann mit den Remote-Datenbankservern und mit anderen Clients auf "Best-Effort" -Basis.

Infolgedessen lösen alle Schreibvorgänge in die Datenbank unmittelbar vor jeder Interaktion mit dem Server lokale Ereignisse aus. Dies bedeutet, dass Ihre App unabhängig von Netzwerklatenz oder Konnektivität reagiert.

Sobald die Konnektivität wiederhergestellt ist, empfängt Ihre App die entsprechenden Ereignisse, sodass der Client mit dem aktuellen Serverstatus synchronisiert wird, ohne dass benutzerdefinierter Code geschrieben werden muss.

Weitere Informationen zum Offline-Verhalten finden Sie unter Weitere Informationen zu Online- und Offline-Funktionen .

Nächste Schritte