Update multiple Firebase documents using an array of Document IDs in Swift - ios

I have an array of Strings which represent Firebase Document IDs like so:
var idArray = [“PuLDb90jgz3a5P8bLQoy”, “PMKoZIp46umXQnUlA64a”, “cVGbD3Wy4gWjZ9fZP7h1”]
This Array is dynamic and has been generated by a previous getDocuments call. It could have up to 15 ID strings in it, so it cannot be hard coded.
Within each Firebase Document I have an Int field called menuPosition set to a current value.
What I am trying to do is update each document which appears in the idArray and simply -1 from the menuPosition field value in one go.
What should be so straightforward is driving me crazy - does anyone know a simple way to do it? Obviously, if I put it in a for loop then the code will run too many times and my menuPosition value would be wrong. I just want each operation to perform once.
Any help would be greatly appreciated

Just run a loop over the document IDs and decrement the field in each iteration.
var idArray = ["J2LovReBF0v8F4e0RSBU", "UcW8tsgld2ZuUo92xfP8", "oHTJ4iO1NWCK7x1aryne"]
for docId in idArray {
Firestore.firestore().document("someCollection/\(docId)").updateData([
"menuPosition": FieldValue.increment(Int64(-1))
])
}
If you want this operation to be atomic then just wrap it in a batch write.
var idArray = ["J2LovReBF0v8F4e0RSBU", "UcW8tsgld2ZuUo92xfP8", "oHTJ4iO1NWCK7x1aryne"]
let db = Firestore.firestore()
let batch = db.batch()
for docId in idArray {
batch.updateData([
"menuPosition": FieldValue.increment(Int64(-1))
], forDocument: db.document("someCollection/\(docId)"))
}
batch.commit()

Related

Airtable Scripting block - Batch copy a field to another field in same table (10,000 records)

I'm trying to copy one field to another field in the same table with 10,000 + records, in batches of 50 using the Scripting App.
What am I doing wrong in this code block? It only copies the first record. If I remove the await, it'll copy 15 records then stop.
let table = base.getTable('Merchants');
let view = table.getView('Grid view');
let query = await view.selectRecordsAsync();
let records = query.records;
updateLotsOfRecords(records);
async function updateLotsOfRecords(records) {
let i = 0;
while (i < records.length) {
const recordBatch = records.slice(i, i + 50);
for (let record of recordBatch) {
let sourceValue = record.getCellValue('Merchant');
await table.updateRecordAsync(record, { 'LogoBase64': sourceValue });
}
i += 50;
}
}
you should use updateRecordsAsync function, not updateRecordAsync
When using single update function in loop, there is no sense to divide it into batches.
You exceed some limit of calls per second, that's why it stops.
For multiple updates, you need to use updateRecordsAsync, like this
while (recordsToWrite.length > 0) {
await updates.updateRecordsAsync(recordsToWrite.slice(0, 50));
recordsToWrite = recordsToWrite.slice(50);
}
Data that you should pass to it, more complex. I learned JS for 3 months and still have difficulties understandins all these "arrays of arrays of objects, passed via object's property". But that's the key to unerstand JS.
It's quite hard to leave basic/pascal habits, with plenty of inserted FOR loops, and GOTO sometimes))
I think, you already found the answer for 2 months, so my answer may be useless, but when i write it here, maybe i understand it better for myself. And help to some beginners also.
For single write, you pass (record, Object), where object is {field:'Value}
For multiple, you should pass
Array of Objects, where
Object is {id:recordID, fields:{object2}} , where
object2 is array of obj3 [ {obj3},{obj3}, {obj3} ], where
obj3 is a { 'Name or ID of field': fieldvalue }
you script might be:
let query = await view.selectRecordsAsync();
let updates=query.records.map(rec=>{
Map method can be applied for arrays, and 'query.records' is array of records. Here
'rec' is loop variable inside this "arrowfunction"
now let's create obj3 , in our case { 'Name or ID of field': fieldvalue }
{'LogoBase64':rec.getCellValue('Merchant')}
wrap it into fields property
fields:{'LogoBase64':rec.getCellValue('Merchant')}
and add record id
wrapping as Object.
To avoid complex string with linebreaks, and to make object creation easier, we can do it with function:
{rec.id, fields:{'LogoBase64':rec.getCellValue('Merchant')}}
fuction myObj(rec){return {rec.id, fields:{'LogoBase64':rec.getCellValue('Merchant')}}
map(rec=>myObj(rec)) - can be written as map(myObj)
we need array of objects, and map method gets first array, doing something with each element and return other array, of results. like we need.
and now finally we get
let table = base.getTable('Merchants');
let view = table.getView('Grid view');
let query = await view.selectRecordsAsync();
function myObj(rec){return {'id':rec.id,'fields':{'Logobase64':rec.getCellValue('Merchant')}}};
let updates=query.records.map(myObj);
while (updates.length > 0) {
await table.updateRecordsAsync(updates.slice(0, 50));
updates = updates.slice(50); }

How to structure a search list in Firestore?

I want to show a list of artists in my app which the user will be able to search through. I'm not sure however how to save this in Firestore?
First I created a collection "searchLists" with a document for each DJ but that means a lot of document reads so that's out of the question.
Now I created a document called "artists" which has a field "artistsDictionary" which contains all the artists.
| searchLists (collection)
* artists (document)
- artistsArray (array)
0: (map)
name: "Artist 0" (string)
1: (map)
name: "Artist 1" (string)
2: (map)
name: "Artist 2" (string)
And I retrieve and parse the array as followed:
let docRef = db.collection("searchLists").document("artists")
docRef.getDocument { (document, error) in
if let document = document, document.exists {
guard let documentData = document.data() else { return }
let artistsDictionaryArray = documentData["artistsArray"] as? [[String: Any]] ?? []
let parsedArtists = artistsDictionaryArray.compactMap {
return SimpleArtist(dictionary: $0)
}
self.artistsArray = parsedArtists
} else {
print("Document does not exist")
}
}
(SimpleArtist is a struct containing a "name" field.)
And I mean, it works, but I'm still new to Firestore and this seems kinda off. Is it? Or is this how I should/could do it?
First I created a collection "searchLists" with a document for each DJ but that means a lot of document reads so that's out of the question.
This is the right approach, so you should go ahead with it.
Why do I say that?
According to the official documentation regarding modeling data in a Cloud Firestore database:
Cloud Firestore is optimized for storing large collections of small documents.
Storing data in an array is not a bad option but this is most likely used, let's say to store favorite djs. I say that because the documents have limits in Firestore. So there are some limits when it comes to how much data you can put into a document. According to the official documentation regarding usage and limits:
Maximum size for a document: 1 MiB (1,048,576 bytes)
As you can see, you are limited to 1 MiB total of data in a single document. When we are talking about storing text, you can store pretty much but as your array getts bigger, be careful about this limitation.
First off, Alexs' answer is 100% correct.
I want to add some additional data points that may help you in the long run.
The first item is arrays. Arrays are very challenging in NoSQL databases - while they provide a logical sequence data via the index, 0, 1, 2 they don't behave like an array in code - so for example; Suppose you wanted to insert an item at an index. Well - you can't (*you can but it's not just a simple 'insert' call). Also, you can't target array elements in queries which limits their usefulness. The smallest unit of change in a Firestore array field is the entire field - smaller changes to individual elements of a field can't be made. The fix is to not use arrays and to let FireStore create the documentID's for you data 'objects' on the fly e.g. the 'keys' to the node
The second issue - (which may not be an issue currently) is how the data is being handled. Suppose you release your app and a user has 2 million artists in their collection - with your code as is, all of that data is downloaded at one time which will probably not be the best UI experience but additionally, it could overwhelm the memory of the device. So working in 'chunks' of data it a lot easier on the device, and the user.
So I put together some sample code to help with that.
First a class to store your Artist data in. Just keeps track of the documentID and the artist name.
class ArtistClass {
var docId = ""
var name = ""
init(aDocId: String, aName: String) {
self.docId = aDocId
self.name = aName
}
}
and a class array to keep the artists in. This would be a potential dataSource for a tableView
var artistArray = [ArtistClass]()
This is to write an artist as a document instead of in an array. The documentID is a FireStore generated 'key' that's created for each artist.
func writeArtists() {
let artistsRef = self.db.collection("artists")
let floyd = [
"name": "Pink Floyd"
]
let zep = [
"name": "Led Zeppelin"
]
let who = [
"name": "The Who"
]
artistsRef.addDocument(data: floyd)
artistsRef.addDocument(data: zep)
artistsRef.addDocument(data: who)
}
and then function to read in all artists.
func readArtists() {
let artistsRef = self.db.collection("artists")
artistsRef.getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
for document in querySnapshot!.documents {
let docId = document.documentID
let name = document.get("name") as! String
let artist = ArtistClass(aDocId: docId, aName: name)
self.artistArray.append(artist)
}
for a in self.artistArray { //prints the artists to console
print(a.docId, a.name)
}
}
}
}
So your data in Firestore looks like this
artists (collection)
8lok0a0ksodPSSKS
name: "Let Zeppelin"
WKkookokopkdokas
name: "The Who"
uh99jkjekkkokoks
name: "Pink Floyd"
so then the cool part. Suppose you have a tableView that shows 10 artists at a time with a down button to see the next 10. Make this change
let artistsRef = self.db.collection("artists").order(by: "name").limit(to: 10)
Oh - and you'll notice the function of sorting now goes the server instead of the device - so if there's a million artists, it's sorted on the server before being delivered to the device which will be significantly faster.
You can also then more easily perform queries for specific artist data and you won't need to be as concerned about storage as each artist is their own document instead of all artists in one.
Hope that helps!

iOS : Firebase Send/Save Data in chunks

I have trying to save data in firebase like this
class FirebaseManager {
static let shared = FirebaseManager()
private let tableRef = Database.database().reference(withPath: "XYZDemo")
func add(item: HealthData) {
self.tableRef.childByAutoId().setValue(item.dictionary)
}
}
This code save data one bye one.
How can i add more than one data at a time i.e save 5 values at once?
To simultaneously write to specific children of a node without overwriting other child nodes, use the updateChildValues method.
Basicly you create an array with the updates/writes you want to perform and write that array to Firebase. Here is an example showing 1 post being written to two different locations:
//Create the new key
let key = self.tableRef.childByAutoId().key
//Post data that is going to be written
let post = ["uid": userID,
"author": username,
"title": title,
"body": body]
//Create the array with (two) updates
let childUpdates = ["/posts/\(key)": post,
"/user-posts/\(userID)/\(key)/": post]
//Write the array to the database
ref.updateChildValues(childUpdates)
A couple things to keep in mind when you are dong this:
It's all or nothing, either all writes fail or all writes succeed. If even 1 of the writes in the array fails then all writes will fail.
Make sure you use the correct reference for each write because it will override all the data at that location.

Best practice to retrieve list of friends information. How to detect multiple queries are finished?

I have users structure lke this:
{
"users": {
"uniqueID1": {
"name": "Anon",
"friends": {
"uniqueID2": true,
"uniqueID3": true
}
}
"uniqueID2": { },
"uniqueID3": { },
}
}
I want to show a user's friends' names. I have to access $user/friends/ to get list of unique IDs, and iterate the list to get friend's information. But iterating the unique ID is making multiple queries, and I have to always check if all of my queries are finished. According to the doc, it seems multiple queries will not impact the performance too much, but if I want to update my view only when all of the queries are finished, I have to check how many queries are finished.
Is there no way of 'execute a completion block when all queries are finished'?
Pseudocode
var totalNumOfFriends = 0
var tempArray = NewArray()
ref(/users/uniqueID1/friends).observeEventType{ snapshot
var uIDList = snapshot.children's keys
totalNumOfFriends = uIDList .count
for uID in uIDList {
var nameRef = ref(/users/uID/name) i.e. /users/uniqueID3/name
nameRef.observeSingleEventOfType { snapshot
var username = snapshot.value
tempArray.append(username)
if tempArray.count == totalNumOfFriends {
// If counts are the same, tempArray has all of my friends' names
// Now update view using tempArray
}
}
}
}
Pseudocode explanation:
Get list of unique IDs from /users/uniqueID1/friends
'Save' number of unique IDs. (Explained in step 4)
For each unique IDs from the list, get user's name by using ref like this /users/uniquedID2/name
For each name retrieved, add it to temporary array. Once the count of the temporary array equals to the count from step 2, update my view as I have retrieved all the names.
Firebase has no built-in way to signal when a number of queries has finished. But you can easily implement this in your own code. Your approach with a counter that checks how many items have already been loaded is the most common approach for that.

Can I make a record more flexible?

Can somebody give me an example of how to make inserting data into an F# record more flexible?
I often see examples using records like this:
type Employee = {mutable name:string; mutable id:string}
let data =
[{name = "Thomas";id = "000"};
{name = "Johny";id = "001"};
{name = "Lucky";id = "002"};
{name = "Don";id = "003"}
]
Can't we start with no data at all and insert the data into the record later?
(What I mean is without declaration of the value of the data like in the example, so for example: the program is running and asking us to insert the data)
Can we doing something like this with record?
If you're talking about specifying values of a record as they become available, then you need to make fields of the record option so that you can represent the fact that value is missing. I'll use immutable records, because this is more common in functional style:
type Employee = { Name:option<string>; ID:option<string> }
Now you can create a record with only ID and add name when the user enters it:
let empty = { Name = None; ID = Some 123 }
let name = // read name from user
let full = { empty with Name = name }
If you're talking about adding items to a list as they become available, then you have several options. The direct one is to write a recursive function that repeatedly reads record and builds a list:
let rec readData i records =
let name = // read name from user
if name <> "" then
// Create new record and add it to our list
let itm = { Name = name; ID = string i }
readData (i + 1) (itm::records)
else
// Return records that we collected so far in the right order
records |> List.rev
Alternatively, you can also use sequence expressions (see for example free Chapter 12 (PDF) of Real-World Functional Programming). If you user interaction involves waiting for events (e.g. mouse click), then you can still use this style, but you'd need to wrap everything in asynchronous workflow and use Async.AwaitEvent.
Are you saw you often saw an example like that?
I'd say that it is not very idiomatic in F# to use mutable records.
Immutability is a rather large subject
to explain in one answer here, but
briefly: immutability means that the
objects you create never change:
they stay the way they were at
creation. In the immutable world, when
you want to 'change' something, you
create a new one, and throw away the
old one.
Anyway, if I understand your question correctly, you are actually talking about mutating data, not the record. So, you could have:
let data = []
let data = {name = "Thomas";id = "000"} :: data
let data = {{name = "Johny";id = "001"} :: data
But in this case, you aren't really 'changing' data, you are just creating a new list each time and pointing data at it.

Resources