2

Im trying to retrieve data set in Firebase from my firebase database and present it on the tableview.To present it forward and be able to view the data that I put into my firebase server

I have tried using multiple questions here on Stackoverflow but nothing seems to be working and the log error seems to be consistently the same Log Error


Firebase Tree

Products {
    dfkjsv4qc22aWE:{
       name:"Apples",
       weight:"1 Bag",
       price:"$5.99",
       imageUrl:"https://firebasestorage.googleapis.com/v0/b/randomImageapp.BlahBlask2233"
    }

    se04Fws444:{
       name:"Flower",
       weight:"Bouquet",
       price:"$10.99",
       imageUrl:"https://firebasestorage.googleapis.com/v0/b/randomImageapp.BlahBlask2233"
    }
}

import UIKit
import Foundation
import Firebase
import FirebaseStorage
import FirebaseDatabase

class ProductListController: UIViewController {

    @IBOutlet weak var productListTableView: UITableView!

    var feeds = [ProductList]()
    var dbRef :DatabaseReference!
    var storage : Storage!

    var dbRef: DatabaseReference!
    var storage: StorageReference!

    fileprivate var products: [ProductList] = []
    {
        didSet
        {
            productListTableView.reloadData()
        }
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        productListTableView.dataSource = self
        productListTableView.delegate = self

        dbRef = Database.database().reference()
        storage = Storage.storage().reference()

        readProducts()
        printProducts()
    }

    var productsArrayDataSource = [ProductList]()

    func readProducts() {
        let productsRef = self.dbRef.child("products") //self.ref points to my firebase
         productsRef.observe(.childAdded, with: { snapshot in
            let aProduct = ProductList(withSnapshot: snapshot)
            self.feeds.append(aProduct)
        })
    }

    func printProducts() {
        for product in self.feeds {
            print(product.id!, product.name!)
    }
}

extension ProductListController: UITableViewDelegate, UITableViewDataSource {

    func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return products.count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "ProductListCell") as!
        ProductListCell

        let details = products[indexPath.row]
        cell.updateUI(with: details)

        return cell
    }
}

import Foundation
import UIKit
import Firebase
import FirebaseDatabase

class ProductList {
    var name: String
    var price: String
    var weight: String
    var imageUrl: String

    init(withSnapshot: DataSnapshot) {
    self.name = withSnapshot.childSnapshot(forPath: "name").value as? String ?? "No Name"
    self.price = withSnapshot.childSnapshot(forPath: "price").value as? String ?? "No Price"
    self.weight = withSnapshot.childSnapshot(forPath: "weight").value as? String ?? "No Weight"
    self.imageUrl = withSnapshot.childSnapshot(forPath: "imageUrl").value as? String ?? "No ImageUrl"
}

}


import UIKit
import SDWebImage
import Firebase

class ProductListCell: UITableViewCell {

    @IBOutlet weak var productImage: UIImageView!
    @IBOutlet weak var productName: UILabel!
    @IBOutlet weak var weight: UILabel!
    @IBOutlet weak var price: UILabel!

    func updateUI(with productList: ProductList) {
        productName.text = productList!.name
        price.text = productList!.price
        weight.text = productList!.weight
        productImage.text = productList!.imageUrl
    }

    override func layoutSubviews() {
        super.layoutSubviews()
    }
}

Edit: this is what I'm getting so far in my errors error 2 it seems like its more a firebase problem than my coding error

  • There's nothing in the code that retrieves any data from Firebase. Please take a moment and review the Firebase Getting Started Guide [Read Data Once](https://firebase.google.com/docs/database/ios/read-and-write#read_data_once). – Jay Sep 10 '19 at 15:36
  • Just put in some firebase data. – Javier Calderon Sep 11 '19 at 11:08
  • Ok, so now you need to do some basic troubleshooting. Nothing in your code calls `fetchProducts` so start with that. Then, add a breakpoint in your code and step through it line by line until one of your vars isn't what you expect it to be - that's common practice when *debugging*. Once you've nailed down the problematic code, update you question with that info and we'll take a look! – Jay Sep 11 '19 at 15:18
  • Sorry for the wait, just updated the code and this seems to kind of work problem is that my my app now shows an empty table view – Javier Calderon Sep 12 '19 at 16:48
  • it seems like the code works its well its just not retriving data from the database for some reason – Javier Calderon Sep 12 '19 at 17:50
  • You code definitely will NOT work as as. The Firebase node is *Products* but your code is trying to retrieve data from *products* here `.child("products")`. Also you're also using `productsRef.observe(.childAdded` which means firebase will iterate over each child node one at a time and present that node in the snapshot. e.g. you don't need this `let allProducts = snapshot.children`. You can just access the child snapshot nodes directly as there's nothing to iterate over. – Jay Sep 12 '19 at 19:17
  • you are right and I'm wrong about my previous statement, I'm still really new to using firebase so I am struggling to figure out how to even put in the code for this correctly, I just want my project to work so it can retrieve data from the my database correctly I'm really tired of seeing an empty tableview – Javier Calderon Sep 12 '19 at 21:06
  • Another two issues I just noticed is the keys you're trying to use `dfkjsv@#4qc22aWE` are not valid because they contain @ and #. See [How To Structure Your Data](https://firebase.google.com/docs/database/ios/structure-data). The other issue is that when the snapshot children are being read, you've got `childSnapshot(forPath: "imageUrl")` however, the actual node name is `imageURL`. It is case sensitive so they need to match exactly. – Jay Sep 12 '19 at 22:39

1 Answers1

2

Let me see if I can get you going the right direction. There are a couple parts to this question so I am drilling down to the Firebase part. If the tableView is not showing after that, that's a tableView delegate issue but it looks like the code you have is ok.

First issue to address is the Firebase structure - well, not the stucture but the keys. From the docs

If you create your own keys, they must be UTF-8 encoded, can be a maximum of 768 bytes, and cannot contain ., $, #, [, ], /, or ASCII control characters 0-31 or 127. You cannot use ASCII control characters in the values themselves, either.

So you can see your keys have illegal characters in them. It's generally best practice to allow Firebase to create the keys with .childByAutoId().

Looking at the ProductList class, it's really fine but you're probably going to also want to keep track the Firebase key. So for example a user selects it, you'll know which item it is. So I just added a key property

class ProductList {
    var key: String
    var name: String
    var price: String
    var weight: String
    var imageUrl: String

    init(withSnapshot: DataSnapshot) {
        self.key = withSnapshot.key
        self.name = withSnapshot.childSnapshot(forPath: "name").value as? String ?? "No Name"
        self.price = withSnapshot.childSnapshot(forPath: "price").value as? String ?? "No Price"
        self.weight = withSnapshot.childSnapshot(forPath: "weight").value as? String ?? "No Weight"
        self.imageUrl = withSnapshot.childSnapshot(forPath: "imageURL").value as? String ?? "No ImageUrl"
    }
}

As mentioned in a comment, the child node names are case sensitive so I also updated the code to read the imageURL node (to match your structure) instead of imageUrl.

Then a class array to store the Products (as shown in your code)

var feeds = [ProductList]()

Then the code to actually read the nodes and populate the feeds array.

func readProducts() {
    let productsRef = self.ref.child("Products") //self.ref points to my firebase
    productsRef.observe(.childAdded, with: { snapshot in
        let aProduct = ProductList(withSnapshot: snapshot)
        self.feeds.append(aProduct)
    })
}

The thing to note here is that we are reading using observe.childAdded. The .childAdded iterates over each child node, one at a time initially, and the .observe leaves an observer on that node that will call the code in the closure any time a new child is added.

Then at some later time, we want to see what's in the array

func printProducts() {
    for product in self.feeds {
        print(product.key, product.name)
    }
}

and then the output when printProducts is called

dfkjsv4qc22aWE Apples
se04Fws444 Flower

I removed the illegal characters from the structure for this answer but again .childByAutoId() is your friend. It's always valid, always unique and separates the key from the data it contains.

Last thought on this is: when do I reload my tableView. There are a couple of options but one is to read the data once with .observeSingleEvent by .value and then iterate over the child nodes in code, populating the array (kind of what you have in your question) and then reload the tableView.

The second option is to leverage that .value events are always fired LAST, after other events such as .childAdded. So you could use childAdded to populate the array but also add a .value event that will fire after all the child nodes are added, to then reload your tableView. There are a number of examples of that process here on SO. Oh, here's one now

Run Code after all children are added

Jay
  • 34,438
  • 18
  • 52
  • 81