Praca z listami danych na Androidzie

Ten dokument opisuje pracę z listami danych w Firebase. Aby poznać podstawy odczytywania i zapisywania danych w Firebase, przeczytaj artykuł Odczytywanie i zapisywanie danych na Androidzie.

Pobieranie DatabaseReference

Aby odczytywać i zapisywać dane w bazie danych, potrzebujesz instancji DatabaseReference:

Kotlin

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

Java

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

Odczytywanie i zapisywanie list

Dołączanie do listy danych

Aby dołączyć dane do listy w aplikacjach wielodostępnych, użyj metody push(). Metoda push() generuje unikalny klucz za każdym razem, gdy do określonego odwołania Firebase dodawany jest nowy element podrzędny. Dzięki użyciu tych automatycznie generowanych kluczy dla każdego nowego elementu na liście kilku klientów może jednocześnie dodawać elementy podrzędne do tej samej lokalizacji bez konfliktów zapisu. Unikalny klucz generowany przez push() jest oparty na sygnaturze czasowej, więc elementy listy są automatycznie uporządkowane chronologicznie.

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

Możesz użyć tych automatycznie generowanych kluczy, aby uprościć spłaszczanie struktury danych. Więcej informacji znajdziesz w przykładzie zwielokrotnienia wyjściowego danych .

Nasłuchiwanie 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, np. dodanie nowego elementu podrzędnego za pomocą metody push() lub zaktualizowanie elementu podrzędnego za pomocą metody updateChildren(). Każda z tych metod może być przydatna 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 Wywołanie zwrotne zdarzenia Typowe użycie
ChildEventListener onChildAdded() Pobieranie list elementów lub nasłuchiwanie dodawania elementów do listy. To wywołanie zwrotne jest wywoływane raz dla każdego istniejącego elementu podrzędnego, a następnie za każdym razem gdy do określonej ścieżki dodawany jest nowy element podrzędny. Obiekt DataSnapshot przekazywany do słuchacza zawiera dane nowego elementu podrzędnego.
onChildChanged() Nasłuchiwanie zmian w elementach listy. To zdarzenie jest wywoływane za każdym razem, gdy element podrzędny jest modyfikowany, w tym gdy modyfikowane są elementy podrzędne elementu podrzędnego. Obiekt DataSnapshot przekazywany do słuchacza zdarzeń zawiera zaktualizowane dane elementu podrzędnego.
onChildRemoved() Nasłuchiwanie usuwania elementów z listy. Obiekt DataSnapshot przekazywany do wywołania zwrotnego zdarzenia zawiera dane usuniętego elementu podrzędnego.
onChildMoved() Nasłuchiwanie zmian kolejności elementów na uporządkowanej liście. To zdarzenie jest wywoływane za każdym razem, gdy wywołanie zwrotne onChildChanged() jest wywoływane przez aktualizację, która powoduje zmianę kolejności elementu podrzędnego. Jest używane z danymi uporządkowanymi za pomocą orderByChild lub orderByValue.

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

Kotlin

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

Nasłuchiwanie zdarzeń wartości

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

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

Nawet jeśli zapytanie znajdzie tylko 1 pasujący element, migawka nadal będzie listą, tylko z 1 elementem. Aby uzyskać dostęp do elementu, musisz iterować wynik:

Kotlin

// 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łączanie słuchaczy

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

Jeśli słuchacz został dodany do lokalizacji danych kilka razy, jest wywoływany kilka razy dla każdego zdarzenia. Aby go całkowicie usunąć, musisz go odłączyć tyle samo razy.

Wywołanie removeEventListener() w przypadku słuchacza nadrzędnego nie powoduje automatycznego usunięcia słuchaczy zarejestrowanych w jego elementach podrzędnych. Aby usunąć wywołanie zwrotne, należy również wywołać removeEventListener() w przypadku wszystkich słuchaczy elementów podrzędnych.

Sortowanie i filtrowanie danych

Za pomocą klasy Realtime Database Query możesz pobierać dane posortowane według klucza, wartości lub wartości elementu podrzędnego. Możesz też filtrować posortowany wynik, aby uzyskać określoną liczbę wyników lub zakres kluczy bądź wartości.

Sortowanie danych

Aby pobrać posortowane dane, zacznij od określenia jednej z metod sortowania, która określi kolejność wyników:

Metoda Wykorzystanie
orderByChild() Sortowanie wyników według wartości określonego klucza elementu podrzędnego lub zagnieżdżonej ścieżki elementu podrzędnego.
orderByKey() Sortowanie wyników według kluczy elementów podrzędnych.
orderByValue() Sortowanie wyników według wartości elementów podrzędnych.

W danym momencie możesz użyć tylko 1 metody sortowania. Wywołanie metody sortowania kilka razy w tym samym zapytaniu spowoduje błąd.

Poniższy przykład pokazuje, jak pobrać listę najpopularniejszych postów użytkownika posortowanych według liczby gwiazdek:

Kotlin

// 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 ze słuchaczem elementów podrzędnych synchronizuje klienta z postami użytkownika ze ścieżki w bazie danych na podstawie jego identyfikatora użytkownika, posortowanymi według liczby gwiazdek otrzymanych przez każdy post. Ta technika używania identyfikatorów jako kluczy indeksu jest nazywana zwielokrotnieniem wyjściowym danych. Więcej informacji znajdziesz w artykule Struktura bazy danych.

Wywołanie metody orderByChild() określa klucz elementu podrzędnego, według którego mają być sortowane wyniki. W tym przypadku posty są sortowane według wartości odpowiedniego elementu podrzędnego "starCount". Zapytania można też sortować według zagnieżdżonych elementów podrzędnych, jeśli masz dane w takiej postaci:

"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 posortować elementy listy według wartości zagnieżdżonych pod kluczem metrics, określając ścieżkę względną do zagnieżdżonego elementu podrzędnego w wywołaniu orderByChild().

Kotlin

// 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 tym, jak sortowane są inne typy danych, znajdziesz w artykule Jak sortowane są dane zapytań.

Filtrowanie danych

Aby filtrować dane, możesz połączyć dowolną z metod limitu lub zakresu z metodą sortowania podczas tworzenia zapytania.

Metoda Wykorzystanie
limitToFirst() Ustawia maksymalną liczbę elementów do zwrócenia z początku posortowanej listy wyników.
limitToLast() Ustawia maksymalną liczbę elementów do zwrócenia z końca posortowanej 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 od określonego klucza lub wartości w zależności od wybranej metody sortowania.
endAt() Zwraca elementy mniejsze lub równe określonemu kluczowi lub wartości w zależności od wybranej metody sortowania.
endBefore() Zwraca elementy mniejsze od określonego klucza lub wartości 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 możesz łączyć ze sobą kilka funkcji limitu lub zakresu. Możesz na przykład połączyć metody startAt() i endAt(), aby ograniczyć wyniki do określonego zakresu wartości.

Nawet jeśli zapytanie znajdzie tylko 1 pasujący element, migawka nadal będzie listą, tylko z 1 elementem. Aby uzyskać dostęp do elementu, musisz iterować wynik:

Kotlin

// 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());
        // ...
    }
});

Ograniczanie liczby 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() do ustawienia limitu 100, początkowo otrzymasz tylko do 100 wywołań zwrotnych onChildAdded(). Jeśli w bazie danych Firebase masz mniej niż 100 elementów, wywołanie zwrotne onChildAdded() jest wywoływane dla każdego elementu.

Gdy elementy się zmieniają, otrzymujesz wywołania zwrotne onChildAdded() dla elementów, które wchodzą w zakres zapytania, oraz wywołania zwrotne onChildRemoved() dla elementów, które z niego wypadają, tak aby łączna liczba elementów wynosiła 100.

Poniższy przykład pokazuje, jak przykładowa aplikacja do blogowania definiuje zapytanie, aby pobrać listę 100 najnowszych postów wszystkich użytkowników:

Kotlin

// 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 tylko zapytanie. Aby zsynchronizować dane, musi mieć dołączonego słuchacza.

Filtrowanie według klucza lub wartości

Za pomocą metod startAt(), startAfter(), endAt(), endBefore() i equalTo() możesz wybrać dowolne punkty początkowe, końcowe i równoważności dla zapytań. Może to być przydatne do paginowania danych lub znajdowania elementów z elementami podrzędnymi o określonej wartości.

Jak sortowane są dane zapytań

W tej sekcji wyjaśniamy, jak dane są sortowane przez każdą z metod sortowania w klasie Query.

orderByChild

Gdy używasz orderByChild(), dane zawierające określony klucz elementu podrzędnego są sortowane w ten sposób:

  1. Najpierw elementy podrzędne z wartością null dla określonego klucza elementu podrzędnego.
  2. Następnie elementy podrzędne z wartością false dla określonego klucza elementu podrzędnego. Jeśli kilka elementów podrzędnych ma wartość false, są one sortowane leksykograficznie według klucza.
  3. Następnie elementy podrzędne z wartością true dla określonego klucza elementu podrzędnego przychodzą następne. Jeśli kilka elementów podrzędnych ma wartość true, są one sortowane leksykograficznie według klucza.
  4. Następnie elementy podrzędne z wartością liczbową, posortowane rosnąco. Jeśli kilka elementów podrzędnych ma tę samą wartość liczbową dla określonego elementu podrzędnego, są one sortowane według klucza.
  5. Po liczbach występują ciągi znaków, które są sortowane leksykograficznie rosnąco. Jeśli kilka elementów podrzędnych ma tę samą wartość dla określonego elementu podrzędnego, są one sortowane leksykograficznie według klucza.
  6. Na końcu występują obiekty, które są sortowane leksykograficznie według klucza rosnąco.

orderByKey

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

  1. Najpierw elementy podrzędne z kluczem, który można przeanalizować jako 32-bitową liczbę całkowitą, posortowane rosnąco.
  2. Następnie elementy podrzędne z wartością ciągu znaków jako kluczem, posortowane leksykograficznie rosnąco.

orderByValue

Gdy używasz orderByValue(), elementy podrzędne są sortowane według ich wartości. Kryteria sortowania są takie same jak w przypadku orderByChild(), z tym że zamiast wartości określonego klucza elementu podrzędnego używana jest wartość węzła.

Dalsze kroki