In the last lesson, we saw how we could use the Cloud Firestore to start saving our user's messages to 1
our Cloud-based database. 2
In this lesson, I want to show you how you can read from the database, so that we can populate our app 3
and all the message bubbles. If you take a look at the documentation under the Getting started section 4
of Cloud Firestore, 5
You can see that it has a section on reading data. We can, again, use our database reference. 6
Tap into a collection with a particular name that exists in your database. 7
So in our case, it's messages. And then we can call getDocuments to get a hold of the documents in that 8
particular collection. 9
We've currently only got one document which is this "Test Message" right here. And I want to be able to 10
show that Text Message when we load up our FlashChat app. 11
So I'm going to log out and go back to the home screen. And I want to be able to modify the code, so that 12
inside viewDidLoad, when that ChatViewController is being loaded up, 13
I want to call a method code loadMessages. And this method loadMessages should be able to pull up 14
all of the current data that's inside our database. And then we're going to use it to populate our table 15
view. 16
So the first thing that should happen when we're loading messages from our Cloud Firestore is we should 17
clear these dummy messages. 18
So let's go ahead and set this messages array to an empty array, so that we clear it out. And the next 19
thing we're going to do is we're going to tap into our database. 20
So we're gonna say db.collection 21
and remember the collection path is going to be using that constant K.FStore.collection name. 22
And then we're going to call that method that we saw over here which is getDocuments. So let's call 23
getDocuments and it has a completion block. With this completion block selected, 24
let's hit enter. And you can see that we get two things back 25
once this process of getting documents completes. One is a query snapshot, 26
so we'll just call that querySnapshot, and then the other is an error 27
if there was one. 28
So this is an optional error. 29
It will only be equal to something if that actually were something bad that happened. 30
So in our code here, we're going to check to see if let e = error, 31
so if error is not nil, then we're going to print. 32
"There was an issue retrieving data from a Firestore." And then we're going to add on that "e" at the end 33
to print it out as well. 34
But if there were no errors, then we want to be able to tap into this query snapshot objects and get 35
hold of the data that's contained inside.This querySnapshot is actually an optional. It's an optional 36
query snapshot. 37
So if we wanted a little bit more detail on what that thing actually is, then instead of going into the 38
Guides section, we go to the Reference section on the Firebase docs. 39
And if we're going to iOS Swift and locate the Firestore section, and then if you go into Classes and 40
scroll down, you should be have to find Query Snapshot right here. 41
So this is an NSObject and it is created from a class called QuerySnapshot 42
and this has a bunch of properties like the query on which you called get documents, or the metadata, 43
or whether if it's empty, or the count of documents inside. But the one that we want is actually this 44
array called documents because this contains the actual documents that saved in that collection. 45
Coming back over here, 46
let's go ahead and tap into our 47
querySnapshot.documents. And this, as you can see, is an array of QueryDocumentSnapshot objects. 48
So what if we wanted to see what a QueryDocumentsSnapshot looks like and what properties does it have, 49
well then we can try and find it inside theFirestore classes and it's right here. 50
So it tells us that the document is guaranteed to exist and its data can be extracted with the data 51
property or simply by subscripting the array. 52
That means we can either say square brackets zero to get the first item from the documents array, 53
and then we can tap into the data property which, notice, it's now a dictionary with a key-value pair. 54
So as long as you know the key, say, for example, in our case, we had a K.FStore.senderField, 55
then we should be able to get the value of that piece of data. 56
But our code is now looking quite terse and it's kind of hard to understand what's going on. 57
So let's make this a little bit better. 58
Now, we know that the query snapshot is an optional. 59
So let's first use an "if let" to bind these documents if they exist to a new constant. 60
Let's call this snapshotDocuments and we're going to set it to equal 61
querySnapshot?.documents. 62
So, now we have this snapshotDocuments which is an array of QueryDocumentSnapshots, 63
and it's now fully unwrapped. 64
So the next step is to loop through this array of documents snapshots and to be able to tap into the 65
data of each of them. 66
We can, for example, create a for loop and say, for doc in snapshotDocuments. 67
So for each of these documents in there, we're going to go ahead and print the doc.data, and this 68
data is now a key-value pair. 69
So let's go ahead and run our code and see what we get printed. 70
So as soon as it loads up, we get our data printed from our documents 71
and it has a body of Test message, a sender: 1@2.com. 72
And if we go ahead and send another message, say, 73
"Testing Testing." 74
Hit send. aAnd it's successfully saved our data. 75
So let's log out and log back in again to trigger that viewDidLoad. 76
You can see this time. 77
We're now getting both messages printed out because of our for loop. 78
So we're looping through all of the documents inside this array, 79
and for each of them, we're tapping into the data object and we're printing it out down here. 80
So instead of printing that data object, what if we could save it as a piece of data? 81
So let data = doc.data, and then we can divide up that data into the sender value and the body value. 82
So we can say let sender = data, and we're going to access the value using the square brackets 83
because it's a dictionary, and we're gonna tap into K.FStore.senderfield. 84
Now, remember that when you're working with dictionaries and when you're trying to retrieve things from 85
dictionaries, because we're using a string to retrieve that piece of data and they can obviously be typos 86
in the string, then the item that we get out or the value is going to be, again, an optional. And even worse, 87
it's not just an optional, 88
but we don't even know what the data type is, because this data has a data type of "Any." 89
And the reason is because Firebase allows us to store lots of different data types into the database, 90
say, we could have had a field called time, right? Like when the message was sent. 91
Well, that obviously wouldn't be a string. 92
So the flexibility on Firebase's part makes us have to do a little bit more work when we're trying 93
to retrieve the items. 94
So we know that the sender data is gonna be a string. Instead of having a optional "Any," 95
I'm going to use the conditional downcast to cast it into a string. 96
Now, we should have an optional string instead. 97
So as long as we're able to retrieve the senderField from our data, then we should end up with some 98
sort of string. 99
So what we can do is we can use an "if let" to bind this sender to this value right here. 100
And in addition, I'm also going to tag on a constant to save on messageBody, 101
and this is going to be set to equal data, and a set of square brackets, and then K.FStore.bodyField. 102
And then here, 103
this is also going to be a question mark, Any, 104
so we also have to conditionally downcast it to a string because we know that the message is also a 105
string data type. 106
So if both of these pieces succeed, then we should end up with a sender that is now a non-optional string 107
and a messageBody which is also a non-optional string. 108
So we're now ready to create a message object using the Message class because it has two properties 109
that need filling: the sender and body. 110
And we've now got both of them back from Firebase. And once we've type cast our sender and messageBody 111
to strings, then we're going to create a newMessage which is going to be created from the Message class. 112
So this one right here with a capital M. 113
And then we're gonna initialize it with the sender being equal to this sender. 114
And if you want to make it just a little bit more clear which one is which, then it might be easier if 115
you have it as messageSender instead. 116
So we can see that we're creating a newMessage. And for the sender property, we're giving it this value 117
which comes from right here. And then the body will set as the messageBody 118
and now we've got a newMessage ready to add to our array of messages which is currently empty. 119
So let's go ahead and do that. 120
Let's tap into the messages array and append this new message to it, 121
so we should now have a message array with two items. And because we're inside a closure right now, 122
we have to, of course, add the keyword "self" in front of messages. So, now we no longer need these dummy messages 123
because we're going to be getting the live messages that are coming back from Firebase. 124
Now, notice how we're calling load messages inside viewDidLoad, 125
but the time point where we add our message to our messages array actually happens inside our closure 126
right here. 127
So our closure could be triggered at any point 128
and it really depends on your internet connection as to how long it would take for you to get back these 129
documents. Very often, what you'll find is that even though this is being triggered right at the point 130
when view is being loaded up, the message is not added in time for it to be shown inside the table view 131
when it starts tapping into the messages array and populating each of the cells. 132
So when we run our app, 133
you can see that despite everything we've done, we're still not seeing any messages show up here, and 134
it's actually completely empty. 135
So what can we do? 136
How can we trigger our table view to call these methods again and reload the table view? 137
Well, there's a method for that. 138
So once we're done appending our message to messages, we can go ahead and call tableView.reloadData, 139
and what this will do is it will be able to tap into the tableView and trigger those data source 140
methods again. 141
But because we're inside a closure, we have to add the word "self" in front. And remember another good practice 142
thing to do is whenever you're trying to manipulate the user interface, like in this case, when we're 143
trying to update the tableView and you're inside a closure, a good idea is to get a hold of the main queue. 144
So we can say DispatcaQueue.main.async, and then inside this block is where we trigger the update 145
to our tableView. 146
And this just ensures that because this process of fetching documents happens in a closure, that means 147
it's happening in the background. 148
And when we're ready to update our tableView with the new messages, we have to fetch the main thread 149
which is the process that's happening in the foreground. And it's on that thread that we actually update 150
our data. 151
So let's run our app and let's see if it works. 152
So once we've logged in and we've gotten hold of the messages, it took maybe a fraction of a second for 153
it to load up, but we now see our "Text message" and "Testing Testing" show up on screen. 154
And the one last thing that we should fix while we're here is notice how when I type a new message and 155
I click send, 156
nothing actually happens in our table, 157
even though we've successfully saved our data. 158
So what do we need? 159
Well, we need another call to get documents or, alternatively, we can use another functionality that Firebase has 160
which is to listen for changes in our database collection. And that is what we're going to tackle 161
in the next lesson. 162
I'll see you there.