firebase / FirebaseUI-iOS

iOS UI bindings for Firebase.
Apache License 2.0
1.51k stars 472 forks source link

FUIIndexTableViewDataSource giving unexpected results #244

Closed codemodouk closed 7 years ago

codemodouk commented 7 years ago

Step 2: Describe your environment

Step 3: Describe the problem:

The snapshots returned from a FUIIndexTableViewDataSource object are in an unexpected format.

I have a 'user-listings' index ref with data structured as follows:

"user-listings" : {
  "K4jtmetqwFP30VCprAby2JQxm9k1" : {
    "-Kafe2ooXzkQtUcNeHgl" : -1.484643648826172E12,
    "-KblExHpQ7MbbfOvd7K8" : -1.48581120946895E12,
    "-KblGjxD-UUMJxA8-DZj" : -1485811679118
  }
}

I have a 'listings' data ref with data structured as follows

"listings" : {
 "-Kafe2ooXzkQtUcNeHgl" : {
   "make" : "VAUXHALL",
   "model" : "Vectra",
   "numberOfDoors" : 5,
   "odometer" : 100618,
   "yearOfManufacture" : 2001
  }
}

When I print to the console the snapshots returned from using the tableView.bind(toIndexedQuery: dataRef) method I see the following:

Snap (make) VAUXHALL
Snap (model) Vectra
Snap (numberOfDoors) 5
Snap (odometer) 100618
Snap (-Kafe2ooXzkQtUcNeHgl) {
    make = VAUXHALL;
    model = Vectra;
    numberOfDoors = 5;
    odometer = 100618;
}

It appears that I get a snapshot for each child of the data corresponding to the index I've queried followed by a snapshot of the data I was expecting.

Relevant Code:

class MasterTableViewController: UITableViewController {

var dataSource: FUIIndexTableViewDataSource!

override func viewDidLoad() {
    super.viewDidLoad()

    let indexQuery = FIRDatabase.database().reference()
        .child("user-listings")
        .child(AppState.shared.uid)

    let dataRef = FIRDatabase.database().reference()
        .child("listings")

    dataSource = tableView.bind(toIndexedQuery: indexQuery, data: dataRef, delegate: self, populateCell: { (tableView, indexPath, snapshot) -> UITableViewCell in
        let cell = tableView.dequeueReusableCell(withIdentifier: "reuseIdentifier", for: indexPath)

        if let snapshot = snapshot {
            print(snapshot)
        }
        return cell
    })
    tableView.dataSource = dataSource
}
}

I had posted the same issue to stackoverflow to see if this was an error in the way I was using the methods, but, I haven't had any responses to the issue. I would really appreciate some pointers if anyone has any.

morganchen12 commented 7 years ago

Is this method being called on the same indexPath, or various different index paths? Is the count of your data source constant at 1, or is it 5?

I think the bug is here.

codemodouk commented 7 years ago

If I understand you correctly the method is being called on different indexPaths. In my test case, there is more than 1 listing owned by the user, so the code in the closure is being called for each row in the index.

Is there a method to get the dataSource count for an FUIIndexTableViewDataSource?

morganchen12 commented 7 years ago

tableView:numberOfRowsInSection: should give the number of items in the data source, since there's only one section.

codemodouk commented 7 years ago

Thanks. In which case I'm getting a row for each snapshot returned, so in my example I get 5.

morganchen12 commented 7 years ago

Hm, that's not what I expected. Can you upload a runnable sample that repros this?

codemodouk commented 7 years ago

Sorry, I think I've misled you, and confused myself...

When I limit the index query to the last row and print out the indexPath.row, the tableView.numberOfRows(section) and the corresponding snapshot in the closure I get:

indexPath: 0 ~ rowCount: 1 ~ snapshot: nil indexPath: 0 ~ rowCount: 1 ~ snapshot: Optional(Snap (make) VOLKSWAGEN) indexPath: 0 ~ rowCount: 1 ~ snapshot: Optional(Snap (model) TIGUAN SE TDI 4MOTION 140) indexPath: 0 ~ rowCount: 1 ~ snapshot: Optional(Snap (numberOfDoors) 5) indexPath: 0 ~ rowCount: 1 ~ snapshot: Optional(Snap (odometer) 80084)

Is that more what you were expecting?

morganchen12 commented 7 years ago

The bug is on our end, and I've pushed a fix.

The fix may not make it out for some time, since we may want to change FUIIndexArray some more (like removing those side effects from the initializer, again). Until then, you can work around the issue by returning a blank table view cell in the data source until the full snapshot is returned, which is guaranteed eventually.

So your code might look like this

dataSource = tableView.bind(toIndexedQuery: indexQuery, data: dataRef, delegate: self) { (tableView, indexPath, snapshot) -> UITableViewCell in
    let cell = tableView.dequeueReusableCell(withIdentifier: "Identifier", for: indexPath)
    if let model = snapshot.flatMap(Datamodel.init(snapshot:)) {
        // This code will only be reached if snapshot is nonnull and fully loaded,
        // i.e. all fields are present.
        cell.populate(with: model)
    } else {
        // Show a blank or loading state in the table view cell.
    }
    return cell
}

struct DataModel {

    init?(snapshot: FIRDataSnapshot) {
        // Only initialize if all the fields required by this data type are present,
        // otherwise return nil.
    }

}

Thanks for reporting!

codemodouk commented 7 years ago

Thanks Morgan

Sent from my iPhone

On 15 Feb 2017, at 19:19, Morgan Chen notifications@github.com wrote:

The bug is on our end, and I've pushed a fix.

The fix may not make it out for some time, since we may want to change FUIIndexArray some more (like removing those side effects from the initializer, again). Until then, you can work around the issue by returning a blank table view cell in the data source until the full snapshot is returned, which is guaranteed eventually.

So your code might look like this

dataSource = tableView.bind(toIndexedQuery: indexQuery, data: dataRef, delegate: self) { (tableView, indexPath, snapshot) -> UITableViewCell in let cell = tableView.dequeueReusableCell(withIdentifier: "Identifier", for: indexPath) if let model = snapshot.flatMap(Datamodel.init(snapshot:)) { // This code will only be reached if snapshot is nonnull and fully loaded, // i.e. all fields are present. cell.populate(with: model) } else { // Show a blank or loading state in the table view cell. } return cell }

struct DataModel {

init?(snapshot: FIRDataSnapshot) {
    // Only initialize if all the fields required by this data type are present,
    // otherwise return nil.
}

} — You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub, or mute the thread.

morganchen12 commented 7 years ago

This fix has been released in v3.1.0.