Praca z listami danych na Androidzie

Ten dokument dotyczy pracy z listami danych w Firebase. Podstawy odczytywania i zapisywania danych Firebase znajdziesz w artykule Odczyt i zapis danych na Androidzie.

Pobieranie odniesienia do bazy danych

Aby móc odczytywać 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();

Odczytywanie i zapisywanie list

Dołącz do listy danych

Użyj metody push(), aby dołączać dane do listy w aplikacjach, które mają wielu użytkowników. Metoda push() generuje unikalny klucz za każdym razem, gdy do określonego odwołania Firebase jest dodawane nowe elementy podrzędne. Dzięki użyciu tych automatycznie generowanych kluczy dla każdego nowego elementu na liście kilku klientów może dodawać elementy podrzędne do tej samej lokalizacji w tym samym czasie bez konfliktów zapisu. Unikalny klucz wygenerowany przez funkcję push() jest oparty na sygnaturze czasowej, więc elementy listy są automatycznie porządkowane chronologicznie.

Możesz użyć odwołania do nowych danych zwróconych przez metodę push(), aby uzyskać wartość automatycznie wygenerowanego klucza wydawcy podrzędnego lub ustawić dane dla elementu podrzędnego. Wywołanie getKey() w przypadku pliku referencyjnego push() zwraca wartość klucza wygenerowanego automatycznie.

Możesz użyć tych automatycznie wygenerowanych kluczy, aby uprościć strukturę danych. Więcej informacji znajdziesz w przykładzie rozpowszechniania danych.

Nasłuchuj zdarzeń podrzędnych

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

Zdarzenia podrzędne są wywoływane w odpowiedzi na określone operacje wykonywane na elementach podrzędnych węzła w wyniku operacji, np. dodania nowego elementu podrzędnego za pomocą metody push() lub aktualizacji elementu podrzędnego za pomocą metody updateChildren(). Każdy z tych elementów może być przydatny do nasłuchiwania zmian w konkretnym węźle w bazie danych.

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

Detektor Wywołanie zwrotne zdarzenia Typowe zastosowanie
ChildEventListener onChildAdded() Pobieranie list elementów lub słuchanie, czy dodano elementy do listy. To wywołanie zwrotne jest aktywowane raz dla każdego istniejącego elementu podrzędnego, a następnie za każdym razem, gdy do określonej ścieżki dodajesz nowy element podrzędny. Pole DataSnapshot przekazane do detektora zawiera dane nowego elementu podrzędnego.
onChildChanged() Wykrywaj zmiany wprowadzone w elementach na liście. To zdarzenie jest wywoływane za każdym razem, gdy węzeł podrzędny zostanie zmodyfikowany, w tym wszelkie modyfikacje jego elementów podrzędnych. Element DataSnapshot przekazany do detektora zdarzeń zawiera zaktualizowane dane elementu podrzędnego.
onChildRemoved() Nasłuchuj elementów usuwanych z listy. Pole DataSnapshot przekazane do wywołania zwrotnego zdarzenia zawiera dane usuniętego elementu podrzędnego.
onChildMoved() Wykrywaj zmiany kolejności elementów na liście uporządkowanej. To zdarzenie jest wyzwalane za każdym razem, gdy aktualizacja powoduje zmianę kolejności elementów podrzędnych, gdy wywołanie zwrotne onChildChanged() jest aktywowane. Jest używany z danymi uporządkowanych według: orderByChild lub orderByValue.

Na przykład aplikacja do obsługi blogów społecznościowych może wykorzystywać te metody razem do monitorowania aktywności w komentarzach do posta, jak w tym przykładzie:

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

Wykrywaj zdarzenia dotyczące wartości

Chociaż korzystanie z ChildEventListener to zalecany sposób odczytywania list danych, w niektórych sytuacjach dołączenie ValueEventListener do odwołania do listy może być przydatne.

Dołączenie elementu ValueEventListener do listy danych spowoduje zwrócenie całej listy danych w formie pojedynczego elementu DataSnapshot, który można potem zapętlić w celu uzyskania dostępu do poszczególnych elementów podrzędnych.

Nawet jeśli dla zapytania występuje tylko jedno dopasowanie, zrzut nadal pozostaje listą, zawiera tylko 1 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 ramach jednej operacji, zamiast nasłuchiwać dodatkowych zdarzeń onChildAdded.

Odłącz detektory

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

Jeśli detektor został dodany do lokalizacji danych wiele razy, jest wywoływany wiele razy w przypadku każdego zdarzenia. Aby całkowicie usunąć odbiornik, musisz go odłączyć tyle samo razy.

Wywołanie removeEventListener() w detektorze nadrzędnym nie powoduje automatycznego usunięcia detektorów zarejestrowanych w węzłach podrzędnych. Aby usunąć wywołanie zwrotne, funkcja removeEventListener() musi też zostać wywołana we wszystkich detektorach podrzędnych.

Sortowanie i filtrowanie danych

Za pomocą klasy Query Bazy danych czasu rzeczywistego możesz pobrać dane posortowane według klucza, wartości lub wartości elementu podrzędnego. Możesz też filtrować posortowany wynik według określonej liczby wyników lub zakresu kluczy bądź wartości.

Sortowanie danych

Aby pobrać posortowane dane, zacznij od określenia jednej z metod ustalania kolejności wyników:

Metoda Wykorzystanie
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.

W danym momencie możesz używać tylko jednej metody sortowania. Wielokrotne wywołanie metody „order by by” w tym samym zapytaniu powoduje błąd.

Ten przykład pokazuje, jak uzyskać listę najpopularniejszych postów użytkownika, posortowaną 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ądkowane według liczby gwiazdek otrzymanych przez każdego posta. Ta technika używania identyfikatorów jako kluczy indeksu jest nazywana rozpowszechnianiem danych. Więcej informacji na ten temat znajdziesz w artykule Uporządkowanie bazy danych.

Wywołanie metody orderByChild() określa klucz podrzędny, według którego mają być sortowane wyniki. W takim przypadku posty są sortowane według wartości odpowiedniego elementu podrzędnego "starCount". Zapytania mogą być też porządkowane według zagnieżdżonych elementów podrzędnych, jeśli dane wyglądają 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 ścieżkę względną do zagnieżdżonego elementu podrzędnego w 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 o kolejności innych typów danych znajdziesz w artykule o kolejności danych w zapytaniach.

Filtrowanie danych

Aby filtrować dane, podczas tworzenia zapytania możesz połączyć dowolne metody limitów lub zakresów z metodą porządkowania.

Metoda Wykorzystanie
limitToFirst() Określa maksymalną liczbę elementów do zwrócenia od początku uporządkowanej listy wyników.
limitToLast() Określa maksymalną liczbę elementów do zwrócenia na końcu uporządkowanej listy wyników.
startAt() Zwraca elementy większe lub równe określonemu kluczowi lub wartości w zależności od wybranej metody sortowania.
startAfter() Zwraca elementy większe niż określony klucz lub wartość w zależności od wybranej metody sortowania.
endAt() Zwraca elementy mniejsze lub równe określonemu kluczowi bądź wartości w zależności od wybranej metody sortowania.
endBefore() Zwracaj elementy mniejsze niż określony klucz lub wartość w zależności od wybranej metody sortowania.
equalTo() Zwraca elementy równe określonemu kluczowi lub wartości w zależności od wybranej metody sortowania.

W przeciwieństwie do metod sortowania według kolejności można łączyć wiele funkcji ograniczeń lub zakresu. Możesz np. łączyć metody startAt() i endAt(), aby ograniczyć wyniki do określonego zakresu wartości.

Nawet wtedy, gdy dla zapytania występuje tylko jedno dopasowanie, zrzut nadal jest listą, zawiera tylko 1 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

Za pomocą metod limitToFirst() i limitToLast() możesz ustawić maksymalną liczbę elementów podrzędnych, które mają być synchronizowane w przypadku danego wywołania zwrotnego. Jeśli na przykład użyjesz limitToFirst(), aby ustawić limit 100, początkowo otrzymasz maksymalnie 100 wywołań zwrotnych onChildAdded(). Jeśli w bazie danych Firebase masz mniej niż 100 elementów, dla każdego z nich uruchamia się wywołanie zwrotne onChildAdded().

Wraz ze zmianą elementów otrzymujesz wywołania zwrotne onChildAdded() dla tych, które wpiszą zapytanie, oraz onChildRemoved() wywołań zwrotnych dla elementów, które z nich nie korzystają. Dzięki temu łączna liczba pozostaje na 100.

Poniższy przykład pokazuje, jak przykładowa aplikacja do blogowania definiuje zapytanie pobierające listę 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);

W tym przykładzie zdefiniowano tylko zapytanie służące do synchronizacji danych, które muszą mieć dołączony detektor.

Filtruj według klucza lub wartości

Za pomocą startAt(), startAfter(), endAt(), endBefore() i equalTo() możesz wybierać dla zapytań dowolne punkty rozpoczęcia, zakończenia i równoważności. Przydaje się to przy dzieleniu danych na strony lub wyszukiwaniu elementów z elementami podrzędnymi, które mają określoną wartość.

Sposób porządkowania danych zapytań

W tej sekcji wyjaśniamy, jak dane są sortowane według metody w klasie Query.

orderByChild

Jeśli używasz metody orderByChild(), dane zawierające określony klucz podrzędny są uporządkowane w ten sposób:

  1. Jako pierwsze wyświetlają się elementy podrzędne z wartością null określonego klucza podrzędnego.
  2. Następne są elementy podrzędne z wartością false określonego klucza podrzędnego. Jeśli kilka elementów podrzędnych ma wartość false, są one sortowane leksykograficznie według klucza.
  3. Następne są elementy podrzędne z wartością true określonego klucza podrzędnego. Jeśli kilka elementów podrzędnych ma wartość true, są one sortowane leksykograficznie według klucza.
  4. Następne są elementy podrzędne z wartością liczbową, posortowane w kolejności rosnącej. Jeśli wiele elementów podrzędnych ma tę samą wartość liczbową określonego węzła podrzędnego, są one sortowane według klucza.
  5. Ciągi znaków znajdują się po liczbach i są sortowane leksykograficznie w kolejności rosnącej. Jeśli wiele elementów podrzędnych ma tę samą wartość w określonym węźle podrzędnym, są one uporządkowane leksykograficznie według klucza.
  6. Obiekty są na końcu i są sortowane leksykograficznie według klucza w kolejności rosnącej.

orderByKey

Gdy do sortowania danych używasz funkcji orderByKey(), są one zwracane w kolejności rosnącej według klucza.

  1. Elementy podrzędne z kluczem, który można przeanalizować jako 32-bitową liczbę całkowitą, są na pierwszym miejscu, posortowane w kolejności rosnącej.
  2. Następnym elementem są elementy podrzędne z wartością w postaci ciągu znaków, posortowane leksykograficznie w kolejności rosnącej.

orderByValue

Gdy używasz metody orderByValue(), elementy podrzędne są uporządkowane według wartości. Kryteria sortowania są takie same jak w orderByChild(), z tą różnicą, że zamiast wartości określonego klucza podrzędnego używana jest wartość węzła.

Dalsze kroki