Structuring data for chat app in Firebase - swift

Im following Firebase guide to structuring data for a chat app. They suggest the structure as seen below.
{
// Chats contains only meta info about each conversation
// stored under the chats's unique ID
"chats": {
"one": {
"title": "Historical Tech Pioneers",
"lastMessage": "ghopper: Relay malfunction found. Cause: moth.",
"timestamp": 1459361875666
},
"two": { ... },
"three": { ... }
},
// Conversation members are easily accessible
// and stored by chat conversation ID
"members": {
// we'll talk about indices like this below
"one": {
"ghopper": true,
"alovelace": true,
"eclarke": true
},
"two": { ... },
"three": { ... }
},
// Messages are separate from data we may want to iterate quickly
// but still easily paginated and queried, and organized by chat
// converation ID
"messages": {
"one": {
"m1": {
"name": "eclarke",
"message": "The relay seems to be malfunctioning.",
"timestamp": 1459361875337
},
"m2": { ... },
"m3": { ... }
},
"two": { ... },
"three": { ... }
}
}
How do I structure my user data so that I can easily display a list of all of the chats they are part of and for each one of them display the last message and timestamp. If I do the following structure:
"users": {
"ghopper": {
"name": "Gary Hopper",
"chats": {
"one: true",
"two": true
}
},
"alovelace" { ... }
},
I can easily get a list of each chat group for a specific user, for example ghopper, by doing (in swift):
ref.child("users").child("ghopper").child("chats").observeSingleEventOfType(.Value, withBlock: { (snapshot) in
//do something with data
}
However I won't have the lastMessage and timestamp in this snapshot. What do I need to do to access this data?
Duplicate all this data for each user? i.e adding users/ghopper/chats/one/ {"lastMessage": "ghopper: Relay malfunction found. Cause: moth.", "timestamp" : 1459361875666}
Make a query for "chats/specificGroupId" for each chat that the user is part of (adding multiple listners)?
Some other way?

How do I structure my user data so that I can easily display a list of
all of the chats they are part of and for each one of them display the
last message and timestamp.
Change the chats structure a tad by adding users who are in the chat node
"chats": {
"one": {
"title": "Historical Tech Pioneers",
"lastMessage": "ghopper: Relay malfunction found. Cause: moth.",
"timestamp": 1459361875666
users
uid_1: true
uid_3: true
},
"two": { ... },
Then you can deep query for all chats a particular user is part of - this will return the chats uid_3 is involved in
chatsRef.queryOrderedByChild("users/uid_3").queryEqualToValue(true)
.observeSingleEventOfType(.Value, withBlock: { snapshot in
//.Value can return multiple nodes within the snapshot so iterate over them
for child in snapshot.children {
let lastmsg = child.value["lastMessage"] as! String
let timestamp = child.value["timestamp"] as! String
print(lastmsg)
print(timestamp)
}
})
Note that each firebase user has a discreet user id obtained when the user is created via auth.uid. This should (generally) be used as the key for each user.

In the block where you have a list of all the chats a user is in, can you do:
var dictionary: [String: Long]
var lastMessage: String
for chat in listOfChatsUserIsIn
ref.child("chats").child("\(chat)").child("lastMessage").observeSingleEventOfType(.Value, withBlock: { (snapshot) in
lastMessage = snapshot
ref.child("chats").child("\(chat)").child("timestamp").observeSingleEventOfType(.Value, withBlock: { (snapshot) in
//add timestamp and last message to dictionary
}
}
I don't know how correct my syntax is. Still learning firebase. But, I think this is basically your second suggestion. Don't know how else you would get the data. This would be O(2n) though which isn't bad.
[[Update 1]]
I was being lazy with my code. I put lastMessage = snapshot to save it so you could add it to the dictionary in the next block.
As for Firebase being asynchronous. I think this would still work as long as you use either the timestamp or message as the key and the other as the value in the dictionary. It may be populated out of order, but you could always sort it by timestamp later. Although, yes, this probably is not best practice.
Jay, I like your solution. Wouldn't you have to also list uid_2: false?
Unfortunately, it seems like both these database structures grow by n^2 as users -> inf and as chats -> inf.

Related

how to query for key exists in object field in firestore document (firebase web sdk)

I have subjects collection. In this collection every document has tutors field that is object where key is id of tutors( from tutors collection)
tutors: {
"tutor_id_1": {
"name": "jonas",
"email": "jonas#gmail.com"
},
"tutor_id_2":{
"name": "stephen",
"email": "stephen#gmail.com"
},
"tutor_id_3":{
"name": "maria",
"email":"maria#gmail.com"
}
}
So how to query subjects where tutors field contain tutor id equal to "tutor_id_1" ?
I found one way
if I have two variables in client side
const tutorToFindId = "xxx"
const tutorToFindEmail = "YYY"
query(
collection(db, 'subjects'),
where(`tutors.${tutorToFindId}.email`, '==', `${tutorToFindEmail}`)
),
Is there any other way ??
As I understand, "tutor_id_1" is being used as a unique id. Considering that, you may structure your data model with "tutors" as a subcollection instead of a field and you will be able to get the content of that specific document as follows:
const docRef = db.collection('subjects').doc('subjectX').collection('tutors').doc(tutorToFindId);
const tutor = await docRef.get();
if (!tutor.exists) {
console.log('No such document!');
} else {
console.log('Document data:', tutor.data());
}
db.getBy({ where: { [`tutors.${tutorToFindId}.email`]: tutorToFindEmail } });
Take a look at the getBy function in https://www.npmjs.com/package/firebase-firestore-helper
Disclaimer: I am the creator of this library. It helps to manipulate objects in Firebase Firestore (and adds Cache)
Enjoy!

How to query nested data in Firebase Database?

How can I make a query to get all bookings of all users that have timestamp in the range of 1519912278 ...1520689878 ? In Firebase docs it states that I can query deeply nested children.
According to this answer, it should work https://stackoverflow.com/a/47805607 , but it return null.
What is the flow of my User app? :
1. User singn up
2. When booking is made, it is saved at /Users/UID/stripe_customer_created_with_card/bookingNumber{booking object}
Why do I need all bookings located under /Users?:
There is a second app designed for Workers which can observe /Users node and each worker should be able to get bookings from /Users based on a timeStamp. For example a worker1382 can read bookings made by /Users from 16 March 2018 to 24 March 2018.
dbRef = FIRDatabase.database().reference().child("Users")
dbRef.queryOrdered(byChild: "TimeStampDateAndTime")
.queryStarting(atValue: "1519912278")
.queryEnding(atValue: "1520689878")
.observeSingleEvent(of: .value, with: { (snapshot: FIRDataSnapshot) in
print(snapshot.value) //returns null
}
Users
UID
cus_CRC50bSq7rXuHv //stripe customer id for the user
617643762 //booking number
TimeStampDateAndTime: "1521309610"
Postcode: "E14 9DS"
BookingNumber:"617643762"
Another question: The parent key of the booking object is equal to BookingNumber. If I replace the parent key 617643762 with TimeStampDateAndTime value 1521309610, would I be able to order the bookings by time stamp and ultimately get only the ones that are within a certain range 1519912278 ...1520689878? So, the path would look like Users/UID/cus/1521309610: {object goes here}
From Firebase Documentation
Queries can also be ordered by deeply nested children, rather than only children one level down. This is useful if you have deeply nested data like this:
{
"lambeosaurus": {
"dimensions": {
"height" : 2.1,
"length" : 12.5,
"weight": 5000
}
},
}
To query the height now, we use the full path to the object rather than a single key.
Now, my question is, what can't I do if I don't know the full path?
let ref = Firebase(url:"https://dinosaur-facts.firebaseio.com/dinosaurs")
ref.queryOrderedByChild("dimensions/height").observeEventType(.ChildAdded, withBlock: { snapshot in
if let height = snapshot.value["height"] as? Double {
println("\(snapshot.key) was \(height) meters tall")
}
})
EDIT 2
Initial Structure looks like this:
{
"Users" : {
"I50dHN7wPqPJClPfYDG76gXizdD2" : { //-> user?.uid of a logged in user
"cus_CRC50bSq7rXuHv" : {
"617643762" : {
"TimeStampDateAndTime" : "1521309610"
}
}
}
}
}
Modified structure:
{
"Users" : {
"I50dHN7wPqPJClPfYDG76gXizdD2" : {
"1521309610" : { // -> store timeStamp as the immediate child key of UID rather than the booking number
"TimeStampDateAndTime" : "1521309610",
"BookingNumber": "617643762"
}
}
}
}
{
"UsersProfile":{
"I50dHN7wPqPJClPfYDG76gXizdD2":{
"StripeCustomer":"cus_CRC50bSq7rXuHv", // -> store StripeCustomer on the UsersProfile
"Name": "John Doe"
}
}
}
You can query properties at a known path under each child node. So you can query dimensions/weight in the dinosaur sample, and you can query 617643762/TimeStampDateAndTime from your data.
But you can't query */TimeStampDateAndTime, since that's not a known path.
If you need to get a list of bookings in a date range across all users, you will need to modify your data structure to allow that query. For example, by adding a top-level list of bookings:
"Bookings": {
"book_617643762"
Customer: "cus_CRC50bSq7rXuHv"
TimeStampDateAndTime: "1521309610"
Postcode: "E14 9DS"
}
With this additional structure, you can then query:
dbRef = FIRDatabase.database().reference().child("Bookings")
dbRef.queryOrdered(byChild: "TimeStampDateAndTime")
.queryStarting(atValue: "1519912278")
.queryEnding(atValue: "1520689878")
.observeSingleEvent(of: .value, with: { (snapshot: FIRDataSnapshot) in
print(snapshot.value) //returns null
}

How do I retrieve a key from a firestore document (using swift 4/ios)

In the firebase docs for firebase database there is info on flattening a database and a message app is used as an example.
https://firebase.google.com/docs/database/web/structure-data
// Conversation members are easily accessible
// and stored by chat conversation ID
"members": {
// we'll talk about indices like this below
"one": {
"ghopper": true,
"alovelace": true,
"eclarke": true
},
"two": { ... },
"three": { ... }
},
Under "members"/"one" each key is the name of a participating member in the chat. I'm pretty sure in firebase database there is a getKey method to get each key.
I have set up my database (using firestore instead of firebase) in a similar way but where each chat is a unique identifier and the document contains the members of the chat where the key is their firebase auth id eg.
var docsref: DocumentReference?
docsref = self.defaultStore?.colleection("chats").document(chatID)
docsref?.getDocument { (document, error) in
if let _ = document {
print("document!.data()")
} else {
print("Document does not exist")
}
}
And when I output:
print(document!.data())
I get ["2mHuccccxxxxxxxxxxk4wkIAt33": 1] which is the firebase auth code of one of the participating chat members and a boolean.
I would like to get that key but wasn't sure how.
Thanks.
I'm not sure if it's the exact Syntax but self.defaultStore?.colleection might be a typo with the double E. It could also possibly be that you're missing a step. You're getting inside the chatID and you're expecting to output the keys inside members without actually accessing it. I'd access members after .document(chatID)
You can get it as follows.
to get value document.data()
to get key document.documentID

In terms of application running time, is putting array elements on the root of UserDefaults preferred than putting them in container key?

I have an array of chat threads, which each also has array of chat messages.
Thread 1: "Hello world."
Participant: Alex, Billy, Cherry
Alex = "Hello"
Billy = "World"
Cherry = "Hahaha"
Thread 2: personal chat
Participants: Alex, Daniel
Alex = "Gday mate!"
Daniel = "Hey, how is it going?"
Alex = "Cool, lots of fun!"
I want to save this in UserDefaults. All of these will be saved in form of dictionaries and arrays.
Now imagine structure like this with thousands of threads and millions of messages. Then imagine I have to save it on the UserDefaults. Which way is better for me in saving these information?
Method 1: Save everything under one key "threads". In this method, I have to load everything and save everything each time I add/change/delete even one message or thread.
{
"threads": [
{
"id":90, "title":"Hello World", "participants":[7,12,54],
"messages": [
{ "id":827, "sender":7, "text":"Hello" },
{ "id":828, "sender":12, "text":"World" },
{ "id":836, "sender":7, "text":"Hahaha" }
...
]
},
{
"id":92, "title":"", "participants":[7,9],
"messages": [
{ "id":850, "sender":7, "text":"Gday mate!" },
{ "id":855, "sender":12, "text":"Hey, how is it going?" },
{ "id":861, "sender":7, "text":"Cool, lot's of fun!" }
...
]
}
...
]
}
Method 2: Break down the threads into elements, and save them one by one on the root. This way I'll only operating with one key at a time. Still have the original "threads" key though, to know what thread entries do I have. But that means the key on the the root level is going to be massive (the phone needs to load all the root key each time I tried to access UserDefaults?), and I'm not only going to have just chats to save.
{
"threads": [ 90, 92, ... ],
"thread-90": {
"id":90, "title":"Hello World", "participants":[7,12,54],
"messages": [
{ "id":827, "sender":7, "text":"Hello" },
{ "id":828, "sender":12, "text":"World" },
{ "id":836, "sender":7, "text":"Hahaha" }
...
]
},
"thread-92": {
"id":92, "title":"", "participants":[7,9],
"messages": [
{ "id":850, "sender":7, "text":"Gday mate!" },
{ "id":855, "sender":12, "text":"Hey, how is it going?" },
{ "id":861, "sender":7, "text":"Cool, lot's of fun!" }
...
]
}
...
}
Which one is better in the long run in terms of access speed, application performance, and CPU load, if all of these will be accessed quite frequent? I don't know how significant the loading time is if it's from UserDefaults.

How can I retrieve data with queryEqualToValue in auto id child. Firebase-iOS

As my question. I can't retrieve data from firebase when I try to use "queryEqualToValue" with auto id key.
self.ref.child(..my child..).queryOrderByChild("name").queryEqualToValue("my name")
auto child id above child "name".
Edit: for my json tree
Students
(auto id)
name
first name
nick name
My real data doesn't like this but this's for example structure.
I really want to check equal to "first name".
Here's my code
let ref = FIRDatabase.database().reference()
ref.child("Students").queryOrderedByChild("name").queryEqualToValue("Jason bourne").observeEventType(.Value, withBlock: { snapshot in
print("value : " + snapshot.value)
}
Given your realtime database looks something like this:
{
"students": {
1: {
"name": {
"first_name": "Nathapong",
"nick_name": "Oniikal3"
}
}
}
}
You can observe the students path with the event type ChildAdded and order the query by child key name/first_name. Then you can use queryEqualToValue to find students with a particular first name.
let ref = FIRDatabase.database().referenceWithPath('students').queryOrderByChild("name/first_name").queryEqualToValue("Nathapong")
ref.observeSingleEventOfType(.ChildAdded, block: { snapshot in
print(snapshot)
})
This code should work. Check for names if I made any mistakes and insert your data.
self.ref.child("Students").child("name").queryOrderedByChild("first name").queryEqualToValue("my name").observeSingleEventOfType(.Value) { (snapshot: FIRDataSnapshot) in
print(snapshot.value)
}
Before use queries with child, you need to specify this using ".indexOn":["name"] in Database Rules. See documentation for examples
{
"rules":{
".write": "true",
".read": "true",
"Students": {
"$key": {
".indexOn":["name"]
}
}
}
}
Your read/write rules can go where you need it. .indexOn is just like another rule, alongside with .validate