0

I have only found answers regarding the real time database rather than firestore, I want to be able to have a user search for a name and get back all the matching documents. I am using a textfield that calls a function onChange of the textfield text (also limits to one call every 2 seconds to decrease amount of calls).

struct StoryModel: Identifiable, Codable, Hashable {
    var id: String
    var isLive: Bool?
    var name: String?
    var description: String?
    
    private enum CodingKeys: String, CodingKey {
        case id
        case isLive
        case name
        case description
    }
}

This is how every document in the same collection is modeled. I want to only get the documents where field "name" matches the textfield search text. I have tried this so far:

    @Published var storiesSearchText: String = ""
    @Published var searchedStories: [StoryModel] = []
    private lazy var storiesDataCollection = Firestore.firestore().collection("Stories")
    
    public func getSearchedStories() {
        print(storiesSearchText)
        self.storiesDataCollection
           .whereField("isLive", isEqualTo: true)
           .whereField("name", in: [self.storiesSearchText])
           .getDocuments { (snapshot, error) in
            if let documents = snapshot?.documents {
                for document in documents {
                    print(document)
                    if let story = try? document.data(as: StoryModel.self) {
                        self.searchedStories.append(story)
                    }
                }
            } else if let error = error {
                print("Error getting search stories: \(error)")
            }
        }
    }

The only way I can get the code to work is to use whereField isEqualTo but this would only work when the user types the name exactly and then searches, which does not give live results as the user searches. How can I achieve this live feedback of search results from my firestore collection?

Edit: None of these questions have live feedback, they are not answering my question. And most of them are not even in Swift. Please leave the link as a comment first before closing this post. I have yet to find an answer for the specific requirements of this question.

How to search in Firestore database?

Firestore - implementing search fields

How to search text in Flutter using Firestore

Google Firestore: Query on substring of a property value (text search)

Trevor
  • 580
  • 5
  • 16
  • There is an answer but.... trying to do live queries of data fetched from a remote server over the internet *as the user types* is often a painful process for the user. Your mileage may very but if the internet gets laggy at any particular time, it will be a very poor user experience. If you can cache or store the data locally - it will be a much better and more fluid experience. Just my .02 – Jay May 03 '22 at 17:36

1 Answers1

1

After some more creative thinking, I decided to create keywords for each document. It is not super elegant but it works. Also each "name" field is limited to X characters so I do not need to worry about the keywords field exceeding more than 20-30 individual keyword strings.

I added

var keywords: [String]?

to the StoryModel and the fields looks like this once filled in on doc creation: keywords

And I am getting the live results with:

private lazy var storiesDataCollection = Firestore.firestore().collection("Stories")

self.storiesDataCollection
   .whereField("keywords", arrayContains: self.storiesSearchText)
   .limit(to: 8)
   .getDocuments {}

I am not including the function that creates the keywords because I have simply not done it yet, I will edit and add it once I complete it, but for testing I just manually created the keywords in firestore. Lastly, this solution does not ignore case, the search text has to match perfectly.

EDIT: Here is the function that creates the keywords, I also added the ability to make a case matched and lowercased for better searching.

    func createKeywords(name: String) {
        var keywords: [String] = []
        var lowercaseKeywords: [String] = []
        var alreadyKeyed: String = ""
        for letter in name {
            if keywords.isEmpty {
                keywords.append("\(letter)")
                let lower = letter.lowercased()
                lowercaseKeywords.append("\(lower)")
            } else {
                let key = alreadyKeyed + "\(letter)"
                keywords.append(key)
                let lower = key.lowercased()
                lowercaseKeywords.append("\(lower)")
            }
            alreadyKeyed.append("\(letter)")
        }
        let allKeywords = keywords + lowercaseKeywords
        self.storiesDataCollection.document("doc id goes here").updateData([
            "keywords" : allKeywords
        ]) { error in
            if let error = error {
                print("Error: \(error)")
            } else {
                print("SUCCESS")
            }
        }
    }

If you were to feed it a string of "Trevor's NYE Party" here is what it would give back:

["T", "Tr", "Tre", "Trev", "Trevo", "Trevor", "Trevor\'", "Trevor\'s", "Trevor\'s ", "Trevor\'s N", "Trevor\'s NY", "Trevor\'s NYE", "Trevor\'s NYE ", "Trevor\'s NYE P", "Trevor\'s NYE Pa", "Trevor\'s NYE Par", "Trevor\'s NYE Part", "Trevor\'s NYE Party", "t", "tr", "tre", "trev", "trevo", "trevor", "trevor\'", "trevor\'s", "trevor\'s ", "trevor\'s n", "trevor\'s ny", "trevor\'s nye", "trevor\'s nye ", "trevor\'s nye p", "trevor\'s nye pa", "trevor\'s nye par", "trevor\'s nye part", "trevor\'s nye party"]

note the backslashes are not actually uploaded to firestore.

Trevor
  • 580
  • 5
  • 16
  • "*arrayContains only returns up to 10 docs per query*" - this is not a true statement. Maybe what you're trying to say is that "in" queries only allow up to 10 elements in the array as described in the [documentation](https://firebase.google.com/docs/firestore/query-data/queries#in_not-in_and_array-contains-any). The size of the query results is not fundamentally limited except by how much memory you have to store the documents. – Doug Stevenson Apr 30 '22 at 22:52
  • @DougStevenson will make a correction. I always mix up the two since I never have a situation where I need more than 10 per query – Trevor Apr 30 '22 at 23:13
  • While you can do this technique, you may want to take a look at my answers to [this question](https://stackoverflow.com/questions/45701123/dealing-with-memory-usage-due-to-high-element-count-in-firebase-query/45738986#45738986) and [this question](https://stackoverflow.com/questions/61825291/search-for-users-that-havent-been-fetch-yet/61836939#61836939) as it could provide another avenue with far less code and storage. While those reference the Realtime Database, the concepts are similar. – Jay May 03 '22 at 17:28
  • @Jay im not sure how the queries 'start atValue' and 'end atValue' translate to firestore. – Trevor May 03 '22 at 19:27
  • `users.whereField("name", isGreaterThan: "B").whereField("name", isLessThan: "J")` will get all users whose name starts at *B* and is less than *J* e.g. *Bob, Bruce...* through *Ivan* would be included buy Jay would not :-) – Jay May 03 '22 at 19:37
  • @Jay interesting, Ill have to play around with this concept today – Trevor May 03 '22 at 20:08