Ten dokument zawiera podstawowe informacje o odczytywaniu i zapisywaniu danych Firebase.
Dane Firebase są zapisywane w odwołaniu FirebaseDatabase
i pobierane przez
przez dodanie detektora asynchronicznego do pliku referencyjnego. Detektor zostaje aktywowany
raz dla początkowego stanu danych i ponownie po każdej zmianie danych.
(Opcjonalnie) Prototypowanie i testowanie przy użyciu Firebase Local Emulator Suite
Zanim zaczniesz mówić o tym, jak Twoja aplikacja odczytuje dane z usługi Realtime Database i zapisuje w niej zapisy, przedstawimy zestaw narzędzi, które możesz wykorzystać do prototypowania i testowania funkcji Realtime Database funkcje: Firebase Local Emulator Suite. Jeśli testujesz inne dane optymalizowanie reguł zabezpieczeń lub wyszukiwanie opłacalny sposób interakcji z backendem, który daje możliwość pracy lokalnej bez wdrażania aktywnych usług może być świetnym pomysłem.
Emulator Realtime Database jest częścią interfejsu Local Emulator Suite, który umożliwia aplikacji interakcję z treścią i konfiguracją emulowanej bazy danych a także opcjonalnie emulowane zasoby projektu (funkcje, inne bazy danych, i reguły zabezpieczeń).
Aby użyć emulatora Realtime Database, wystarczy wykonać kilka czynności:
- Dodajesz wiersz kodu do konfiguracji testowej aplikacji, aby połączyć się z emulatorem.
- Uruchomienie
firebase emulators:start
w katalogu głównym projektu lokalnego. - Wykonywanie wywołań z prototypowego kodu aplikacji za pomocą platformy Realtime Database pakietu SDK w zwykły sposób lub za pomocą interfejsu API REST Realtime Database.
Dostępny jest szczegółowy przewodnik dotyczący elementów Realtime Database i Cloud Functions. Warto też zapoznać się z wprowadzeniem do funkcji Local Emulator Suite.
Pobieranie odniesienia do bazy danych
Aby móc odczytywać lub zapisywać dane z bazy danych, musisz mieć instancję
DatabaseReference
:
Kotlin+KTX
private lateinit var database: DatabaseReference // ... database = Firebase.database.reference
Java
private DatabaseReference mDatabase; // ... mDatabase = FirebaseDatabase.getInstance().getReference();
Zapisywanie danych
Podstawowe operacje zapisu
W przypadku podstawowych operacji zapisu możesz użyć funkcji setValue()
, aby zapisać dane w określonym
zastępując wszystkie istniejące dane w tej ścieżce. Możesz użyć tej metody, aby:
- Typy kart odpowiadające dostępnym typom JSON:
String
Long
Double
Boolean
Map<String, Object>
List<Object>
- Przekaż niestandardowy obiekt Java, jeśli klasa, która go definiuje, ma wartość domyślną konstruktor, który nie przyjmuje żadnych argumentów i ma publiczne metody pobierania dla właściwości do przypisania.
Jeśli używasz obiektu Java, jego zawartość jest automatycznie mapowana.
do lokalizacji podrzędnych
w sposób zagnieżdżony. Użycie obiektu Java powoduje też zwykle,
bardziej czytelny i łatwiejszy w utrzymaniu. Na przykład, jeśli masz
aplikacji z podstawowym profilem użytkownika, obiekt User
może wyglądać tak:
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. }
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; } }
Użytkownika setValue()
możesz dodać w ten sposób:
Kotlin+KTX
fun writeNewUser(userId: String, name: String, email: String) { val user = User(name, email) database.child("users").child(userId).setValue(user) }
Java
public void writeNewUser(String userId, String name, String email) { User user = new User(name, email); mDatabase.child("users").child(userId).setValue(user); }
Użycie tego parametru setValue()
spowoduje zastąpienie danych w określonej lokalizacji,
łącznie z węzłami podrzędnymi. Nadal możesz jednak zaktualizować dziecko bez
cały obiekt jest tworzony na nowo. Jeśli chcesz zezwolić użytkownikom na aktualizowanie swoich profili
możesz zmienić nazwę użytkownika w następujący sposób:
Kotlin+KTX
database.child("users").child(userId).child("username").setValue(name)
Java
mDatabase.child("users").child(userId).child("username").setValue(name);
Odczyt danych
Odczytywanie danych za pomocą trwałych detektorów
Aby odczytywać dane na ścieżce i nasłuchiwać zmian, użyj funkcji addValueEventListener()
aby dodać ValueEventListener
do DatabaseReference
.
Detektor | Wywołanie zwrotne zdarzenia | Typowe zastosowanie |
---|---|---|
ValueEventListener |
onDataChange() |
Odczyt i nasłuchiwanie zmian w całej zawartości ścieżki. |
Za pomocą metody onDataChange()
możesz odczytać statyczny zrzut
ze ścieżek, które istniały w momencie wystąpienia zdarzenia. Ta metoda
jest wyzwalany raz, gdy podłączony jest odbiornik, i ponownie za każdym razem, gdy dane są
w tym dzieci, zmiany. Wywołanie zwrotne zdarzenia jest przekazywane do zrzutu zawierającego
wszystkich danych w tej lokalizacji, w tym danych podrzędnych. W przypadku braku danych funkcja
zrzut zwróci wartość false
po wywołaniu usług exists()
i null
getValue()
.
W poniższym przykładzie pokazano, jak aplikacja do blogowania społecznościowego pobiera szczegóły posta z bazy danych:
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)
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);
Detektor odbiera obiekt DataSnapshot
zawierający dane w określonym miejscu
tej lokalizacji w bazie danych w momencie wystąpienia zdarzenia. Dzwonię do: getValue()
na
migawka zwraca obiektową reprezentację danych w Javie. Jeśli nie ma danych
w lokalizacji, połączenie z numerem getValue()
zwraca null
.
W tym przykładzie ValueEventListener
definiuje również metodę onCancelled()
, która
jest wywoływane, jeśli odczyt został anulowany. Na przykład odczyt można anulować, jeśli
klient nie ma uprawnień do odczytu lokalizacji bazy danych Firebase. Ten
jest przekazywany obiekt DatabaseError
wskazujący przyczynę błędu.
Odczytaj dane raz
Odczytaj raz za pomocą get()
Pakiet SDK służy do zarządzania interakcjami z serwerami baz danych niezależnie od tego, czy jest online lub offline.
Należy zwykle używać metod ValueEventListener
opisanych powyżej
na odczytywanie danych, aby otrzymywać powiadomienia o aktualizacjach danych z backendu.
techniki detektora zmniejszają wykorzystanie i płatności oraz są zoptymalizowane pod kątem
aby zadbać o jak najlepsze wrażenia użytkowników korzystających z internetu i offline.
Jeśli dane są potrzebne tylko raz, możesz użyć funkcji get()
, aby uzyskać zrzut
z bazy danych. Jeśli z jakiegoś powodu get()
nie może zwrócić serwera
klient sprawdzi pamięć podręczną pamięci lokalnej i zwróci błąd, jeśli
nadal nie znaleziono wartości.
Niepotrzebne użycie get()
może zwiększyć wykorzystanie przepustowości i doprowadzić do utraty
. Można temu zapobiec, korzystając z detektora w czasie rzeczywistym, jak pokazano powyżej.
Kotlin+KTX
mDatabase.child("users").child(userId).get().addOnSuccessListener {
Log.i("firebase", "Got value ${it.value}")
}.addOnFailureListener{
Log.e("firebase", "Error getting data", it)
}
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()));
}
}
});
Czytanie raz za pomocą detektora
W niektórych przypadkach możesz chcieć, aby zwracana była wartość z lokalnej pamięci podręcznej
natychmiast, bez sprawdzania aktualizacji na serwerze. W
można użyć funkcji addListenerForSingleValueEvent
, aby pobrać dane z
do pamięci podręcznej dysku lokalnego.
Jest to przydatne w przypadku danych, które trzeba wczytać tylko raz i w pewnym stopniu często się zmieniają lub wymagają aktywnego słuchania. Na przykład aplikacja do blogowania w poprzednich przykładach korzysta z tej metody do wczytywania profilu użytkownika, gdy rozpocząć tworzenie nowego posta.
Aktualizowanie lub usuwanie danych
Zaktualizuj określone pola
Jednoczesne zapisywanie do określonych elementów podrzędnych węzła bez zastępowania innych
węzłów podrzędnych, użyj metody updateChildren()
.
Wywołując updateChildren()
, możesz zaktualizować wartości podrzędne niższego poziomu przez
i podaj ścieżkę dostępu do klucza. Jeśli dane są przechowywane w wielu lokalizacjach na potrzeby skalowania
możesz zaktualizować wszystkie wystąpienia tych danych za pomocą funkcji
rozpowszechnianie danych. Na przykład plik
aplikacja do obsługi blogów społecznościowych może mieć Post
taką klasę:
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, ) } }
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; } }
Aby utworzyć posta i jednocześnie zaktualizować go do ostatniej aktywności i kanału aktywności użytkownika publikującego, aplikacja do tworzenia blogów korzysta podobny do tego:
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) }
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); }
W tym przykładzie użyto metody push()
do utworzenia w węźle posta posta zawierającego posty dla:
wszystkich użytkowników w organizacji /posts/$postid
i jednocześnie pobieraj klucz za pomocą polecenia
getKey()
Za pomocą klucza można utworzyć drugi wpis w
posty na blogu /user-posts/$userid/$postid
.
Korzystając z tych ścieżek, można wprowadzać zmiany w wielu lokalizacjach jednocześnie
drzewo JSON z pojedynczym wywołaniem updateChildren()
, jak w tym przykładzie
utworzy nowy post w obu lokalizacjach. Równoczesne aktualizacje dokonywane w ten sposób
są niepodzielne: wszystkie aktualizacje zakończą się sukcesem, albo wszystkie aktualizacje kończą się niepowodzeniem.
Dodaj zakończone wywołanie zwrotne
Jeśli chcesz się dowiedzieć, kiedy Twoje dane zostały zatwierdzone, możesz dodać
detektor ukończenia. Zarówno setValue()
, jak i updateChildren()
przyjmują opcjonalny
detektor ukończenia, który jest wywoływany po pomyślnym zapisie
dla bazy danych. Jeśli połączenie się nie powiedzie, odbiornik
przekazuje obiekt błędu wskazujący przyczynę błędu.
Kotlin+KTX
database.child("users").child(userId).setValue(user) .addOnSuccessListener { // Write was successful! // ... } .addOnFailureListener { // Write failed // ... }
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 // ... } });
Usuń dane
Najprostszym sposobem usunięcia danych jest wywołanie funkcji removeValue()
w odniesieniu do
lokalizacji danych.
Możesz również usunąć, określając null
jako wartość innego zapisu
operacji, na przykład setValue()
lub updateChildren()
. Tej metody możesz używać,
za pomocą updateChildren()
, aby usunąć wiele elementów podrzędnych w pojedynczym wywołaniu interfejsu API.
Odłącz detektory
Wywołania zwrotne są usuwane przez wywołanie metody removeEventListener()
na Twoim
Dokumentacja bazy danych Firebase.
Jeśli detektor został dodany wiele razy do lokalizacji danych, jest wywoływane wiele razy dla każdego zdarzenia i musisz odłączyć je tę samą liczbę aby ją całkowicie usunąć.
Wywoływanie połączenia removeEventListener()
w przypadku detektora nadrzędnego
automatycznie usuwać detektory zarejestrowane w węzłach podrzędnych;
Funkcja removeEventListener()
musi być również wywoływana w przypadku podrzędnych detektorów
, aby usunąć wywołanie zwrotne.
Zapisywanie danych jako transakcji
Podczas pracy z danymi, które mogły zostać uszkodzone w wyniku równoczesnej pracy takich jak liczniki przyrostowe, możesz użyć funkcji operacji transakcji. Należy przekazać tej operacji dwa argumenty: funkcję aktualizacji i opcjonalny zakończenia. Funkcja aktualizacji przyjmuje bieżący stan danych jako argument i zwraca nowy pożądany stan, który chcesz zapisać. Jeśli zanim nowa wartość zostanie pomyślnie zapisana w lokalizacji, inny klient funkcja aktualizacji jest wywoływana ponownie z nową, bieżącą wartością, próba zapisu zostanie ponowiona.
Na przykład w przykładowej aplikacji do blogowania społecznościowego użytkownicy mogą oznaczanie postów gwiazdką i usuwanie gwiazdek oraz śledzenie liczby przyznanych gwiazdek w następujący sposób:
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!!) } }) }
Java
private void onStarClicked(DatabaseReference postRef) { postRef.runTransaction(new Transaction.Handler() { @NonNull @Override public Transaction.Result doTransaction(@NonNull 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); } }); }
Użycie transakcji zapobiega nieprawidłowemu liczeniu gwiazdek w przypadku większej liczby Użytkownicy oznaczali gwiazdką tego samego posta w tym samym czasie lub klient miał nieaktualne dane. Jeśli transakcja została odrzucona, serwer zwraca bieżącą wartość klientowi, , co powoduje ponowne uruchomienie transakcji ze zaktualizowaną wartością. Powtarza się do momentu transakcja została zaakceptowana lub dokonano zbyt wielu prób.
Atomic przyrosty po stronie serwera
W powyższym przypadku użycia zapisujemy w bazie danych 2 wartości: identyfikator użytkownik, który oznacza post lub oznaczenie gwiazdką posta, i zwiększona liczba gwiazdek. Jeśli że użytkownik oznaczy post gwiazdką, możemy użyć przyrostu atomowego zamiast transakcji.
Kotlin+KTX
private fun onStarClicked(uid: String, key: String) { val updates: MutableMap<String, Any> = hashMapOf( "posts/$key/stars/$uid" to true, "posts/$key/starCount" to ServerValue.increment(1), "user-posts/$uid/$key/stars/$uid" to true, "user-posts/$uid/$key/starCount" to ServerValue.increment(1), ) database.updateChildren(updates) }
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); }
Ten kod nie korzysta z operacji transakcji, więc nie pobiera automatycznie w przypadku wystąpienia konfliktu aktualizacji uruchom ponownie system. Ponieważ jednak operacja przyrostu odbywa się bezpośrednio na serwerze bazy danych, nie występuje konflikt.
Jeśli chcesz wykrywać i odrzucać konflikty dotyczące aplikacji, na przykład w postach, które zostały już wcześniej oznaczone gwiazdką, należy napisać niestandardowy dla konkretnego przypadku użycia.
Praca z danymi w trybie offline
Jeśli klient utraci połączenie sieciowe, aplikacja będzie nadal działać .
Każdy klient połączony z bazą danych Firebase ma własną wersję wewnętrzną żadnych danych dotyczących używanych detektorów lub oznaczonych jako przeznaczone do zachowania synchronizować z serwerem. Podczas odczytywania lub zapisywania danych ta lokalna wersja protokołu dane są wykorzystywane w pierwszej kolejności. Klient Firebase następnie synchronizuje te dane z zdalnymi serwerami baz danych i innymi klientami działają „najlepiej”, podstaw.
W rezultacie wszystkie zapisy w bazie danych wywołują zdarzenia lokalne natychmiast, przed dowolną interakcję z serwerem. Oznacza to, że aplikacja pozostaje elastyczna niezależnie od czasu oczekiwania czy połączenia.
Po przywróceniu połączenia aplikacja otrzyma odpowiedni zestaw zdarzeń, aby klient synchronizował się z bieżącym stanem serwera bez konieczności napisać dowolny niestandardowy kod.
Więcej informacji o zachowaniu użytkowników offline Więcej informacji o możliwościach pracy online i offline