Get Data from Document after Textfield Input in Firebase

Hi guys,

retrieve Data from a single Document works fine, but now I want to have a Textfield where the User can search for a single Document inside my Firestore Database.

FireStoreManager.swift:

import Firebase
import SwiftUI

class FirestoreManager: ObservableObject {
@Published var name: String = “”
@Published var elevation: String = “”
@Published var runway: String = “”
@Published var searchairport: String = “”

init() {
    fetchRestaurant()
}

func fetchRestaurant() {
    let db = Firestore.firestore()

    let docRef = db.collection("Airports").document(searchairport) 
                  // variable for searching the airport ???
    docRef.getDocument { (document, error) in
        guard error == nil else {
            print("error", error ?? "")
            return
        }

        if let document = document, document.exists {
            let data = document.data()
            if let data = data {
                print("data", data)
                self.name = data["name"] as? String ?? ""
                self.elevation = data["elevation"] as? String ?? ""
                self.runway = data["runway"] as? String ?? ""
            }
        }

    }
}  

}

ContentView looks like this:

struct ContentView: View {
@EnvironmentObject var firestoreManager: FirestoreManager

var body: some View {
   
    
    TextField("Enter Airportname", text: $searchairport )
    
    Text("ICAO: \(firestoreManager.name)")
    Text("Elevation: \(firestoreManager.elevation)")
    Text("Runway: \(firestoreManager.runway)")

        .padding()
}

}

struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
.environmentObject(FirestoreManager())
}
}

My Firestore looks like this

In my opinion, a better way to do this is to allow Firebase to create a record using the Auto documentID process rather than going to the trouble of specifying a specific document ID since you run the risk of creating a duplicate documentID accidentally. They are supposed to be unique.

See this firebase screenshot example:

Ignore the fact that in the above example airports is not capitalised

In your ObservableObject your createAirport function could be like this:

    func createAirport(name: String, elevation: String, runway: String) {
        let db = Firestore.firestore()

        db.collection("Airports").addDocument(data: [
            "name": name,
            "elevation": elevation,
            "runway": runway
        ])
    }

Then your fetchAirport function (I named the function fetchAirport because it’s not a restaurant) would be something like this:

    func fetchAirport() {
        let db = Firestore.firestore()

        let query = db.collection("Airports")
            .whereField("name", isEqualTo: searchAirport)
        
        query.getDocuments { snapshot, error in
            guard error == nil, snapshot != nil else {
                print("Error: \(error?.localizedDescription ?? "")")
                return
            }

            // Assuming that the Airport ID is always going to be unique
            //  there will only be 1 record
            for doc in snapshot!.documents {
                self.name = doc["name"] as? String ?? ""
                self.elevation = doc["elevation"] as? String ?? ""
                self.runway = doc["runway"] as? String ?? ""
            }
        }
    }

This is a query where the documents are searched to find a match where the name field is equal to the search criteria.

I hope that helps.

2 Likes

Hi Chris,
thank you for the better approach with the query and the equal to.

Do I need to connect my variable searchAirport in the Textfield with a button to start the search query in the ContentView() ?

struct ContentView: View {
@EnvironmentObject var firestoreManager: FirestoreManager

var body: some View {
   
    
 TextField("Enter Airportname", text: $firestoreManager.searchAirport)
  //Searchfield with the variable which is equal to name

 Text("ICAO: \(firestoreManager.name)")
 Text("Elevation: \(firestoreManager.elevation)")
 Text("Runway: \(firestoreManager.runway)")

        .padding()
}

}

struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
.environmentObject(FirestoreManager())
}
}

I just got it to work with a button
Thank you so much for your help Chris. I am happy that my project comes finally alive :slight_smile:

Solution

struct ContentView: View {
@EnvironmentObject var firestoreManager: FirestoreManager
@State private var searchAirport: String = “”

var body: some View {
   
    
    TextField("Enter Airportname", text: $firestoreManager.searchAirport)
    
    Button {
        firestoreManager.fetchAirport()
        
    } label: {
        Text("Save Airport")
           
    }

The other way you can do this is use the .onSubmit modifier on the TextField which responds to when you press the return key.

ie:

TextField("Enter Airportname", text: $firestoreManager.searchAirport)
    .textInputAutocapitalization(.characters)
    .textFieldStyle(.roundedBorder)
    .onSubmit(of: .text, {
        // This responds to pressing return on the TextField
        firestoreManager.fetchAirport()
    })

The other two modifiers:
.textInputAutocapitalization(.characters) converts all alphabetic entries to uppercase (very handy since your airport identifiers are uppercase) and
.textFieldStyle(.roundedBorder) just makes the TextField look a bit nicer on the screen as per this example code I set up to test the code I provided you previously.

Screen Shot 2023-03-09 at 07.49.07

2 Likes

Thank you for the tips and hints, I implemented them for better comfort. :slight_smile: