Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Having Trouble Pulling Data From Firebase RT Database

Super new to coding so apologies if something is super obvious here.

I'm working on an app that I can use to keep track of my weight lifting split. I write the data like this:

public func writeNewExercise(splitName: String, day: Int, exerciseNum: Int, exerciseName: String, sets: String, repsSecs: String, isTimed: Bool, completion: @escaping (Bool) -> Void) {
        
        let user = AuthManager.shared.user
        var exerciseRef: DatabaseReference!
        exerciseRef = Database.database().reference(withPath: "\(user.uid)/splits/\(splitName)/day \(day)/exercise \(exerciseNum)")
        
        var dataDictionary: [String: Any] = [:]
        dataDictionary["Exercise Name"] = exerciseName
        dataDictionary["Sets"] = sets
        dataDictionary["Reps or Secs"] = repsSecs
        dataDictionary["Is Timed"] = isTimed
        
        exerciseRef.setValue(dataDictionary) { error, _ in
            if error == nil {
                completion(true)
                return
            } else {
                completion(false)
                return
            }
            
        }
        
    }

This gives me a JSON dictionary in Firebase that looks like this:

{
  "8aIzPgurRLPPEYDpXWv54r5JjvH3" : {
    "splits" : {
      "Test Split" : {
        "day 1" : {
          "exercise 0" : {
            "Exercise Name" : "Curls",
            "Is Timed" : false,
            "Reps or Secs" : "12",
            "Sets" : "4"
          }
        }
      }
    }
  },

What I want to do now is to pull this data so I can insert each exercise into a tableView cell. Don't want to do anything fancy with it -- just be able to view it so I can follow my split. I'm doing this more for practice than practicality. I've tried pulling the data about 15 different ways, and no matter what I do it just won't work. I'm totally stumped. Here is the code I have right now:

    public func downloadPost(splitName: String, day: Int, completion: @escaping (Bool) -> Void){
        
        let user = AuthManager.shared.user
        var exerciseRef: DatabaseReference!
        exerciseRef = Database.database().reference()
        
        var exerciseArray = [Exercise]()
        
        exerciseRef.child("Users").child(user.uid).child("splits").child(splitName).child("day \(day)").observe(.value) { snapshot in
            
            if snapshot.exists(){
                for x in 0...100{
                
                let nameValue = snapshot.childSnapshot(forPath: "exercise \(x)/Exercise Name").value
                let setsValue = snapshot.childSnapshot(forPath: "exercise \(x)/Sets").value
                let repsOrSecsValue = snapshot.childSnapshot(forPath: "exercise \(x)//Sets/Reps or Secs").value
                let isTimedValue = snapshot.childSnapshot(forPath: "exercise \(x)/Sets/Is Timed").value
                
                let exercise = Exercise(name: "\(nameValue!)",
                                        sets: "\(setsValue!)",
                                        repsOrSecs: "\(repsOrSecsValue!)",
                                        isTimed: isTimedValue as? Bool ?? false)
                    print(exercise.name)
                    print(exercise.sets)
                    print(exercise.repsOrSecs)
                    print(exercise.isTimed)
                exerciseArray.append(exercise)
                completion(true)
                return
            }
        } else {
            print("no snapshot exists")
        }
        
        print(exerciseArray)
        
    }
    }

Exercise is a custom class I've created that has a name, amount of sets, amount of reps, and a Bool "isTimed". This code prints:

no snapshot exists, []

Trying other things, I've got it to print something like:

null, 0, 0, false

Some other stuff I've tried has been:

  • using slash navigation instead of chaining .childs in the .observe.value
  • using .getData instead of .observe
  • throwing DispatchQueue.main.async all over the place
  • making the exerciseRef be the whole database, then calling to the specific point when assigning the snapshot.value
  • Much else

I've probably put something like 15 hours into just this at this point, and I really cannot figure it out. Any help would be massively appreciated. I'll watch this post closely and post any info that I may have left out if it's needed.

Thanks!

UPDATE

Got everything working by using the code provided by Medo below. For others trying to do something like this, after pulling the array as Medo demonstrated, just set all the labels in your tableViewCell to ExportedArray[indexPath.row].theClassPropertyYouWant

like image 909
Nick Mcmullan Avatar asked Nov 23 '25 20:11

Nick Mcmullan


1 Answers

Here is my solution:

public func downloadPost(splitName: String, day: Int, completion: @escaping (([Exercise]) -> ())){
    
    let user = AuthManager.shared.user
    var exerciseRef: DatabaseReference!
    exerciseRef = Database.database().reference()
    
    var exerciseArray = [Exercise]()
    
    exerciseRef.child(user.uid).child("splits").child(splitName).child("day \(day)").observe(.value, with: { snapshot in
        
        guard let exercises = snapshot.children.allObjects as? [DataSnapshot] else {
            print("Error: No snapshot")
            return
        }
        
        
        
        for exercise in exercises {
            let exerciseData = exercise.value as? [String:Any]
            
            let exerciseName = exerciseData["Exercise Name"] as? String
            let isTimed = exerciseData["Is Timed"] as? Bool
            let repsOrSecs = exerciseData["Reps or Secs"] as? String
            let sets = exerciseData["Sets"] as? String
            
            let exerciseIndex = Exercise(name: "\(exerciseName)",
                                    sets: "\(sets)",
                                    repsOrSecs: "\(repsOrSecs)",
                                    isTimed: isTimed)
            
            exerciseArray.append(exerciseIndex)
            
        }
      completion(exerciseArray)  
    }
}

You can call the function downloadPost and extract the array from it like this:

downloadPost(splitName: "", day: 0, completion: {
    aNewArray in
    
    // aNewArray is your extracted array [Exercise]
    print("\(aNewArray)")
    
})

Few things to be aware of:

  1. If you want to ensure that your storing your exercises in order (and extract the data in order) then instead of having exercises 0, 1, 2... (in your database), name it by an id called "childByAutoId". Firebase will auto order them for you as you add/push or extract that data. Replace your writeNewExercise function with:

    let user = AuthManager.shared.user
    var exerciseRef: DatabaseReference!
    let key = Database.database().reference().childByAutoId().key ?? ""
    exerciseRef = Database.database().reference(withPath: "\(user.uid)/splits/\(splitName)/day \(day)/\(key)")
    
    var dataDictionary: [String: Any] = [:]
    dataDictionary["Exercise Name"] = exerciseName
    dataDictionary["Sets"] = sets
    dataDictionary["Reps or Secs"] = repsSecs
    dataDictionary["Is Timed"] = isTimed
    
    exerciseRef.setValue(dataDictionary) { error, _ in
        if error == nil {
            completion(true)
            return
        } else {
            completion(false)
            return
        }
    
    }
    
  2. Firebase Realtime Database is a breadth first search and download. So you should probably flatten out your database structure as much as possible. This means observing on exerciseRef.child("Users").child(user.uid).child("splits").child(splitName).child("day \(day)") would still download all the exercise days.

like image 55
Medo Almouhtadi Avatar answered Nov 25 '25 09:11

Medo Almouhtadi