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