1

Novice programmer, currently working on an ios app using firebase as our backend. I'm trying to grab values from the firebase database to populate a TableView, but there are two issues. Firstly, it seems to ignore the ref.observeSingleEvent that I set up to try and retrieve the values, and while running the console ouputs:

2016-10-16 00:26:02.635: STOP!! Will reset deviceID from memory.
2016-10-16 00:26:02.635: Failed to fetch default token Error Domain=com.firebase.iid Code=6 "(null)"

similar to this issue. I'm fairly certain I've followed the Firebase documentation correctly for setup.

Important Part of Code within CommitteesTableViewController:

// Sets each cell up with its committee title
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "committeeCell", for: indexPath) as! CommitteeTableViewCell
    let committee = cell.committeeLabel
    let sectionRef = FIRDatabase.database().reference(withPath: "Committee".child(String(indexPath.section))
    let committeeRef = sectionRef.child(String(indexPath.row))
    let currentRef = committeeRef.child(String(indexPath.row)).child("name")
    currentRef.observeSingleEvent(of: .value, with: { (snapshot) in
        committee?.text = snapshot.value as? String
    })
    return cell
}

JSON Database that is in firebase:

{
"Committee" : {
    "0" : {
        "0" : {
            "name" : "First Disarmament and International Security Committee",
            "Head Chair" : "Jessie Mao",
            "Vice Chairs" : "Himaja Jangle, Sita McGuire",
            "Topics" : "Congo: Resources in Conflict, Modern Warfare: Privatization of War"
        },
        "1" : {
            "name" : "UN Special,  Political And Decolonization Committee",
            "Head Chair" : "Trevor Dowds",
            "Vice Chairs" : "Lucia Zhang, Sarah Yue",
            "Topics" : "A Review of UN Peacekeeping Operations, Situation in Sudan and South Sudan"
        },
        "2" : {
            "name" : "UN 6th Legal Committee",
            "Head Chair" : "Benjy Malings",
            "Vice Chairs" : "Sherry Guo, Emma Lautanen",
            "Topics" : "Reform of the ICC/ICJ System, Reconsidering the Responsibility to Protect (R2P)"
        },
        "name" : "Bloc A"
    },
    "1" : {
        "0" : {
            "name" : "UN Human Rights Council",
            "Head Chair" : "Amanda Lee",
            "Vice Chairs" : "Jessica Zhao",
            "Topics" : "Palestinian Women's Rights, Capital Punishment and the Rights of Prisoners"
        },
        "1" : {
            "name" : "UN Educational, Scientific, And Cultural Organization",
            "Head Chair" : "Adam Umemoto",
            "Vice Chairs" : "Emily Yan, Jacob Hands",
            "Topics" : "Child Soldiers: The Rights of Children in Armed Conflict, Reconsidering the 2030 Education Agenda"
        },
        "2" : {
            "name" : "UN Economic And Social Council",
            "Head Chair" : "Alex Feibleman",
            "Vice Chairs" : "Octavio Garcia Farfan, Liam Campbell",
            "Topics" : "Strengthening Resilience to Climate Related Disasters, Commercial Bribery in Multinational Organizations"
        },
        "3" : {
            "name" : "World Health Organization",
            "Head Chair" : "Mekhala Hoskote",
            "Vice Chairs" : "Brandon Doan, Ashley Njoroge",
            "Topics" : "Sustainable Vaccine Practices and Financing, Improving Slum Health"
        },
        "4" : {
            "name" : "International Criminal Police Organization, INTERPOL ",
            "Head Chair" : "Natasha Cougoule",
            "Vice Chairs" : "Chelsea Evans, Jacky Tian",
            "Topics" : "Controlling the Black Market Trade of Civil War Antiques, Protection of Journalists in Conflict Zones"
        },
        "name" : "Bloc B"
    },
    "2" : {
        "0" : {
            "name" : "UN Security Council",
            "Head Chair" : "Kim Nguyen, TJ Ford",
            "Vice Chairs" : "Mischa Fritz",
            "Topics" : "South China Sea, Open Agenda"
        },
        "1" : {
            "name" : "Historical UN Security Council",
            "Head Chair" : "Pranay Patil",
            "Vice Chairs" : "Michael Pollack",
            "Topics" : "Gulf War, Khmer Rouge"
        },
        "2" : {
            "name" : "North Atlantic Treaty Organization",
            "Head Chair" : "Gloria Cheung",
            "Vice Chairs" : "Trent Gomberg",
            "Topics" : "Post-Intervention Stability in Libya, Unrest in the East: Security in the Black Sea and Baltic Region"
        },
        "3" : {
            "name" : "African Union",
            "Head Chair" : "Itago Kangashi",
            "Vice Chairs" : "Zoe Brouns",
            "Topics" : "Volatile Political Systems: The Role of the AU in Establishing Democracies, The State of Agriculture and Food Security"
        },
        "4" : {
            "name" : "Organization Of American States",
            "Head Chair" : "Adrian Hernandez",
            "Vice Chairs" : "Jane Kim",
            "Topics" : "Drugs and Development in Latin America, Migration Movements in Latin America"
        },
        "5" : {
            "name" : "Chinese State Council",
            "Head Chair" : "Rita Hu",
            "Vice Chairs" : "Haochen Zhou",
            "Topics" : "Review on the Loosening of the One-Child Policy, One Belt, One Road"
        },
        "6" : {
            "name" : "United States Senate",
            "Head Chair" : "Jonas Majewski",
            "Vice Chairs" : "Eric Cherwin",
            "Topics" : "Immigration Reform and a Pathway to Citizenship, Trade Agreements and the American Economy"
        },
        "7" : {
            "name" : "UNEP Division Of Environmental Laws And Conventions",
            "Head Chair" : "Nate Parke",
            "Vice Chairs" : "Kendra Singh",
            "Topics" : "Compensatory Mitigation in a Global Context, Common but Differentiated Environmental Responsibility"
        },
        "8" : {
            "name" : "UN Framework Convention On Climate Change  - Conference Of The Parties 22.5",
            "Head Chair" : "Katie Lee",
            "Vice Chairs" : "Se Yeon Kim",
            "Topics" : "Building Adaptation Capacity for Vulnerable Communities, Governance of Geoengineering"
        },
        "9" : {
            "name" : "European Court Of Human Rights",
            "Head Chair" : "Jake Moskowitz",
            "Vice Chairs" : "Patty Midy",
            "Topics" : "Cyprus v. Turkey"
        },
        "10" : {
            "name" : "Press Corps",
            "Head Chair" : "Sarah Bauer",
            "Vice Chairs" : "Hyunwook Kim, Stacey Dojiri",
            "Topics" : "Open Agenda"
        },
        "name" : "Specialized"
    },
    "3" : {
        "0" : {
            "name" : "Joint Cabinet Crisis",
            "Head Chair" : "Michael Eliot",
            "Vice Chairs" : "Andy Luo, Michael McDonald, Tushita Saraf",
            "Topics" : "Trade Wars: Anglo-Dutch East India Companies"
        },
        "1" : {
            "name" : "Committee For Sustainable Development",
            "Head Chair" : "Rob Purviance",
            "Vice Chairs" : "Daksh Bhatia",
            "Topics" : "Rebuilding a Nation: Infrastructure Development in Myanmar"
        },
        "2" : {
            "name" : "Historical Crisis",
            "Head Chair" : "Arjun Banerjee",
            "Vice Chairs" : "Alex Wilfert",
            "Topics" : "Unification of Germany"
        },
        "name" : "Crisis"
    }
}

}

UPDATE

So changing the firebase database in any way (like adding a random value) causes it to work momentarily, but then after stopping the app for some time it stops working again, and continues to work and not work sporadically. I think it has to do with the choice of observeSingleEvent but I'm not sure.

Community
  • 1
  • 1
Michael Eliot
  • 831
  • 8
  • 18
  • See my answer here :- http://stackoverflow.com/a/39560078/6297658. Are there any error's other than this. – Dravidian Oct 16 '16 at 09:40

3 Answers3

2

Can you try to change this:

let sectionRef = FIRDatabase.database().reference().child(String(indexPath.section))

to this:

let sectionRef = FIRDatabase.database().reference(withPath: "Committee").child(String(indexPath.section))
ronatory
  • 7,156
  • 4
  • 29
  • 49
  • Thank you! Yes this was an error, but not the underlying issue. Read the update I just edited the post with – Michael Eliot Oct 16 '16 at 09:56
  • Welcome, so what is your goal with your populated table view? You just want to show the data once or you want to show also updates of new added entries? Cause `observeSingleEventOfType` triggers once and then does not trigger again. @MichaelEliot – ronatory Oct 16 '16 at 10:01
  • All I want to do is pull the data once – Michael Eliot Oct 16 '16 at 10:10
  • For me your approach seems to be ok with the use of `observeSingleEventOfType`. Can you give it a try to change the line with `.observeSingleEvent ` to `currentRef.observe(.value, with: { (snapshot) in`? Just to see if the behavior is different and more useful. – ronatory Oct 16 '16 at 10:26
1

In general: It's best practise to create a function for your download purposes and call the function instead of putting all the lines of code inside your cell declaration:

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

    // we are passing the Label and the indexPath to out func
    myFirebaseFunc(label: cell.committeeLabel, indexPath: indexPath)
    return cell
}

Also you should use observeEventType, to handle the case, that there might be data added to your Firebase during runtime and you want the data to be added to your UITableView.

func myFirebaseFunc(label: UILabel, indexPath: NSIndexPath) {

    let sectionRef = FIRDatabase.database().reference(withPath: "Committee".child(String(indexPath.section))
    let committeeRef = sectionRef.child(String(indexPath.row))
    let currentRef = committeeRef.child(String(indexPath.row)).child("name")
    currentRef.observeEventType(of: .value, withBlock: { (snapshot) in

        if snapshot.exists() {
            label?.text = snapshot.value as? String
        }
    })
}

To your case: Also I assume that your App crashes because indexPath out of bounds. It's not best practise to download your Firebase data according to your indexPath within the population of the Cell. Since, as stated correctly by Jay, the download is async.

You have to download your desired data first and then populate a tableView according to your data like this:

You create an Array of Models (struct) or NSMutableDictionary.

// first you check, if your snapshot exists
if snapshot.exists() {

    // you delete your Array, you your data will not be added multiple
    // times once you add data in the background and the func gets
    // called again

    self.myArray.removeAll()

    // you sort your snapshot according to your needs (for example of the (pre set) date value. If date is missing, the code will not crash!
    let sorted = (snapshot.value!.allValues as NSArray).sortedArrayUsingDescriptors([NSSortDescriptor(key: "date",ascending: false)])

    // then you loop through your sorted Array
    for element in sorted {

       // you create a name String of your name element in your Dictionary
       let name = element.valueForKey("name")! as? String

       // if you have created a struct, you instantiate a model and set the name
       let m = MyStruct(name: name!)

       // and you append it to your Array     
       self.myArray.append(m)      
    }
    // last but not least we reload our tableView on the main threat
    DispatchQueue.main.async{
        self.tableView.reloadData()
    }
}

Then you set numbersOfRowsInSection to myArray.count

And fill your tableView with:

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

    let mine = myArray[indexPath.row]
    cell.committeeLabel.text = mine.name
    return cell
}

This would be our struct in this example:

struct MyStuct {
    var name: String = ""
}

Our Array instantiated in the ViewController class:

let myArray: [MyStruct] = []

In this best practise scenario we would call our myFirebaseFunc func at the viewDidAppear func of our ViewController.

David Seek
  • 16,783
  • 19
  • 105
  • 136
1

Firebase is asynchronous: it takes time for values to be returned from Firebase via a query or observe call.

Your code isn't allowing for that so whats happening is the observeSingleEvent is called and the cellForRowAt function is returning (an empty) cell before Firebase has a chance to return the data and populate committee.

Code is way faster than the internet so you need to operate on data within the observe blocks (closures) as that's the time when it's valid.

Best practice is to populate the datasource within an Firebase observe block (closure) - here are the steps

  1. Get the values from Firebase via observe .value block
  2. Within the block, iterate over the returned values and populate an array (datasource)
  3. Reload the tableView

and to sort by Head Char, here's a swifty solution. Assume the dict is populated with .values from Firebase

let dict = ["0": ["name": "First Disarmament and International Security Committee",
              "Head Chair": "Jessie Mao"],
            "1": ["name": "UN Special,  Political And Decolonization Committee",
              "Head Chair": "Trevor Dowds"],
            "2": ["name": "UN 6th Legal Committee",
              "Head Chair": "Benjy Malings"]
]
let sortedArray = Array(dict).sorted { $0.1["Head Chair"]! < $1.1["Head Chair"]! }
Jay
  • 34,438
  • 18
  • 52
  • 81
  • isn't that basically what I told him to do? :P you brought up the point with the asynchronous loading. +1 for that. but your step 1-3 is my suggestion as well. whatever +1 – David Seek Oct 16 '16 at 13:54
  • @DavidSeek It's similar but felt was important to focus on the main issue (which is shown in the *In General* section of your answer as well) of trying to call Firebase synchronously like a function. The *return cell* is (potentially) going to get called way before the myFirebase func has time to populate label?.text = in the observe closure. That's corrected in the To Your Case section. Also, blowing out the array and re-loading every time there's one change within the structure is heavy. I would suggest leveraging .added, .changed and .removed events to keep the flow lightweight. – Jay Oct 16 '16 at 15:36
  • yes. absolutely right, that there is a high chance, that the cell will be created before the download is completed. all solid and good points. thanks for the input – David Seek Oct 16 '16 at 15:38