Odczyt i zapis danych na Androidzie

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:

  1. Dodajesz wiersz kodu do konfiguracji testowej aplikacji, aby połączyć się z emulatorem.
  2. Uruchomienie firebase emulators:start w katalogu głównym projektu lokalnego.
  3. 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

Dalsze kroki