Praca z listami danych na Androidzie

Ten dokument dotyczy pracy z listami danych w Firebase. Podstawowe informacje o odczytywaniu i zapisywaniu danych Firebase znajdziesz w artykule Odczytywanie i zapisywanie 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łączanie do listy danych

Aby dołączać dane do listy w aplikacji wielostanowiskowej, użyj metody push(). Metoda push() generuje unikalny klucz za każdym razem, gdy do określonego odwołania Firebase dodawane jest nowe dziecko. Dzięki tym automatycznie generowanym kluczom dla każdego nowego elementu na liście kilka klientów może dodawać podelementy do tej samej lokalizacji w tym samym czasie bez konfliktów podczas zapisu. Unikalny klucz wygenerowany przez funkcję push() jest oparty na sygnaturze czasowej, więc elementy listy są automatycznie sortowane 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 funkcji getKey() w przypadku odwołania push() zwraca wartość klucza wygenerowanego automatycznie.

Możesz używać tych automatycznie generowanych kluczy, aby uprościć spłaszczenie struktury danych. Więcej informacji znajdziesz w przykładzie dotyczącym rozgałęzienia danych.

Nasłuchuj zdarzeń podrzędnych

Podczas pracy z listami aplikacja powinna nasłuchiwać zdarzeń podrzędnych, a nie zdarzeń wartościowych 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 Połączenie 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. DataSnapshot przekazane do odbiorcy zawiera dane nowego 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. Zmienna DataSnapshot przekazana do listenera zawiera zaktualizowane dane dotyczące dziecka.
onChildRemoved() Słuchaj, czy elementy są usuwane z listy. Argument DataSnapshot przekazany do wywołania zwrotnego zdarzenia zawiera dane dotyczące usuniętego podrzędnego konta.
onChildMoved() Słuchaj zmian kolejności elementów na liście uporządkowanej. To zdarzenie jest wywoływane, gdy wywołanie zwrotne onChildChanged()zostaje wywołane przez aktualizację, która powoduje zmianę kolejności elementów podrzędnych. Jest on używany z danymi uporządkowanymi za pomocą funkcji orderByChild lub orderByValue.

Na przykład aplikacja do blogowania na potrzeby mediów społecznościowych może używać tych metod razem, aby monitorować aktywność w komentarzach do postu, 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);

Nasłuchiwanie zdarzeń wartości

Chociaż używanie ChildEventListener jest zalecanym sposobem odczytywania list danych, w niektórych sytuacjach przydatne może być dołączenie ValueEventListener do listy odwołań.

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

Nawet wtedy, gdy zapytanie pasuje tylko do jednego elementu, snapshot jest nadal listą, ale 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 listy w ramach jednej operacji zamiast nasłuchiwać dodatkowych zdarzeń onChildAdded.

Odłączanie słuchaczy

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

Jeśli odbiornik został dodany kilka razy do lokalizacji danych, jest on wywoływany wielokrotnie w przypadku każdego zdarzenia. Aby go całkowicie odłączyć, musisz to zrobić tyle razy.

Wywołanie metody removeEventListener() w słuchaczu nadrzędnym nie powoduje automatycznego usuwania słuchaczy zarejestrowanych w podrzędnych węzłach. Aby usunąć wywołanie zwrotne, musisz też wywołać metodę removeEventListener() w słuchaczach podrzędnych.

Sortowanie i filtrowanie danych

Aby pobrać dane posortowane według klucza, wartości lub wartości podrzędnego, możesz użyć klasy Realtime Database Query. Możesz też ograniczyć liczbę wyników do określonej liczby lub do zakresu kluczy bądź wartości.

Sortowanie danych

Aby pobrać posortowane dane, najpierw określ jedną z metod sortowania, która określi sposób sortowania wyników:

Metoda Wykorzystanie
orderByChild() Uporządkuj wyniki według wartości określonego klucza podrzędnego lub ścieżki podrzędnej ujętej w nawiasach.
orderByKey() Uporządkuj wyniki według kluczy podrzędnych.
orderByValue() Porządkuj wyniki według wartości elementów podrzędnych.

Możesz użyć tylko jednej metody sortowania naraz. Wywołanie metody sortowania kilka razy w tym samym zapytaniu powoduje błąd.

Ten przykład pokazuje, jak pobrać listę 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
    // ...
});

Określa ono zapytanie, które po połączeniu z podrzędnym odbiorcą synchronizuje klienta z postami użytkownika z ścieżki w bazie danych na podstawie jego identyfikatora użytkownika, posortowanymi według liczby gwiazdek przypisanych do każdego posta. Ta technika używania identyfikatorów jako kluczy indeksu nazywa się rozgałęźnikiem danych. Więcej informacji znajdziesz w artykule Struktura bazy danych.

Wywołanie metody orderByChild() określa klucz podrzędny, według którego mają być posortowane wyniki. W tym przypadku posty są sortowane według wartości ich "starCount"elementów podrzędnych. 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 porządkowaniu innych typów danych znajdziesz w artykule Porządkowanie danych zapytań.

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 z początku uporządkowanej listy wyników.
limitToLast() Określa maksymalną liczbę elementów do zwrócenia z końca posortowanej listy wyników.
startAt() Zwraca elementy, których wartość jest większa lub równa 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, których klucz lub wartość jest mniejszy lub równy określonemu kluczowi lub wartości, w zależności od wybranej metody sortowania.
endBefore() Zwraca elementy, które są mniejsze niż określony klucz lub wartość, w zależności od wybranej metody sortowania.
equalTo() Zwraca elementy o wartości określonej w kluczu lub wartości w zależności od wybranej metody sortowania.

W przeciwieństwie do metod porządkowania według kolumny możesz łączyć wiele funkcji limit i zakres. 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 wykonać pętlę na wyniku:

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

Ograniczanie liczby wyników

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

Gdy elementy się zmieniają, otrzymujesz onChildAdded() wywołania zwrotne dla elementów, które wchodzą do zapytania, oraz onChildRemoved() wywołania zwrotne dla elementów, które z niego wypadają, dzięki czemu łączna liczba pozostaje równa 100.

Ten przykład pokazuje, jak przykładowa aplikacja do blogowania definiuje zapytanie, aby pobrać 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. Aby dane mogły być synchronizowane, musi być dołączony odbiorca.

Filtrowanie według klucza lub wartości

Aby wybrać dowolne punkty początkowe, końcowe i równoważne zapytań, możesz użyć znaczników startAt(), startAfter(), endAt(), endBefore()equalTo(). Może to być przydatne do podziału danych na strony lub znajdowania elementów z elementami podrzędnymi o określonej wartości.

Sposób sortowania danych zapytania

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. Najpierw są wyświetlane elementy podrzędne, dla których wartość atrybutu podrzędnego null jest równa null.
  2. Następnie podawane są elementy podrzędne, dla których wartość określonego klucza podrzędnego to false. Jeśli wiele elementów podrzędnych ma wartość false, są one sortowane alfabetycznie 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ępnie pojawiają się elementy z wartością liczbową, posortowane w kolejności rosnącej. Jeśli wiele elementów podrzędnych ma tę samą wartość numeryczną w przypadku 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 wybranym węźle podrzędnym, są one uporządkowane alfabetycznie według klucza.
  6. Obiekty znajdują się na końcu i są posortowane 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. Kolejne są elementy podrzędne z wartością ciągu znaków jako kluczem, posortowane leksykograficznie w kolejności rosnącej.

orderByValue

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

Dalsze kroki