Pracuj z listami danych na Androidzie

W tym dokumencie opisano pracę z listami danych w Firebase. Aby poznać podstawy odczytywania i zapisywania danych Firebase, zobacz Odczyt i zapis danych w systemie Android .

Uzyskaj odniesienie do bazy danych

Aby czytać i zapisywać dane z bazy danych, potrzebujesz instancji DatabaseReference :

Kotlin+KTX

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

Java

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

Czytać i pisać listy

Dołącz do listy danych

Użyj metody push() , aby dołączyć dane do listy w aplikacjach obsługujących wielu użytkowników. Metoda push() generuje unikalny klucz za każdym razem, gdy do określonego odniesienia Firebase dodawany jest nowy element podrzędny. Używając automatycznie generowanych kluczy dla każdego nowego elementu na liście, kilku klientów może dodać elementy podrzędne do tej samej lokalizacji w tym samym czasie, bez konfliktów zapisu. Unikalny klucz generowany przez push() opiera się na znaczniku czasu, więc elementy listy są automatycznie porządkowane chronologicznie.

Możesz użyć odniesienia do nowych danych zwróconych przez metodę push() , aby uzyskać wartość automatycznie wygenerowanego klucza potomka lub ustawić dane dla potomka. Wywołanie getKey() w odniesieniu do odwołania push() zwraca wartość automatycznie wygenerowanego klucza.

Możesz użyć tych automatycznie wygenerowanych kluczy, aby uprościć spłaszczanie struktury danych. Aby uzyskać więcej informacji, zobacz przykład rozłożenia danych.

Słuchaj wydarzeń podrzędnych

Podczas pracy z listami aplikacja powinna nasłuchiwać zdarzeń podrzędnych, a nie zdarzeń wartości używanych dla pojedynczych obiektów.

Zdarzenia podrzędne są wyzwalane w odpowiedzi na określone operacje, które mają miejsce na elementach podrzędnych węzła w wyniku operacji, takiej jak dodanie nowego elementu podrzędnego za pomocą metody push() lub aktualizacja elementu podrzędnego za pomocą metody updateChildren() . Każdy z nich razem może być przydatny do nasłuchiwania zmian w określonym węźle w bazie danych.

Aby nasłuchiwać zdarzeń podrzędnych w DatabaseReference , dołącz ChildEventListener :

Słuchacz Odwołanie zdarzenia Typowe użycie
ChildEventListener onChildAdded() Pobieraj listy elementów lub słuchaj dodatków do listy elementów. To wywołanie zwrotne jest wyzwalane raz dla każdego istniejącego elementu podrzędnego, a następnie ponownie za każdym razem, gdy do określonej ścieżki dodawane jest nowe dziecko. DataSnapshot przekazany do odbiornika zawiera dane nowego dziecka.
onChildChanged() Słuchaj zmian w elementach na liście. To zdarzenie jest uruchamiane za każdym razem, gdy modyfikuje się węzeł podrzędny, w tym wszelkie modyfikacje elementów podrzędnych węzła podrzędnego. DataSnapshot przekazany do detektora zdarzeń zawiera zaktualizowane dane elementu podrzędnego.
onChildRemoved() Posłuchaj, czy elementy są usuwane z listy. DataSnapshot przekazany do wywołania zwrotnego zdarzenia zawiera dane usuniętego elementu podrzędnego.
onChildMoved() Słuchaj zmian w kolejności elementów na uporządkowanej liście. To zdarzenie jest wywoływane za każdym razem, gdy wywołanie zwrotne onChildChanged() zostanie wywołane przez aktualizację, która powoduje zmianę kolejności elementu podrzędnego. Jest używany z danymi uporządkowanymi za pomocą orderByChild lub orderByValue .

Na przykład aplikacja do blogowania społecznościowego może używać tych metod łącznie do monitorowania aktywności w komentarzach do wpisu, jak pokazano poniżej:

Kotlin+KTX

val childEventListener = object : ChildEventListener {
    override fun onChildAdded(dataSnapshot: DataSnapshot, previousChildName: String?) {
        Log.d(TAG, "onChildAdded:" + dataSnapshot.key!!)

        // A new comment has been added, add it to the displayed list
        val comment = dataSnapshot.getValue<Comment>()

        // ...
    }

    override fun onChildChanged(dataSnapshot: DataSnapshot, previousChildName: String?) {
        Log.d(TAG, "onChildChanged: ${dataSnapshot.key}")

        // A comment has changed, use the key to determine if we are displaying this
        // comment and if so displayed the changed comment.
        val newComment = dataSnapshot.getValue<Comment>()
        val commentKey = dataSnapshot.key

        // ...
    }

    override fun onChildRemoved(dataSnapshot: DataSnapshot) {
        Log.d(TAG, "onChildRemoved:" + dataSnapshot.key!!)

        // A comment has changed, use the key to determine if we are displaying this
        // comment and if so remove it.
        val commentKey = dataSnapshot.key

        // ...
    }

    override fun onChildMoved(dataSnapshot: DataSnapshot, previousChildName: String?) {
        Log.d(TAG, "onChildMoved:" + dataSnapshot.key!!)

        // A comment has changed position, use the key to determine if we are
        // displaying this comment and if so move it.
        val movedComment = dataSnapshot.getValue<Comment>()
        val commentKey = dataSnapshot.key

        // ...
    }

    override fun onCancelled(databaseError: DatabaseError) {
        Log.w(TAG, "postComments:onCancelled", databaseError.toException())
        Toast.makeText(
            context,
            "Failed to load comments.",
            Toast.LENGTH_SHORT,
        ).show()
    }
}
databaseReference.addChildEventListener(childEventListener)

Java

ChildEventListener childEventListener = new ChildEventListener() {
    @Override
    public void onChildAdded(DataSnapshot dataSnapshot, String previousChildName) {
        Log.d(TAG, "onChildAdded:" + dataSnapshot.getKey());

        // A new comment has been added, add it to the displayed list
        Comment comment = dataSnapshot.getValue(Comment.class);

        // ...
    }

    @Override
    public void onChildChanged(DataSnapshot dataSnapshot, String previousChildName) {
        Log.d(TAG, "onChildChanged:" + dataSnapshot.getKey());

        // A comment has changed, use the key to determine if we are displaying this
        // comment and if so displayed the changed comment.
        Comment newComment = dataSnapshot.getValue(Comment.class);
        String commentKey = dataSnapshot.getKey();

        // ...
    }

    @Override
    public void onChildRemoved(DataSnapshot dataSnapshot) {
        Log.d(TAG, "onChildRemoved:" + dataSnapshot.getKey());

        // A comment has changed, use the key to determine if we are displaying this
        // comment and if so remove it.
        String commentKey = dataSnapshot.getKey();

        // ...
    }

    @Override
    public void onChildMoved(DataSnapshot dataSnapshot, String previousChildName) {
        Log.d(TAG, "onChildMoved:" + dataSnapshot.getKey());

        // A comment has changed position, use the key to determine if we are
        // displaying this comment and if so move it.
        Comment movedComment = dataSnapshot.getValue(Comment.class);
        String commentKey = dataSnapshot.getKey();

        // ...
    }

    @Override
    public void onCancelled(DatabaseError databaseError) {
        Log.w(TAG, "postComments:onCancelled", databaseError.toException());
        Toast.makeText(mContext, "Failed to load comments.",
                Toast.LENGTH_SHORT).show();
    }
};
databaseReference.addChildEventListener(childEventListener);

Słuchaj wydarzeń wartościowych

Chociaż używanie obiektu ChildEventListener jest zalecanym sposobem odczytywania list danych, istnieją sytuacje, w których przydatne jest dołączenie obiektu ValueEventListener do odwołania do listy.

Dołączenie obiektu ValueEventListener do listy danych spowoduje zwrócenie całej listy danych w postaci pojedynczego DataSnapshot , który można następnie zapętlić, aby uzyskać dostęp do poszczególnych elementów podrzędnych.

Nawet jeśli zapytanie ma tylko jedno dopasowanie, zrzut ekranu nadal jest listą; zawiera tylko jeden element. Aby uzyskać dostęp do elementu, musisz zapętlić wynik:

Kotlin+KTX

// My top posts by number of stars
myTopPostsQuery.addValueEventListener(object : ValueEventListener {
    override fun onDataChange(dataSnapshot: DataSnapshot) {
        for (postSnapshot in dataSnapshot.children) {
            // TODO: handle the post
        }
    }

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

Java

// My top posts by number of stars
myTopPostsQuery.addValueEventListener(new ValueEventListener() {
    @Override
    public void onDataChange(@NonNull DataSnapshot dataSnapshot) {
        for (DataSnapshot postSnapshot: dataSnapshot.getChildren()) {
            // TODO: handle the post
        }
    }

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

Ten wzorzec może być przydatny, gdy chcesz pobrać wszystkie elementy podrzędne listy w jednej operacji, zamiast nasłuchiwać dodatkowych zdarzeń onChildAdded .

Odłącz słuchaczy

Wywołania zwrotne są usuwane poprzez wywołanie metody removeEventListener() w odwołaniu do bazy danych Firebase.

Jeśli słuchacz został dodany do lokalizacji danych wiele razy, jest on wywoływany wielokrotnie dla każdego zdarzenia i należy go odłączyć tyle samo razy, aby go całkowicie usunąć.

Wywołanie metody removeEventListener() na odbiorniku nadrzędnym nie powoduje automatycznego usunięcia odbiorników zarejestrowanych w jego węzłach podrzędnych; removeEventListener() należy także wywołać na wszystkich odbiornikach podrzędnych, aby usunąć wywołanie zwrotne.

Sortowanie i filtrowanie danych

Możesz użyć klasy Realtime Database Query , aby pobrać dane posortowane według klucza, wartości lub wartości elementu podrzędnego. Możesz także filtrować posortowane wyniki według określonej liczby wyników lub zakresu kluczy lub wartości.

Sortuj dane

Aby pobrać posortowane dane, zacznij od określenia jednej z metod porządkowania, aby określić sposób uporządkowania wyników:

metoda Stosowanie
orderByChild() Uporządkuj wyniki według wartości określonego klucza podrzędnego lub zagnieżdżonej ścieżki podrzędnej.
orderByKey() Uporządkuj wyniki według kluczy podrzędnych.
orderByValue() Uporządkuj wyniki według wartości podrzędnych.

Możesz użyć tylko jednej metody sortowania na raz. Wielokrotne wywołanie metody sortowania według tego samego zapytania powoduje błąd.

Poniższy przykład ilustruje sposób pobrania listy najpopularniejszych postów użytkownika posortowanych według liczby gwiazdek:

Kotlin+KTX

// My top posts by number of stars
val myUserId = uid
val myTopPostsQuery = databaseReference.child("user-posts").child(myUserId)
    .orderByChild("starCount")

myTopPostsQuery.addChildEventListener(object : ChildEventListener {
    // TODO: implement the ChildEventListener methods as documented above
    // ...
})

Java

// My top posts by number of stars
String myUserId = getUid();
Query myTopPostsQuery = databaseReference.child("user-posts").child(myUserId)
        .orderByChild("starCount");
myTopPostsQuery.addChildEventListener(new ChildEventListener() {
    // TODO: implement the ChildEventListener methods as documented above
    // ...
});

Definiuje to zapytanie, które w połączeniu z odbiornikiem podrzędnym synchronizuje klienta z postami użytkownika ze ścieżki w bazie danych na podstawie jego identyfikatora użytkownika, uporządkowanego według liczby gwiazdek otrzymanych przez każdy post. Ta technika używania identyfikatorów jako kluczy indeksujących nazywa się rozdzielaniem danych, możesz przeczytać więcej na ten temat w Strukturuj swoją bazę danych .

Wywołanie metody orderByChild() określa klucz podrzędny, według którego mają zostać uporządkowane wyniki. W tym przypadku posty są sortowane według wartości odpowiedniego elementu podrzędnego "starCount" . Zapytania można także porządkować według zagnieżdżonych elementów podrzędnych, jeśli masz dane wyglądające tak:

"posts": {
  "ts-functions": {
    "metrics": {
      "views" : 1200000,
      "likes" : 251000,
      "shares": 1200,
    },
    "title" : "Why you should use TypeScript for writing Cloud Functions",
    "author": "Doug",
  },
  "android-arch-3": {
    "metrics": {
      "views" : 900000,
      "likes" : 117000,
      "shares": 144,
    },
    "title" : "Using Android Architecture Components with Firebase Realtime Database (Part 3)",
    "author": "Doug",
  }
},

W tym przykładzie możemy uporządkować elementy listy według wartości zagnieżdżonych w kluczu metrics , określając względną ścieżkę do zagnieżdżonego elementu podrzędnego w naszym wywołaniu orderByChild() .

Kotlin+KTX

// Most viewed posts
val myMostViewedPostsQuery = databaseReference.child("posts")
    .orderByChild("metrics/views")
myMostViewedPostsQuery.addChildEventListener(object : ChildEventListener {
    // TODO: implement the ChildEventListener methods as documented above
    // ...
})

Java

// Most viewed posts
Query myMostViewedPostsQuery = databaseReference.child("posts")
        .orderByChild("metrics/views");
myMostViewedPostsQuery.addChildEventListener(new ChildEventListener() {
    // TODO: implement the ChildEventListener methods as documented above
    // ...
});

Więcej informacji na temat porządkowania innych typów danych można znaleźć w artykule Jak uporządkowane są dane zapytań .

Filtrowanie danych

Aby filtrować dane, podczas konstruowania zapytania możesz połączyć dowolną metodę limitu lub zakresu z metodą porządkowania.

metoda Stosowanie
limitToFirst() Ustawia maksymalną liczbę elementów do zwrócenia od początku uporządkowanej listy wyników.
limitToLast() Ustawia maksymalną liczbę elementów zwracanych z końca uporządkowanej listy wyników.
startAt() Zwracaj elementy większe lub równe określonemu kluczowi lub wartości, w zależności od wybranej metody sortowania.
startAfter() Zwróć pozycje większe niż określony klucz lub wartość, w zależności od wybranej metody sortowania.
endAt() Zwróć produkty mniejsze lub równe określonemu kluczowi lub wartości, w zależności od wybranej metody zamawiania.
endBefore() Zwróć pozycje mniejsze niż określony klucz lub wartość, w zależności od wybranej metody zamawiania.
equalTo() Zwróć pozycje równe określonemu kluczowi lub wartości, w zależności od wybranej metody sortowania.

W przeciwieństwie do metod sortowania, można łączyć wiele funkcji limitów lub zakresów. Można na przykład połączyć metody startAt() i endAt() , aby ograniczyć wyniki do określonego zakresu wartości.

Nawet jeśli zapytanie ma tylko jedno dopasowanie, zrzut ekranu nadal jest listą; zawiera tylko jeden element. Aby uzyskać dostęp do elementu, musisz zapętlić wynik:

Kotlin+KTX

// My top posts by number of stars
myTopPostsQuery.addValueEventListener(object : ValueEventListener {
    override fun onDataChange(dataSnapshot: DataSnapshot) {
        for (postSnapshot in dataSnapshot.children) {
            // TODO: handle the post
        }
    }

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

Java

// My top posts by number of stars
myTopPostsQuery.addValueEventListener(new ValueEventListener() {
    @Override
    public void onDataChange(@NonNull DataSnapshot dataSnapshot) {
        for (DataSnapshot postSnapshot: dataSnapshot.getChildren()) {
            // TODO: handle the post
        }
    }

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

Ogranicz liczbę wyników

Możesz użyć metod limitToFirst() i limitToLast() , aby ustawić maksymalną liczbę dzieci, które mają być synchronizowane dla danego wywołania zwrotnego. Na przykład, jeśli użyjesz limitToFirst() do ustawienia limitu na 100, początkowo otrzymasz tylko do 100 wywołań zwrotnych onChildAdded() . Jeśli w bazie danych Firebase przechowywanych jest mniej niż 100 elementów, dla każdego elementu uruchamiane jest wywołanie zwrotne onChildAdded() .

Gdy elementy się zmieniają, otrzymujesz wywołania zwrotne onChildAdded() dla elementów, które wchodzą w zapytanie, oraz wywołania zwrotne onChildRemoved() dla elementów, które z niego wypadają, tak że łączna liczba pozostaje na poziomie 100.

Poniższy przykład ilustruje, jak przykładowa aplikacja do blogowania definiuje zapytanie w celu pobrania listy 100 najnowszych postów wszystkich użytkowników:

Kotlin+KTX

// Last 100 posts, these are automatically the 100 most recent
// due to sorting by push() keys.
databaseReference.child("posts").limitToFirst(100)

Java

// Last 100 posts, these are automatically the 100 most recent
// due to sorting by push() keys
Query recentPostsQuery = databaseReference.child("posts")
        .limitToFirst(100);

Ten przykład definiuje jedynie zapytanie, aby faktycznie zsynchronizować dane, musi mieć podłączony słuchacz .

Filtruj według klucza lub wartości

Możesz użyć startAt() , startAfter() , endAt() , endBefore() i equalTo() , aby wybrać dowolne punkty początkowe, końcowe i równoważne dla zapytań. Może to być przydatne do dzielenia danych na strony lub znajdowania elementów z elementami podrzędnymi, które mają określoną wartość.

Sposób uporządkowania danych zapytania

W tej sekcji wyjaśniono, w jaki sposób dane są sortowane według każdej metody porządkowania w klasie Query .

orderByChild

Podczas korzystania z orderByChild() dane zawierające określony klucz podrzędny są porządkowane w następujący sposób:

  1. Dzieci z wartością null dla określonego klucza podrzędnego są traktowane jako pierwsze.
  2. Następne są dzieci z wartością false dla określonego klucza podrzędnego. Jeśli wiele elementów podrzędnych ma wartość false , są one sortowane leksykograficznie według klucza.
  3. Następne są dzieci z wartością true dla określonego klucza podrzędnego. Jeśli wiele elementów podrzędnych ma wartość true , są one sortowane leksykograficznie według klucza.
  4. Następne są dzieci z wartością liczbową, posortowane w kolejności rosnącej. Jeśli wiele elementów podrzędnych ma tę samą wartość liczbową dla określonego węzła podrzędnego, są one sortowane według klucza.
  5. Ciągi znaków występują po liczbach i są sortowane leksykograficznie w kolejności rosnącej. Jeśli wiele elementów podrzędnych ma tę samą wartość dla określonego węzła podrzędnego, są one uporządkowane leksykograficznie według klucza.
  6. Obiekty są umieszczane na końcu i są sortowane leksykograficznie według kluczy w kolejności rosnącej.

orderByKey

Jeśli do sortowania danych używana jest orderByKey() , dane są zwracane w kolejności rosnącej według klucza.

  1. Na pierwszym miejscu znajdują się dzieci z kluczem, który można przeanalizować jako 32-bitową liczbę całkowitą, posortowane w kolejności rosnącej.
  2. Następne są dzieci, których kluczem jest wartość ciągu, posortowane leksykograficznie w kolejności rosnącej.

orderByValue

Podczas korzystania z orderByValue() elementy podrzędne są porządkowane według ich wartości. Kryteria porządkowania są takie same jak w przypadku orderByChild() , z tą różnicą, że zamiast wartości określonego klucza podrzędnego używana jest wartość węzła.

Następne kroki