Skip to content

Commit e93ec4d

Browse files
committed
cloud firestore post updated
1 parent 1d7efb1 commit e93ec4d

File tree

1 file changed

+201
-30
lines changed

1 file changed

+201
-30
lines changed

_drafts/2019-04-08-cloud_firestore.md

Lines changed: 201 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -14,19 +14,19 @@ keywords: "firebase, chmura, dane, baza, cloud, storage, database, nosql, androi
1414
`Cloud Firestore` jest elastyczną, skalowalną, hierarchiczną bazą danych `NoSQL` w chmurze służącą do przechowywania i synchronizowania w czasie rzeczywistym danych między klientami i serwerem. Oferuje wsparcie w trybie `offline` co ułatwia budowania responsywnych aplikacji działających niezależnie od jakości połączenia sieciowego. Umożliwia integracje z `Google Cloud Platform` i funkcjonalnościami `Firebase` jak np. `Cloud Functions` czy `Authentication` oraz innymi zewnętrznymi usługami. Dostęp i modyfikacja danych odbywa się zgodnie z zasadami zachowania bezpieczeństwa i prywatności, które mogą być dodatkowo definiowane za pomocą reguł w konsoli Firebase.
1515

1616
## Model
17-
Dane reprezentowane są w postaci dokumentów (zbliżonych w stukurze do formatu `JSON`) przechowywanych w zorganizowanej kolekcji w hierarchicznej strukturze bazy danych `NoSQL` (w przeciwieństwie do `SQL` nie ma tabel ani wierszy). Każdy `dokument` składa się z nazwy, zbioru par `klucz - wartość` (prymityw lub obiekt złożony) i może posiadać obiekty zagnieżdzone i podkolekcje. `Kolekcje` pełnią rolę kontenera dla różnych dokumentów i nie mogą posiadać innych kolekcji, a w doborze ich zawartości warto zachować logiczny porządek dzieląc dokumenty ze względu na kategorie i przeznaczenie co upraszcza poruszanie się po strukturze w kodzie klienta. `Podkolekcja` jest kolekcją w dokumencie i służy do budowania zagnieżdzonej struktury folderów.
17+
Dane reprezentowane są w postaci dokumentów (zbliżonych w stukurze do formatu `JSON`) przechowywanych w zorganizowanej kolekcji w hierarchicznej strukturze bazy danych `NoSQL` (w przeciwieństwie do `SQL` nie ma tabel ani wierszy). Każdy `dokument` (`document`) składa się z nazwy, zbioru par `klucz - wartość` (prymityw lub obiekt złożony) i może posiadać obiekty zagnieżdzone i podkolekcje. `Kolekcje` (`collection`) pełnią rolę kontenera dla różnych dokumentów i nie mogą posiadać innych kolekcji, a w doborze ich zawartości warto zachować logiczny porządek dzieląc dokumenty ze względu na kategorie i przeznaczenie co upraszcza poruszanie się po strukturze w kodzie klienta. `Podkolekcja` jest kolekcją w dokumencie i służy do budowania zagnieżdzonej struktury folderów.
1818

1919
//TODO rysunek
2020

21-
Ze względu na optymalizacje wydajności dostępu do bazy danych wprowadzony został mechanizm indeksowania, który wyróżnia dwa rodzaje indeksów: `single-field` (przechowuje posortowaną mapę wszystkich dokumentów zawierających dane pole) i `composite` (przechowuje posortowaną mapę wszystkich dokumentów zawierających wiele danych pól). Cloud Firestore jest zaprojektowany przede wszystkim z myślą o dużych kolekcjach małych dokumentów. Aby uzyskać dostęp do dokumentu lub kolekcji należy uzyskać `referencje`.
21+
Ze względu na optymalizacje wydajności dostępu do bazy danych wprowadzony został mechanizm indeksowania, który wyróżnia dwa rodzaje indeksów: `single-field` (przechowuje posortowaną mapę wszystkich dokumentów zawierających dane pole) i `composite` (przechowuje posortowaną mapę wszystkich dokumentów zawierających wiele danych pól). Cloud Firestore jest zaprojektowany przede wszystkim z myślą o dużych kolekcjach małych dokumentów. Aby uzyskać dostęp do dokumentu lub kolekcji należy uzyskać `referencje` - obiekt typu `DocumentReference`.
2222

2323
{% highlight kotlin %}
2424
private fun createReferences() {
25-
val database = FirebaseFirestore.getInstance()
26-
val collectionRef = database.collection("collection")
27-
val documentRef = collectionRef.document("document")
28-
val documentRef2 = database.document("collection/document") //or use direct path instead
29-
val nestedDocumentRef = documentRef.collection("subcollection").document("nested")
25+
val database = FirebaseFirestore.getInstance()
26+
val collectionRef = database.collection("collection")
27+
val documentRef = collectionRef.document("document")
28+
val documentRef2 = database.document("collection/document") //or use direct path instead
29+
val nestedDocumentRef = documentRef.collection("subcollection").document("nested")
3030
}
3131
{% endhighlight %}
3232

@@ -35,39 +35,210 @@ Dokumenty mogą przechowywać wartości `prymitywów` (numeryczne, logiczne, tek
3535

3636
{% highlight kotlin %}
3737
private fun createExampleData() {
38-
val document = HashMap<String, Any?>()
39-
document["text"] = "value"
40-
document["logic"] = true
41-
document["number"] = 9.99
42-
document["date"] = Timestamp(Date())
43-
document["list"] = arrayListOf(1,2,3)
44-
document["null"] = null
38+
val document = HashMap<String, Any?>()
39+
document["text"] = "value"
40+
document["logic"] = true
41+
document["number"] = 9.99
42+
document["date"] = Timestamp(Date())
43+
document["list"] = arrayListOf(1,2,3)
44+
document["null"] = null
4545
}
4646

4747
private fun createSimilarData() {
48-
val person = HashMap<String, Any>()
49-
person["name"] = "John"
50-
person["surname"] = "Walker"
51-
person["born"] = 1805
48+
val person = HashMap<String, Any>()
49+
person["name"] = "John"
50+
person["surname"] = "Walker"
51+
person["born"] = 1805
5252

53-
//structure of data in the same collection don't have to be exactly the same
54-
val person2 = HashMap<String, Any>()
55-
person["name"] = "Jasper"
56-
person["second"] = "Newton"
57-
person["surname"] = "Daniel"
58-
person["born"] = 1850
53+
//structure of data in the same collection don't have to be exactly the same
54+
val person2 = HashMap<String, Any>()
55+
person2["name"] = "Jasper"
56+
person2["second"] = "Newton"
57+
person2["surname"] = "Daniel"
58+
person2["born"] = 1850
5959

60-
//add to database by passing those objects
60+
//add to database by passing those objects
6161
}
6262

6363
private fun createDataByObject() {
64-
val person = Person("William", "Grant", 1839)
65-
//add to database by passing object
64+
val person = Person("William", "Grant", 1839)
65+
//add to database by passing object
6666
}
6767
{% endhighlight %}
6868

69-
## Operacje
70-
//TODO
69+
## Zapis
70+
Aby dokonać zapisu w Cloud Firestore należy podać identyfikator dokumentu oraz przekazać obiekt danych metodą `set` lub w przypadku automatycznego generowania id wystarczy tylko przekazać danę metodą `add`. Jeśli dokument nie istnieje wówczas zostanie utworzy, w przeciwnym razie jego zawartośc zostanie nadpisana, chyba że zostaną użyte odpowiednie opcje. Dodanie obserwatorów umożliwia śledzenie stanu operacji.
71+
72+
{% highlight kotlin %}
73+
private fun addDataWithId() {
74+
//create data
75+
var club = Club("Milan", "Italy", 1899)
76+
club.uclTrophiesYear = listOf(1963, 1993, 1989, 1990, 1994, 2003)
77+
club.budget = 1000L
78+
79+
//put data to database
80+
database.collection("club").document("acmilan").set(club)
81+
.addOnSuccessListener {
82+
//some action
83+
}
84+
.addOnFailureListener {
85+
//some action
86+
}
87+
88+
//or use reference.set(club, SetOptions.merge()) for merge if file exists
89+
}
90+
91+
private fun addDataWithoutId() {
92+
var club = Club("Chelsea FC", "England", 1905)
93+
database.collection("club").add(club)
94+
.addOnSuccessListener {
95+
//some action
96+
}
97+
.addOnFailureListener {
98+
//some action
99+
}
100+
}
101+
{% endhighlight %}
102+
103+
W celu aktualizacji wybranych pól dokumentu bez nadpisywania całej jego zawartości należy użyć metodę `update` na referencji. Odwołując się do obiektów zagnieżdzonych należy wykorzystać właściwą notację oddzielając pola i wartości przecinkiem, a w przypadku modyfikacji elementów tablic użyć metod `FieldValue.arrayUnion` oraz `FieldValue.arrayRemove`.
104+
105+
{% highlight kotlin %}
106+
private fun updateData() {
107+
val reference = database.collection("club").document("acmilan")
108+
reference.update("name", "AC Milan")
109+
//add some listeners
110+
}
111+
112+
private fun updateArrayData() {
113+
//add new data
114+
database.collection("club").document("acmilan").set(club, SetOptions.merge())
115+
116+
reference.update("uclTrophiesYear", FieldValue.arrayUnion(2007))
117+
//add some listeners
118+
}
119+
{% endhighlight %}
120+
121+
Usunięcie dokumentu następuje poprzez wywołanie metody `delete` na referencji dokumentu (nie powoduje usunięcia jego podkolekcji), natomiast przypisanie wartości `FieldValue.delete()` do pola w operacji `update` powoduje jego usunięcie. Usuwanie całych kolekcji po stronie klient jest niezalecane ze względu na zagrożenia wydajności, wycieku pamięci, bezpieczeńśtwa po stronie klienta wynikające z potrzeby wysyłania wielu żądań.
122+
123+
{% highlight kotlin %}
124+
private fun deleteDocument() {
125+
database.collection("club").document("acmilan").delete()
126+
//add some listeners
127+
}
128+
129+
private fun deleteField() {
130+
val reference = database.collection("club").document("acmilan")
131+
132+
val updates = HashMap<String, Any>()
133+
updates["uclTrophiesYear"] = FieldValue.delete()
134+
135+
reference.update(updates)
136+
//add some listeners
137+
}
138+
{% endhighlight %}
139+
140+
## Odczyt
141+
Pobieranie dokumentów odbywa się przez wywołanie metody `get` na referencji dokumentu lub kolekcji. Dodatkowo można ustawić źródło pobierania danych na `serwer` lub pamięć `cache` oraz `parsować` otrzymane dane do instancji klasy. Rozszerzenie żądania o żłożone zapytania pozwala filtrowanie (`where`), sortowanie (`orderBy`) i ograniczanie liczby wyników spełniających założenia (`limit`).
142+
143+
{% highlight kotlin %}
144+
private fun getDocument() {
145+
val reference = database.collection("club").document("acmilan")
146+
147+
//add optional source and listeners and retrieve document
148+
val source = Source.SERVER //change to Source.CACHE for cached data
149+
reference.get(source)
150+
.addOnSuccessListener { document ->
151+
//some action on DocumentSnapshot instance
152+
val club = document.toObject(Club::class.java)
153+
}
154+
.addOnFailureListener { exception ->
155+
//some action
156+
}
157+
}
158+
159+
private fun getMultipleDocuments() {
160+
//add some listeners and optional query clausule
161+
database.collection("club").whereEqualTo("country", "Italy").get()
162+
.addOnSuccessListener { documents ->
163+
//some action on QueryDocumentSnapshot instance
164+
for(document in documents) { }
165+
}
166+
}
167+
168+
private fun getDocumentsByQueries() {
169+
//add queries and listeners
170+
database.collection("club")
171+
.whereEqualTo("country", "Italy")
172+
.whereLessThan("year", 1930)
173+
.orderBy("year", Query.Direction.DESCENDING)
174+
.limit(5)
175+
.get()
176+
.addOnSuccessListener { documents ->
177+
//some action on QueryDocumentSnapshot instance
178+
}
179+
}
180+
{% endhighlight %}
181+
182+
Ponadto istnieje możliwość nasłuchiwania modyfikacji danych dla dokumentu i kolekcji w czasie rzeczywistym poprzez dodanie obiektu słuchacza z uwzględnieniem źródła i typu zmian.
183+
184+
{% highlight kotlin %}
185+
private fun listenForDocumentChanges() {
186+
val reference = database.collection("club").document("acmilan")
187+
188+
reference.addSnapshotListener(EventListener<DocumentSnapshot> { snapshot, exception ->
189+
//check is exception
190+
if (exception != null) return@EventListener
191+
192+
//check source of the changes
193+
if (snapshot != null && snapshot.metadata.hasPendingWrites()) {
194+
//local changes
195+
}
196+
else {
197+
//server changes
198+
}
199+
200+
//check type of the change
201+
for (changes in snapshots!!.documentChanges) {
202+
//could be: DocumentChange.Type.ADDED, MODIFIED or REMOVED
203+
}
204+
})
205+
206+
//do the same on the multiple documents from collection to listen more documents
207+
}
208+
{% endhighlight %}
71209

72210
## Transakcje
73-
//TODO
211+
Cloud Firestore wspiera atomowe operacje dla zapisu i odczytu, które są implikowane tylko wtedy kiedy wszystkie operacje zbioru zakończą się pozytywnie, zatem wykonanie zbioru operacji atomowych jest spójne i nierozdzielne. Wyróżnia się dwa typy operacji atomowych: `transakcje` (`transaction`) - zbiór operacji odczytu i zapisu na wielu dokumentach oraz `zestaw zapisu` (`batched write`) - zbiór operacji zapisu dla wielu dokumentów. Transakcje pozwalają na grupowanie wielu operacji w jedną transakcje i wykorzystywane są przede wszystkim do modyfikacji dokumentów bazując na ich bieżącym stanie. Wykonywane są za pomocą metody `runTransaction` lub zbioru operacji zapisu na obiekcie `WriteBatch`.
212+
213+
{% highlight kotlin %}
214+
private fun makeTransaction() {
215+
val reference = database.collection("club").document("acmilan")
216+
217+
database.runTransaction { transaction ->
218+
val snapshot = transaction.get(reference)
219+
val budget = snapshot.getLong("budget")
220+
if(budget <= 1000)
221+
transaction.update(reference, "budget", budget + 500L)
222+
}
223+
}
224+
225+
private fun makeWriteBatch() {
226+
val batch = database.batch()
227+
228+
//add new document
229+
val realMadrid = Club("Real Madrid", "Spain", 1902)
230+
val rmReference = database.collection("club").document("realmadrid")
231+
batch.set(rmReference, realMadrid)
232+
233+
//update current
234+
val acmReference = databae.collection("club").document("acmilan")
235+
batch.update(acmReference, "budget", 2000L)
236+
237+
//do more add, update, delete operations
238+
239+
//commit operations and add some listeners
240+
batch.commit.addOnCompleteListener {
241+
//some action
242+
}
243+
}
244+
{% endhighlight %}

0 commit comments

Comments
 (0)