Android
Baza danych w czasie rzeczywistym Firebase
Szukaj…
Uwagi
Moduł obsługi zdarzeń Firebase Realtime DataBase
Najpierw zainicjuj bazę danych Firebase:
FirebaseDatabase database = FirebaseDatabase.getInstance();
Napisz do swojej bazy danych:
// Write a message to the database
FirebaseDatabase database = FirebaseDatabase.getInstance();
DatabaseReference myRef = database.getReference("message");
myRef.setValue("Hello, World!");
Czytaj z bazy danych:
// Read from the database
myRef.addValueEventListener(new ValueEventListener() {
@Override
public void onDataChange(DataSnapshot dataSnapshot) {
// This method is called once with the initial value and again
// whenever data at this location is updated.
String value = dataSnapshot.getValue(String.class);
Log.d(TAG, "Value is: " + value);
}
@Override
public void onCancelled(DatabaseError error) {
// Failed to read value
Log.w(TAG, "Failed to read value.", error.toException());
}
});
Pobierz dane o zdarzeniach na Androida:
ChildEventListener childEventListener = new ChildEventListener() {
@Override
public void onChildAdded(DataSnapshot dataSnapshot, String previousChildName) {
Log.d(TAG, "onChildAdded:" + dataSnapshot.getKey());
}
@Override
public void onChildChanged(DataSnapshot dataSnapshot, String previousChildName) {
Log.d(TAG, "onChildChanged:" + dataSnapshot.getKey());
}
@Override
public void onChildRemoved(DataSnapshot dataSnapshot) {
Log.d(TAG, "onChildRemoved:" + dataSnapshot.getKey());
}
@Override
public void onChildMoved(DataSnapshot dataSnapshot, String previousChildName) {
Log.d(TAG, "onChildMoved:" + 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();
}
};
ref.addChildEventListener(childEventListener);
Szybki montaż
Ukończ część Instalacja i konfiguracja, aby połączyć aplikację z Firebase.
Spowoduje to utworzenie projektu w Firebase.Dodaj zależność dla Firebase Realtime Database do pliku
build.gradle
poziomie modułu:
compile 'com.google.firebase:firebase-database:10.2.1'
- Skonfiguruj reguły bazy danych Firebase
Teraz jesteś gotowy do pracy z bazą danych Realtime w systemie Android.
Na przykład piszesz wiadomość Hello World
do bazy danych pod kluczem message
.
// Write a message to the database
FirebaseDatabase database = FirebaseDatabase.getInstance();
DatabaseReference myRef = database.getReference("message");
myRef.setValue("Hello, World!");
Projektowanie i zrozumienie sposobu pobierania danych w czasie rzeczywistym z bazy danych Firebase
W tym przykładzie założono, że skonfigurowano już bazę danych Firebase Realtime. Jeśli dopiero zaczynasz, zapoznaj się z tym, jak dodać Firebase do swojego projektu na Androida.
Najpierw dodaj zależność bazy danych Firebase do pliku build.gradle na poziomie aplikacji:
compile 'com.google.firebase:firebase-database:9.4.0'
Teraz stwórzmy aplikację czatu, która przechowuje dane w bazie danych Firebase.
Krok 1: Utwórz klasę o nazwie Czat
Po prostu stwórz klasę z kilkoma podstawowymi zmiennymi wymaganymi do czatu:
public class Chat{
public String name, message;
}
Krok 2: Utwórz niektóre dane JSON
Aby wysyłać / pobierać dane do / z bazy danych Firebase, musisz użyć JSON. Załóżmy, że niektóre czaty są już przechowywane na poziomie głównym w bazie danych. Dane tych czatów mogą wyglądać następująco:
[
{
"name":"John Doe",
"message":"My first Message"
},
{
"name":"John Doe",
"message":"Second Message"
},
{
"name":"John Doe",
"message":"Third Message"
}
]
Krok 3: Dodanie słuchaczy
Istnieją trzy rodzaje słuchaczy. W poniższym przykładzie użyjemy childEventListener
:
DatabaseReference chatDb = FirebaseDatabase.getInstance().getReference() // Referencing the root of the database.
.child("chats"); // Referencing the "chats" node under the root.
chatDb.addChildEventListener(new ChildEventListener() {
@Override
public void onChildAdded(DataSnapshot dataSnapshot, String s) {
// This function is called for every child id chat in this case, so using the above
// example, this function is going to be called 3 times.
// Retrieving the Chat object from this function is simple.
Chat chat; // Create a null chat object.
// Use the getValue function in the dataSnapshot and pass the object's class name to
// which you want to convert and get data. In this case it is Chat.class.
chat = dataSnapshot.getValue(Chat.class);
// Now you can use this chat object and add it into an ArrayList or something like
// that and show it in the recycler view.
}
@Override
public void onChildChanged(DataSnapshot dataSnapshot, String s) {
// This function is called when any of the node value is changed, dataSnapshot will
// get the data with the key of the child, so you can swap the new value with the
// old one in the ArrayList or something like that.
// To get the key, use the .getKey() function.
// To get the value, use code similar to the above one.
}
@Override
public void onChildRemoved(DataSnapshot dataSnapshot) {
// This function is called when any of the child node is removed. dataSnapshot will
// get the data with the key of the child.
// To get the key, use the s String parameter .
}
@Override
public void onChildMoved(DataSnapshot dataSnapshot, String s) {
// This function is called when any of the child nodes is moved to a different position.
// To get the key, use the s String parameter.
}
@Override
public void onCancelled(DatabaseError databaseError) {
// If anything goes wrong, this function is going to be called.
// You can get the exception by using databaseError.toException();
}
});
Krok 4: Dodaj dane do bazy danych
Wystarczy utworzyć obiekt klasy Chat i dodać wartości w następujący sposób:
Chat chat=new Chat();
chat.name="John Doe";
chat.message="First message from android";
Teraz uzyskaj odwołanie do węzła czatów, tak jak to zrobiono w sesji pobierania:
DatabaseReference chatDb = FirebaseDatabase.getInstance().getReference().child("chats");
Zanim zaczniesz dodawać dane, pamiętaj, że potrzebujesz jeszcze jednego głębokiego odniesienia, ponieważ węzeł czatu ma jeszcze kilka węzłów, a dodanie nowego czatu oznacza dodanie nowego węzła zawierającego szczegóły czatu. Możemy wygenerować nową i unikalną nazwę węzła za pomocą funkcji push()
na obiekcie DatabaseReference
, która zwróci kolejną DatabaseReference
, która z kolei wskazuje na nowo utworzony węzeł w celu wstawienia danych czatu.
Przykład
// The parameter is the chat object that was newly created a few lines above.
chatDb.push().setValue(chat);
Funkcja setValue()
upewni się, że wszystkie funkcje onDataChanged
aplikacji zostaną wywołane (w tym to samo urządzenie), co jest przypadkiem dołączonym odbiornikiem węzła „czaty”.
Denormalizacja: płaska struktura bazy danych
Denormalizacja i płaska struktura bazy danych są niezbędne do skutecznego pobierania oddzielnych wywołań. Dzięki poniższej strukturze możliwe jest również utrzymywanie relacji dwukierunkowych. Wadą tego podejścia jest to, że zawsze trzeba aktualizować dane w wielu miejscach.
Na przykład wyobraź sobie aplikację, która pozwala użytkownikowi przechowywać wiadomości dla siebie (notatki).
Pożądana płaska struktura bazy danych:
|--database
|-- memos
|-- memokey1
|-- title: "Title"
|-- content: "Message"
|-- memokey2
|-- title: "Important Title"
|-- content: "Important Message"
|-- users
|-- userKey1
|-- name: "John Doe"
|-- memos
|-- memokey1 : true //The values here don't matter, we only need the keys.
|-- memokey2 : true
|-- userKey2
|-- name: "Max Doe"
Użyta klasa notatek
public class Memo {
private String title, content;
//getters and setters ...
//toMap() is necessary for the push process
private Map<String, Object> toMap() {
HashMap<String, Object> result = new HashMap<>();
result.put("title", title);
result.put("content", content);
return result;
}
}
Pobieranie notatek użytkownika
//We need to store the keys and the memos seperately
private ArrayList<String> mKeys = new ArrayList<>();
private ArrayList<Memo> mMemos = new ArrayList<>();
//The user needs to be logged in to retrieve the uid
String currentUserId = FirebaseAuth.getInstance().getCurrentUser().getUid();
//This is the reference to the list of memos a user has
DatabaseReference currentUserMemoReference = FirebaseDatabase.getInstance().getReference()
.child("users").child(currentUserId).child("memos");
//This is a reference to the list of all memos
DatabaseReference memoReference = FirebaseDatabase.getInstance().getReference()
.child("memos");
//We start to listen to the users memos,
//this will also retrieve the memos initially
currentUserMemoReference.addChildEventListener(new ChildEventListener() {
@Override
public void onChildAdded(DataSnapshot dataSnapshot, String s) {
//Here we retrieve the key of the memo the user has.
String key = dataSnapshot.getKey(); //for example memokey1
//For later manipulations of the lists, we need to store the key in a list
mKeys.add(key);
//Now that we know which message belongs to the user,
//we request it from our memos:
memoReference.child(key).addValueEventListener(new ValueEventListener() {
@Override
public void onDataChange(DataSnapshot dataSnapshot) {
//Here we retrieve our memo:
Memo memo = dataSnapshot.getValue(Memo.class);
mMemos.add(memo);
}
@Override
public void onCancelled(DatabaseError databaseError) { }
});
}
@Override
public void onChildChanged(DataSnapshot dataSnapshot, String s) { }
@Override
public void onChildRemoved(DataSnapshot dataSnapshot) { }
@Override
public void onChildMoved(DataSnapshot dataSnapshot, String s) { }
@Override
public void onCancelled(DatabaseError databaseError) { }
}
Tworzenie notatki
//The user needs to be logged in to retrieve the uid
String currentUserUid = FirebaseAuth.getInstance().getCurrentUser().getUid();
//This is the path to the list of memos a user has
String userMemoPath = "users/" + currentUserUid + "/memos/";
//This is the path to the list of all memos
String memoPath = "memos/";
//We need to retrieve an unused key from the memos reference
DatabaseReference memoReference = FirebaseDatabase.getInstance().getReference().child("memos");
String key = memoReference.push().getKey();
Memo newMemo = new Memo("Important numbers", "1337, 42, 3.14159265359");
Map<String, Object> childUpdates = new HashMap<>();
//The second parameter **here** (the value) does not matter, it's just that the key exists
childUpdates.put(userMemoPath + key, true);
childUpdates.put(memoPath + key, newMemo.toMap());
FirebaseDatabase.getInstance().getReference().updateChildren(childUpdates);
Po wypchnięciu baza danych wygląda następująco:
|--database
|-- memos
|-- memokey1
|-- title: "Title"
|-- content: "Message"
|-- memokey2
|-- title: "Important Title"
|-- content: "Important Message"
|-- generatedMemokey3
|-- title: "Important numbers"
|-- content: "1337, 42, 3.14159265359"
|-- users
|-- userKey1
|-- name: "John Doe"
|-- memos
|-- memokey1 : true //The values here don't matter, we only need the keys.
|-- memokey2 : true
|-- generatedMemokey3 : true
|-- userKey2
|-- name: "Max Doe"
Zrozumienie bazy danych JSON firebase
Zanim zabrudzimy ręce kodem, uważam, że konieczne jest zrozumienie, w jaki sposób dane są przechowywane w bazie ogniowej. W przeciwieństwie do relacyjnych baz danych firebase przechowuje dane w formacie JSON. Pomyśl o każdym wierszu relacyjnej bazy danych jako o obiekcie JSON (który jest zasadniczo nieuporządkowaną parą klucz-wartość). Tak więc nazwa kolumny staje się kluczowa, a wartość przechowywana w tej kolumnie dla jednego określonego wiersza jest wartością. W ten sposób cały wiersz jest reprezentowany jako obiekt JSON, a ich lista reprezentuje całą tabelę bazy danych. Bezpośrednią korzyścią, jaką widzę w tym zakresie, jest modyfikacja schematu, która staje się znacznie tańsza w porównaniu do starego RDBMS. Łatwiej jest dodać kilka dodatkowych atrybutów do JSON niż zmienić strukturę tabeli.
oto przykładowy JSON, który pokazuje, jak dane są przechowywane w bazie ogniowej:
{
"user_base" : {
"342343" : {
"email" : "[email protected]",
"authToken" : "some string",
"name" : "Kaushal",
"phone" : "+919916xxxxxx",
"serviceProviderId" : "firebase",
"signInServiceType" : "google",
},
"354895" : {
"email" : "[email protected]",
"authToken" : "some string",
"name" : "devil",
"phone" : "+919685xxxxxx",
"serviceProviderId" : "firebase",
"signInServiceType" : "github"
},
"371298" : {
"email" : "[email protected]",
"authToken" : "I am batman",
"name" : "Bruce Wayne",
"phone" : "+14085xxxxxx",
"serviceProviderId" : "firebase",
"signInServiceType" : "shield"
}
},
"user_prefs": {
"key1":{
"data": "for key one"
},
"key2":{
"data": "for key two"
},
"key3":{
"data": "for key three"
}
},
//other structures
}
To wyraźnie pokazuje, jak dane, które przechowaliśmy w relacyjnych bazach danych, mogą być przechowywane w formacie JSON. Następnie zobaczmy, jak odczytać te dane na urządzeniach z Androidem.
Pobieranie danych z bazy ogniowej
Zakładam, że wiesz już o dodawaniu bazy ogniowej zależności w stopniach w Android Studio. Jeśli po prostu nie postępuj zgodnie z instrukcjami tutaj . Dodaj swoją aplikację w konsoli Firebase, stopniuj synchronizację Android Studio po dodaniu zależności. Wszystkie zależności nie są potrzebne, tylko baza danych firebase i uwierzytelnianie bazy danych firebase.
Teraz, gdy wiemy, jak są przechowywane dane i jak dodawać zależności stopni, zobaczmy, jak korzystać z importowanego zestawu SDK systemu Android do pobierania danych.
utwórz odwołanie do bazy danych firebase
DatabaseReference userDBRef = FirebaseDatabase.getInstance().getReference();
// above statement point to base tree
userDBRef = DatabaseReference.getInstance().getReference().child("user_base")
// points to user_base table JSON (see previous section)
stąd możesz połączyć wiele wywołań metody child (), aby wskazać dane, którymi jesteś zainteresowany. Na przykład, jeśli dane są przechowywane, jak pokazano w poprzedniej sekcji i chcesz wskazać użytkownika Bruce Wayne, możesz użyć:
DatabaseReference bruceWayneRef = userDBRef.child("371298");
// 371298 is key of bruce wayne user in JSON structure (previous section)
Lub po prostu przekaż całe odwołanie do obiektu JSON:
DatabaseReference bruceWayneRef = DatabaseReference.getInstance().getReference()
.child("user_base/371298");
// deeply nested data can also be referenced this way, just put the fully
// qualified path in pattern shown in above code "blah/blah1/blah1-2/blah1-2-3..."
Teraz, gdy mamy odniesienie do danych, które chcemy pobrać, możemy używać detektorów do pobierania danych w aplikacjach na Androida. W przeciwieństwie do tradycyjnych wywołań, w których uruchamiane są wywołania interfejsu API REST za pomocą modernizacji lub salwy, tutaj wymagany jest prosty odbiornik oddzwaniania, aby uzyskać dane. Firebase sdk wywołuje metody wywołania zwrotnego i gotowe.
Istnieją dwa typy nasłuchiwania, które możesz podłączyć, jeden to ValueEventListener, a drugi to ChildEventListener (opisane w następnej sekcji). Dla każdej zmiany danych w węźle, do której mamy odwołania i dodane detektory, detektory zdarzeń o wartości zwracają całą strukturę JSON, a detektor zdarzeń podrzędnych zwraca określone dziecko, w którym nastąpiła zmiana. Oba są użyteczne na swój sposób. Aby pobrać dane z bazy firebase, możemy dodać jednego lub więcej detektorów do referencji bazy danych firebase (lista userDBRef stworzyliśmy wcześniej).
Oto przykładowy kod (objaśnienie kodu po kodzie):
userDBRef.addValueEventListener(new ValueEventListener() {
@Override
public void onDataChange(DataSnapshot dataSnapshot) {
User bruceWayne = dataSnapshot.child("371298").getValue(User.class);
// Do something with the retrieved data or Bruce Wayne
}
@Override
public void onCancelled(DatabaseError databaseError) {
Log.e("UserListActivity", "Error occured");
// Do something about the error
});
Czy zauważyłeś, że typ klasy minął. DataSnapshot może konwertować dane JSON na nasze zdefiniowane POJO, po prostu przekaż odpowiedni typ klasy.
Jeśli Twój przypadek użycia nie wymaga wszystkich danych (w naszym przypadku tabela user_base) za każdym razem, gdy nastąpi jakaś niewielka zmiana lub powiesz, że chcesz pobrać dane tylko raz , możesz użyć metody addListenerForSingleValueEvent () z odwołania do bazy danych. To wywołuje oddzwonienie tylko raz.
userDBRef.addListenerForSingleValueEvent(new ValueEventListener() {
@Override
public void onDataChange(DataSnapshot dataSnapshot) {
// Do something
}
@Override
public void onCancelled(DatabaseError databaseError) {
// Do something about the error
});
Powyższe próbki podadzą wartość węzła JSON. Aby zdobyć klucz, wystarczy zadzwonić:
String myKey = dataSnapshot.getKey();
Słuchanie aktualizacji potomnych
Weźmy przykład zastosowania, np. Aplikację do czatowania lub współpracującą aplikację do listy zakupów (która w zasadzie wymaga synchronizacji listy obiektów między użytkownikami). Jeśli korzystasz z bazy danych firebase i dodajesz detektor zdarzeń wartościowych do węzła nadrzędnego czatu lub nadrzędnego węzła listy zakupów, zakończysz całą strukturę czatu od początku czasu (miałem na myśli początek czatu) za każdym razem, gdy dodawany jest węzeł czatu ( tzn. ktoś mówi cześć). To, czego nie chcemy robić, interesuje nas tylko nowy węzeł lub tylko stary węzeł, który został usunięty lub zmodyfikowany, niezmienionych nie należy zwracać.
W takim przypadku możemy użyć ChildEvenListener . Bez dalszego adiuu, oto przykładowy kod (patrz poprzednie sekcje przykładowych danych JSON):
userDBRef.addChildEventListener(new ChildEventListener() {
@Override
public void onChildAdded(DataSnapshot dataSnapshot, String s) {
}
@Override
public void onChildChanged(DataSnapshot dataSnapshot, String s) {
}
@Override
public void onChildRemoved(DataSnapshot dataSnapshot) {
}
@Override
public void onChildMoved(DataSnapshot dataSnapshot, String s) {
//If not dealing with ordered data forget about this
}
@Override
public void onCancelled(DatabaseError databaseError) {
});
Nazwy metod są oczywiste. Jak widać, za każdym razem, gdy dodawany jest nowy użytkownik lub modyfikowana jest jakaś właściwość istniejącego użytkownika, użytkownik jest usuwany lub usuwany, wywoływana jest odpowiednia metoda wywołania zwrotnego nasłuchiwania zdarzeń z odpowiednimi danymi. Więc jeśli odświeżasz interfejs użytkownika dla powiedzmy aplikacji czatu, pobierz JSON z onChildAdded () parsuj do POJO i umieść go w swoim interfejsie użytkownika. Pamiętaj tylko, aby usunąć słuchacza, gdy użytkownik opuści ekran.
onChildChanged () daje całą wartość potomną ze zmienionymi właściwościami (nowymi).
Funkcja onChiledRemoved () zwraca usunięty węzeł potomny.
Pobieranie danych z paginacją
Gdy masz ogromną bazę danych JSON, dodanie detektora zdarzeń o wartości nie ma sensu. Zwróci olbrzymi JSON i jego analiza byłaby czasochłonna. W takich przypadkach możemy zastosować paginację i pobrać część danych oraz wyświetlić lub przetworzyć je. Niby leniwe ładowanie lub pobieranie starych czatów, gdy użytkownik kliknie, aby pokazać starszy czat. W takim przypadku można użyć zapytania .
Weźmy nasz stary przykład w poprzednich sekcjach. Baza użytkowników zawiera 3 użytkowników, jeśli liczba ta rośnie do 300 tysięcy i chcesz pobrać listę użytkowników w partiach po 50:
// class level
final int limit = 50;
int start = 0;
// event level
Query userListQuery = userDBRef.orderByChild("email").limitToFirst(limit)
.startAt(start)
userListQuery.addValueEventListener(new ValueEventListener() {
@Override
public void onDataChange(DataSnapshot dataSnapshot) {
// Do something
start += (limit+1);
}
@Override
public void onCancelled(DatabaseError databaseError) {
// Do something about the error
});
Tutaj można dodawać i odsłuchiwać wydarzenia wartościowe lub potomne. Wywołaj zapytanie ponownie, aby pobrać kolejne 50. Pamiętaj, aby dodać metodę orderByChild () , bez tego to nie zadziała. Firebase musi znać kolejność stronicowania.