1

I don't have a storyboard. I'm doing everything programmatically.

The loadData() method takes Firebase data, put it into a Company object, and loads the object into the companies array. In the didFinishLaunchingWithOptions method in the App Delegate, I instantiated the class and called loadData()

When I run breakpoint at the line indicated by the comment and type "po companies" in the console, I get 0 companies. The print statements inside .observe are printed to the console and I can see that the company's properties are non-null, but anything outside .observe, including the for loop and the print statement called after the load data method in the App Delegate are not printed.

class informationStateController {    
func loadData() {
    //Set firebase database reference
    ref = FIRDatabase.database().reference()

    //Retrieve posts and listen for changes
    databaseHandle = ref?.child("companies").observe(.childAdded, with: { (snapshot) in
        //Code that executes when child is added
        let company = Company()
        company.name = snapshot.childSnapshot(forPath: "name").value as! String
        print(company.name)
        company.location = snapshot.childSnapshot(forPath: "location").value as! String
        print(company.location)
        self.companies.append(company)
        print("databaseHandle was called")
    })

    for company in companies {
        print(company)
    }
    //breakpoint inserted here

}
}

Why is my array empty and why are print statements outside .observe NOT printing to the console? The output for the console is set to "All Output". I called import FirebaseDatabase in the class and import Firebase in the App Delegate.

AL.
  • 36,815
  • 10
  • 142
  • 281
14wml
  • 4,048
  • 11
  • 49
  • 97
  • `observe` _observes_ changes to your firebase table, it doesn't automatically fetch new ones. You probably want to GET them first, then establish the observer. – brandonscript Jan 12 '17 at 20:32
  • Could you show me what the GET method is? According to the firebase documentation for reading data for ios (https://firebase.google.com/docs/database/ios/read-and-write), to read data, there is only `.observeSingleEvent` and `.observe` – 14wml Jan 12 '17 at 20:42
  • 1
    Even if you would be doing this right, your array would probably still be empty as the child listener gets fired after your break point gets executed. – Rodrigo Ehlers Jan 12 '17 at 20:51
  • True that @hotrod didn't even notice that. Breatpoint should be inside the listener callback. – brandonscript Jan 12 '17 at 20:56

2 Answers2

2

Data is loaded from the Firebase Database asynchronously. This means that by the time you print the companies, they won't have loaded yet.

You can easily see this by also printing the companies as they're loaded:

//Set firebase database reference
ref = FIRDatabase.database().reference()

//Retrieve posts and listen for changes
databaseHandle = ref?.child("companies").observe(.childAdded, with: { (snapshot) in
    //Code that executes when child is added
    let company = Company()
    company.name = snapshot.childSnapshot(forPath: "name").value as! String
    print(company.name)
    company.location = snapshot.childSnapshot(forPath: "location").value as! String
    print(company.location)
    self.companies.append(company)
    print("databaseHandle was called")
    for company in companies {
        print(company)
    }
})

Now you'll first see one company printed (when childAdded fires for the first time), then two companies (when childAdded fires again), then three companies, etc.

Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
  • If data is loaded asynchronously, does that mean after I call `loadData()` in my question, there isn't be any data loaded into the array? Then how do I get an array with all the data loaded in, so I can pass it to other view controllers? – 14wml Jan 13 '17 at 01:07
  • For the initial data, the client always first fires all `.childAdded` events and only then fires `.value`. So you can listen for a `.value` event to detect when all initial data have been loaded. – Frank van Puffelen Jan 13 '17 at 02:25
  • I tried `.observe(.value)` in `loadData()` and iterated through the `snapshot.children` but the print statement after `loadData()` still showed that the array was empty- am I doing something wrong? – 14wml Jan 13 '17 at 04:29
  • The data is loaded asynchronously. By the time `loadData()` returns, the data hasn't loaded yet. And you cannot return something that hasn't loaded yet. The solution is to move the code that needs the data *into* the callback block of the `.value` event in `loadData()`. So right where the print statements now are – Frank van Puffelen Jan 13 '17 at 04:53
  • I highly recommend reading some more on asynchronous data loading on iOS, because you'll find this to be a very common pattern when accessing modern web/cloud APIs. See for example [this answer](http://stackoverflow.com/q/34211787], [this answer](http://stackoverflow.com/questions/36256976/) or [this answer for Swift[(http://stackoverflow.com/questions/25203556/returning-data-from-async-call-in-swift-function). – Frank van Puffelen Jan 13 '17 at 04:56
  • Thank you so much for your help. I'm not sure if this is possible, but I need an actual copy of the array to exist because when a table view cell is pressed, this leads to a detail table view that displays detailed information about that company. I want to access the array to load the specific company's information to the detail table view. – 14wml Jan 13 '17 at 04:58
  • One of the links I provided shows a way to wait for data. But it's a practice I'd highly recommend against. The modern web is asynchronous and you're best off by accepting (and in time embracing) that fact. – Frank van Puffelen Jan 13 '17 at 05:00
  • Btw first link you provided didn't work, but I will look into those resources thank you! – 14wml Jan 13 '17 at 05:04
0

Per the docs (emphasis mine)

Important: The FIRDataEventTypeValue event is fired every time data is changed at the specified database reference, including changes to children. To limit the size of your snapshots, attach only at the highest level needed for watching changes. For example, attaching a listener to the root of your database is not recommended.

In your case, you're observing changes to the database, but no changes are happening, so you won't bet getting new data. I think the docs make this unnecessarily confusing, if you want to pull records that already exist, you have to query for it:

https://firebase.google.com/docs/database/ios/lists-of-data#sort_data

// Last 100 posts, these are automatically the 100 most recent
// due to sorting by push() keys
let recentPostsQuery = (ref?.child("companies").queryLimited(toFirst: 100))!

Once you have that queried data, you can then deal with the observer and append data as required when new data is pushed.

All of this aside, Frank's answer is the reason you'll never see the print when a company is added even if you set the listener up right — you need to write that inside the completion block of the observer or query.

brandonscript
  • 68,675
  • 32
  • 163
  • 220
  • Is this (https://postimg.org/image/rvrpth9m9/) what you're recommending I do based on your post? How is that different from what I did before? I'm also not sure why I should be querying since base on youtube videos (https://www.youtube.com/watch?v=RMudKhNY0sI&t=732s) for retrieving data from firebase, a lot of them use `.observe (.childAdded)`. – 14wml Jan 13 '17 at 01:08
  • Not sure if that syntax is right — doesn't look quite like it. Point is, if you create an observer, you won't actually see the data until you create an entry in the database. Observe listens for changes, it doesn't query for existing records. If you only want to use observe, you'll need to run the app and then insert records in order to see it working. And -- regardless, you still have the same problem that Frank's answer points out no matter which solution you use. – brandonscript Jan 13 '17 at 01:24