 Hello, everyone. In this video, I'm going to show you how to add some search functionality to your app that you might find useful for your project for searching for contents of the posts. So, here I am in my trusty Firebase example project, which is on GitHub and we used it before to start working with the Firestore database and with Cloud Storage to display some pictures. So, I have updated that code, so if you already have the Firebase example project, you probably want to either pull these latest changes or grab a new copy if you made changes to that project. So, here I am. I have created a new activity and I'm on the main screen here in my emulator and it's called Launch Firestore Search Activity. So, I will do that. Now, this Firestore Search Activity is showing the data that's in the Firestore database. If you recall from the previous lab we had in the Firestore Activity, there's the ability to add people in here. So, if you don't see any data, go into the Firestore example and add a few data points here. Because this is a Firebase project, you will need to go into Firebase, set up a project for this app to connect to, and download the Google services.json file. So, I refer you back to the earlier lab on that. Alright, so let me go into the Firestore Search. So, right now, all this activity does, this boilerplate, is I've got a recycler view here. I can click on these, nothing happens. It's just displaying the data that's in the database. There's not much to this activity. I've got the recycler view set up and using the same stuff that we used in the Firestore lab, I've created a query that's just getting all the data, hooking that query into the adapter for the recycler view and showing it. And that's really all that we're doing. So, let's add a search to this. First thing, let's go into the Layout file and let's make a search box. And I'm going to go through this with you so that you can see how I did it because there might be some gotchas that you run into while you go to add it if you're just going based on the documentation on Google. So, here I've got my text view, which is this little header right here, and then my recycler view, which is all of this stuff. So, let me go ahead and add a text box. So, text boxes are the edit text widget. How wide do I want it to be? Well, let's span the whole screen, so we'll say match parent, and for the height, we will wrap the content. Okay, just to make things a little prettier, let's give it a margin of just say 12 density independent pixels. No big deal there. And we're in a constraint layout. So, if you recall, when you have a constraint layout, every widget needs to have at least one vertical constraint and at least one horizontal constraint. So, what I want this thing to be is I want it to be kind of in the middle here of these two items. So, let me center it horizontally, first of all. Start. So, I'm going to do this in the text. You can do this in the designer view if you want to. I'm just going to do it in text. So, start to the start of the parent. And to the end, end of the parent. And this will make it so that even though this thing is matching the width right now, it's going to just say, hey, center this thing horizontally within the activity. All right, but now I want to put it in between the header and the recycler view. So, I'm going to constrain the top of my edit text to the bottom of the text header. So, this guy. And then I'm going to constrain the bottom to the top of the recycler view, which is this guy. Okay. So, I'm also, I need to move the recycler view a little bit. Otherwise, it'll run over top of the edit text. So, let me give the edit text an ID. And I'll call it the search box. I'll need that elsewhere as well. And then I'm going to constrain the top of the recycler view to the bottom of the search box. All right. And it's giving me a little anger here. Text field does not specify an input type and a label. So, we all need a label right now. But let's try and take care of some of this other stuff. So, let's actually do a few things. So, Android input type equals text autocomplete. Okay. So, this will, as you're typing in, it'll pull up the autocomplete feature on your keyboard if one is available. It doesn't have to be that way. The hint, we should give a hint. Now, what's a hint for the edit text? The hint is the text that you see in the background of the edit text until he starts typing something and then it goes away. I've already created a search hint in the strings for you. And that search hint is search by last name. So, we are going to be writing this so that it searches by last name. All right. I'm going to get back over here. All right. All right. Get back down into my edit text. And let's make it a little fancy, right? Right now, this thing is pretty bland. There's kind of a neat thing that you can do with edit text, and actually with a lot of widgets, which is you can put a drawable, like a picture inside of the widget if you want to. So, the way you can do that is through a feature called Android drawable start. So, Android has a bunch of baked in icons that you can refer to. The one that we will use is called menu search. Yeah. Menu search, right? And that's going to give you the little magnifying glass that you're used to seeing with a search box. Okay. You know what? Let's just run this and make sure that we are going to get our edit text here. Make sure it all looks good. All right. So, I am in the firestore search activity. Okay. So, here's my edit text, right? There's my fancy little magnifying glass icon, and I'm starting to fill it in, right? So, and you can see here, actually, as we start to type, it is trying to do the autocomplete, so that's nice. All right. But our edit text doesn't do anything right now. It's there. It looks nice, and it applies that we should be able to search. So, let's go make the search happen. All right. So, for that, we need to go into the activity. All right. So, what I need to do in order to make search happen is basically I need to be listening for any changes that are made within the search box, and whenever those changes are made, then I want to kind of update the query, update what the recycler view is showing. Okay. So, one thing at a time, though. Let's, first of all, get this text box and get to listening to changes in text. All right. So, I've got to get my search box, and by now, you should all be familiar with this pattern, which is find view by ID. Now, I called this thing, I believe, search box. Yes, there it is. All right. So, this will give me a copy of the widget in Java. And now, what I need to do is I need to add something that's going to listen for changes here. And, of course, Android does have something baked into this, and it's called the text change listener. Okay. So, we want to call search box.add text change listener. All right. This is a method that's going to put attach a listener to the widget, but then we need to define what this thing does. Okay. So, if you look at the tool tip here, it's saying it needs a text watcher class. Okay. So, if I type new text watcher here, I get a recommendation. All right. I'm going to pick that. Okay. And it populates it with a couple of methods. Right. So, before text changes and after text change. Right. So, what are the exact differences among all these things and when are they called? I refer you to the API documentation for this thing. But the one that we are interested in, so what is this guy down here after the text has changed? Because what we want to know after the text has changed is what is the new value in this text box? Okay. So, for that, we can just kind of ignore these. They have to be here. Otherwise, you'll get a compilation error because this thing is an interface and you have to implement all the methods of an interface. But we can leave them blank. Okay. It just means that they're not going to do anything when these events fire. All right. So, what do we want to do, though, in this case? Well, I think that what we should do, let's just log the fact that at the debug level. So, I have a, you know, the Android logging facility will create some data in logcat when we make a call like this. So, I'm calling the log at the debug level. And I've already defined a tag up here. And tag is just a string to help me identify which location in my code is generating it. So, the search, so search box has changed to, and then you'll notice here that after text changed is giving a parameter, editable. Okay. What's an editable? Well, edit text is an instance of editable. So, what I can do is I could say s.toString to actually get the value of the string. Okay. All right. So, let's, again, let's just run this and make sure that our textlet changed listener is firing off. All right. So, I'm going to launch my search activity again. And let me pull up my logcat because that's where this information is going to show up. Okay. So, I've got a bunch of spam in here from Firebase. Let me filter it out. So, if I filter it by my tag, I should only get data that's coming out of, you know, being logged with this tag. All right. So, now, let's type something. A. Aha. Search box has changed to A. C. D. E. F. Okay. So, every time I change it, yeah, it's actually registering it. So, that's good. That's on a, that's a good start. Okay. And as I'm deleting things, it's getting updated. So, this is all looking pretty promising. All right. Now, now that we have changed it, we're, we know that it's getting changed. Let's do something with that information. And the thing that we want to do is we want to update the query, right, that is going to Firestore and giving us this data back. Now, what's a query, right? A query is, it's a general database concept. And it's basically a specification for what you want to search for. So, originally, what we are doing is we are searching the Firestore. And in the Firestore, I have a collection called people. And I'm saying, just give me all the people, but order them by their created time ascending. So, the oldest first, right? That's all I'm doing. Then, I create this query. And in particular to our recycler, we have to make this options thing in this form. This is all the same as from the previous laboratory. And then on my adapter, I set it with these options. Okay. So, what we want to do is actually change the options that we are searching for. Okay. So, what I'm going to do is I'm going to grab these couple of lines here, the query and the creation of the options. So, now I've copied them, I've pasted them down here. And what we would like to do is add a little filter basically here. So, what I'm going to do is, if you remember, my hint was to search by last name. Okay. So, if I want to build a query and I'll show you a link, there's a whole bunch of information on how to create different queries, complex queries. Things like that on the fire store, fire-based documentation, I'll provide the link to that so you can kind of look at it. And so, while I'm searching on text and on a name for your project, you can absolutely build, say, some pickers or some spinners to select, say, a minimum price, a maximum price, and then do a search for an item that falls where its price falls within both of the men in the max. You can do stuff like that. You can do searches for date, all sorts of things, right? So, I'm just showing you a text example. All right, so let's search by last name. So, an example of a very simple query is what we're going to do is we're going to add some information here. Where equal to, okay? So, I'm calling a method here. Where equal to, and then I give it the field. So, in the fire store, if we take a look at it, I'll go over to the application where this is really quickly. So, I'm using 315 sample, go to my database. Oh, so slowly I go to my database. Okay, here's my people collection. And you can see all the people I have in it. And my field name is last. That's what we're looking for here. Okay, so last. Where the field last is equal to what value? Well, the value I want to search for is whatever is in the search box. S.2 string. Okay. They will still be ordered by their creation time from oldest to newest. Okay. So, I've updated my query. Right, that's the first step. This query is a little more sophisticated. I now need to create these options here for the recycler view. These are configurations for this thing. And finally, I have my adapter for the recycler view. Remember, an adapter is always providing content to some sort of list. Now, these recycler adapters that I've created, right, it extends a firestore recycler adapter. The firestore recycler adapter knows about a thing called update options. Okay. So, we have changed the original options for how this thing behaved. Originally, it did this stuff. Now, we've changed the query so we need to update its options. All right. That update is instantaneous. Let's go ahead and rerun it and see what happens. All right. So, we'll launch the firestore activity. And we've got a couple of people here. And I want you to notice, however, there are three entries here with the last name Hopper. Okay. So, I'm going to search for Hopper. Now, the first thing you should notice is when I start to type this, there is no, it's not a smart search. It's not searching for contains the information. It's going to look for an exact match and equal to. And unfortunately with firestore, there's no way to do kind of like a substring operation. It's either equal to less than, greater than, but there's no in or contains. Okay. So, I get my hoppers back and I got all three of them. But if we go into the firestore, you'll notice that I'm actually getting a warning here and it says this query requires an index, which is interesting because it actually returned everybody here. But it's telling me that the query requires an index. So, what is an index? And database land is basically a map to all the data that fulfills a query and it's an optimization scheme. So, firestore is letting me actually do this because I have such a small amount of data in here. But if you had millions and millions of data points in here, it wouldn't work, right? You have to create the index. So, firestore in the logcat is going to, it gives you a link to creating the index. And I just clicked on it. Okay. It's going to pull up some information here. Okay. Create a composite index. Okay. It uses composite indexes for compound queries. And this is going to make your search faster, especially over a large data set. Okay. So, I'm going to tell firestore to create this index. It's actually going to take some time. Okay. So, I'll pause here for a little bit while we wait for this. It could take a couple of minutes. All right. So, the index finally finished. I actually got a little pop up here that told me that. And when I refreshed, it says this index is enabled. And you can see here that the index is enabled. And now, I'm back in my application. Whenever I do the search again, I'm no longer getting this warning. Okay. So, that's good. That's good. That's good. That's good. All right. So, cool. We've got a search, right? But there's a little bit of a bug, a thing that we probably want to fix, which is. So, I'm able to search for these folks. However, if I clear out the search box, I don't get anything. And the reason is that this query is still being applied. They're equal to last name. Well, this is going to be the empty string. Okay. None of the data points in my firestore have a last name that is blank, right? So, the search is failing. So, probably what we want to do here, the easiest way, or an easy way to handle this, is going to be to check and see if that string is empty right here. And if so, let's just do the original query that gets everything. Okay. The way we can do that is, if s.toStrain.isEmpty, right? So, if it is empty, then I want to do kind of the original thing. And else, do my more refined query. Okay. So, the original thing is actually this exact same thing. Except I get rid of this where to. So, I've got some duplicated code here. I don't need to do all of this stuff twice. In fact, all I need to do is change the query itself and move the stuff around. I'm just optimizing a little bit, make my code look a little cleaner, a little nicer. There we go. Now, if the search box is empty, okay, change my query, get rid of any filter, just give me everybody ordered by their created time. However, if the search box has something in it, else it's not empty, then query on that last name, build up the options, and finally update the adapter. So, let's go ahead and run this. So, let's search for hoppers again. And there they are, looking good. And if we start zeroing this out, all right. And we've got everybody back again. So, here's an example of how to integrate search. There's no reason that this has to be attached to a text box like this. Like I said, you could have a search page where you've got a bunch of drop downs of different values. You can even pull the set of values that are possible out of the fire store first if you wanted to. So, you have lots of options about how to integrate search. It's just unfortunate you can't search for something like with the fire store. You can't search for something like Turr and get back Alan Turring, and plus all this stuff is case sensitive, so it has to be an exact match. You can probably, as clever computer scientists, come up with some ways to work with that. But anyway, here you go. That's how to search the fire store. There's lots of different types of queries you can create. Good luck and have fun with it.