Cannot retrieve data from Firebase using Swift - ios

As the title says I have a weird problem to retrieve simple data from Firebase, but I really can't figure out where I'd go wrong.
This is my schema:
And this the code:
import Firebase
let DB_BASE = Database.database().reference()
class FirebaseService {
static let instance = FirebaseService()
private var REF_BASE = DB_BASE
private var REF_SERVICE_STATUS = DB_BASE.child("Service_Status")
struct ServiceStatus {
var downloadStatus: Bool
var uploadStatus: Bool
}
func getServiceStatus() -> (ServiceStatus?) {
var serviceStatus: ServiceStatus?
REF_SERVICE_STATUS.observeSingleEvent(of: .value) { (requestSnapshot) in
if let unwrapped = requestSnapshot.children.allObjects as? [DataSnapshot] {
for status in unwrapped {
serviceStatus.downloadStatus = status.childSnapshot(forPath: "Download_Status").value as! Bool
serviceStatus.uploadStatus = status.childSnapshot(forPath: "Upload_Status").value as! Bool
}
// THANKS TO JAY FOR CORRECTION
return sponsorStatus
}
}
}
}
but at the end serviceStatus is nil. Any advice?

I think you may be able to simplify your code a bit to make it more manageable. Try this
let ssRef = DB_BASE.child("Service_Status")
ssRef.observeSingleEvent(of: .value) { snapshot in
let dict = snapshot.value as! [String: Any]
let down = dict["Download_Status"] ?? false
let up = dict["Upload_Status"] ?? false
}
the ?? will give the down and up vars a default value of false if the nodes are nil (i.e. don't exist)
Oh - and trying to return data from a Firebase asynchronous call (closure) isn't really going to work (as is).
Remember that normal functions propagate through code synchronously and then return a value to the calling function and that calling function then proceeds to the next line of code.
As soon as you call your Firebase function, your code is going to happily move on to the next line before Firebase has a chance to get the data from the server and populate the return var. In other words - don't do it.
There are always alternatives so check this link out
Run code only after asynchronous function finishes executing

Related

How to get progress of asynchronous Firebase data read?

I have some code that reads data from Firebase on a custom loading screen that I only want to segue once all of the data in the collection has been read (I know beforehand that there won't be more than 10 or 15 data entries to read, and I'm checking to make sure the user has an internet connection). I have a loading animation I'd like to implement that is started by calling activityIndicatorView.startAnimating() and stopped by calling activityIndicatorView.stopAnimating(). I'm not sure where to place these or the perform segue function in relation to the data retrieval function. Any help is appreciated!
let db = Firestore.firestore()
db.collection("Packages").getDocuments{(snapshot, error) in
if error != nil{
// DB error
} else{
for doc in snapshot!.documents{
self.packageIDS.append(doc.documentID)
self.packageNames.append(doc.get("title") as! String)
self.packageIMGIDS.append(doc.get("imgID") as! String)
self.packageRadii.append(doc.get("radius") as! String)
}
}
}
You don't need to know the progress of the read as such, just when it starts and when it is complete, so that you can start and stop your activity view.
The read starts when you call getDocuments.
The read is complete after the for loop in the getDocuments completion closure.
So:
let db = Firestore.firestore()
activityIndicatorView.startAnimating()
db.collection("Packages").getDocuments{(snapshot, error) in
if error != nil{
// DB error
} else {
for doc in snapshot!.documents{
self.packageIDS.append(doc.documentID)
self.packageNames.append(doc.get("title") as! String)
self.packageIMGIDS.append(doc.get("imgID") as! String)
self.packageRadii.append(doc.get("radius") as! String)
}
}
DispatchQueue.main.async {
activityIndicatorView.stopAnimating()
}
}
As a matter of style, having multiple arrays with associate data is a bit of a code smell. Rather you should create a struct with the relevant properties and create a single array of instances of this struct.
You should also avoid force unwrapping.
struct PackageInfo {
let id: String
let name: String
let imageId: String
let radius: String
}
...
var packages:[PackageInfo] = []
...
db.collection("Packages").getDocuments{(snapshot, error) in
if error != nil{
// DB error
} else if let documents = snapshot?.documents {
self.packages = documents.compactMap { doc in
if let title = doc.get("title") as? String,
let imageId = doc.get("imgID") as? String,
let radius = doc.get("radius") as? String {
return PackageInfo(id: doc.documentID, name: title, imageId: imageId, radius: radius)
} else {
return nil
}
}
}
There is no progress reporting within a single read operation, either it's pending or it's completed.
If you want more granular reporting, you can implement pagination yourself so that you know how many items you've already read. If you want to show progress against the total, this means you will also need to track the total count yourself though.

How to make firebase .observeSingleEventOf function run synchronously

I am running Firebase's .getSingleEventOf function to read data from my database in my program, and it is an asynchronous function. How would I make it synchronous (or make another synchronous function to house the code)?
I've tried to use the data passed through the function, but it hasn't been working! It only returns 0 elements, even though I know that I have data with the specific letters in my database.
import Foundation
import Firebase
struct CompeteUserFinderService {
static func findCompetitors(contains letters: String?) -> [String] {
//Make sure there was an input
guard let letters = letters else { return [] }
var usernames = [String]()
//Database reference
let ref = Database.database().reference().child("usernames")
ref.observeSingleEvent(of: .value) { (snapshot) in
//Creates an array of all values in the usernames branch
let value = Array((snapshot.value as! [String: Any]).keys)
usernames = value.map { $0.lowercased() }.filter { $0.contains(letters.lowercased()) }
}
return usernames
}
}

the retrieved data doesn't append in my array swift4

I'm trying to append "the retrieved data -Keys- from firebase" into an array but it doesn't work
This is the for loop output #2 the retrieved keys
This the keys from firebase
This is the code
let ref = Database.database().reference()
ref.child("Faculty ").observe(.value, with: { (snapshot) in
if let result = snapshot.children.allObjects as? [DataSnapshot] {
for child in result {
let FacultyName = child.key as! String
print(FacultyName)
self.NamesofFac.append(FacultyName)
}
}
})
for i in 0...self.NamesofFac.count {
print(self.NamesofFac.count)
print(" line")
print(self.NamesofFac)
The problem you are having is the Firebase Observe function give a callback in the form of a (snapshot).
It takes a bit of time to go to the web to get the data, therefore, firebase returns the data asynchronously. Therefore your code in your for loop will run before your firebase data has been returned. At the time your for loop code runs the array is still blank. But the for loop code in a separate function as you see in my sample code and call it straight after your for loop inside your firebase observe call.
Try this instead:
override func viewDidLoad() {
getFirebaseData()
}
func getFirebaseData() {
let ref = Database.database().reference()
ref.child("Faculty ").observe(.value, with: { (snapshot) in
if let result = snapshot.children.allObjects as? [DataSnapshot] {
for child in result {
let FacultyName = child.key as! String
print(FacultyName)
self.NamesofFac.append(FacultyName)
}
printNames()
}
})
}
func printNames() {
for i in 0...self.NamesofFac.count {
print(self.NamesofFac.count)
print(" line")
print(self.NamesofFac)
}
}
This was it won't print the names until they have been fully loaded from firebase.
PS: Your naming conventions are incorrect. You seem to be naming variables with a capital letter. Variables should be camel case. Classes should start with a capital.

Can't retrieve data from Firebase Database

I my code I can't retrieve data from Firebase.
When I am adding values on Firebase I do see that items added on Firebase database on my console.
Also I had created listener .childAdded and I see that items added on Firebase Database.
But when I call .value it retrieves nil result.
I don't know what's happening there.
Here is my code.
class FirebaseManager {
var ref: DatabaseReference?
var database: DatabaseHandle?
static let shared: FirebaseManager = {
Database.database().isPersistenceEnabled = true
return FirebaseManager()
}()
private func initFireabase() {
ref = Database.database().reference(withPath: AMUtils.getUDID())
}
func addToDB(composition: Composition) {
initFireabase()
ref?.child(String(composition.id)).child("id").setValue(composition.id)
ref?.child(String(composition.id)).child("title").setValue(composition.title)
}
func removeFromDb(composition: Composition) {
ref?.child(String(composition.id)).removeValue()
}
func getCompositonFromDB(onResult: #escaping ([Composition]) -> Void){
initFireabase()
var compositions: [Composition] = []
database = ref?.observe(.value, with: { (snapshot) in
compositions.removeAll()
let value = snapshot.value as? JSON
compositions.append(AudioListParser.parseObject(json: value!))
self.ref?.removeObserver(withHandle: self.database!)
onResult(compositions)
})
}
}
getCompositonFromDB() I am calling when I am starting the view controller and this is always nil even I have values on database
Could anyone tell me what I did wrong here?
have you changed the rules for database??
like if you are authenticating you should set not null for both write and read and if you are not authenticating then keep it null for both, because doing exact opposite of above could cause some errors!!

How to call data within a custom class function from Firebase

I have a post class that I use to fill a collection view with post data from Firebase. I was having trouble getting some user data so I tried putting the observer in the post class. This seems to work fine, however there is a slight delay in getting the data from Firebase so it seems to finish the init() function before the firebase call is complete. This is the post class :
class Post {
var _comment1Text: String?
var _comment1User: String?
var _comment1Name: String?
init(comment1Text: String, comment1User: String, comment1Name: String) {
self._comment1Text = comment1Text
self._comment1User = comment1User
self._comment1Name = comment1Name
if self._comment1User != "" {
DataService.ds.REF_USERS.child(self._comment1User!).observeSingleEventOfType(.Value, withBlock: { userDictionary in
let userDict = userDictionary.value as! NSDictionary
self._comment1Name = userDict.objectForKey("username") as? String
})
}
print(self._comment1Text)
print(self._comment1Name)
}
}
If I print within the firebase call, it works. However, if I print after it, for some reason, comment1name is not yet filled. Is there a way to get self._comment1Name to contain the data from Firebase in time to fill the collectionView?
Thanks in advance.
DataService.ds.REF_USERS.child(self._comment1User!).observeSingleEventOfType(.Value
is an Asynchronous call, So access your print functions inside the completionBlock and you have to update your collectionView inside the completionBlock.
DataService.ds.REF_USERS.child(self._comment1User!).observeSingleEventOfType(.Value, withBlock: { userDictionary in
let userDict = userDictionary.value as! NSDictionary
self._comment1Name = userDict.objectForKey("username") as? String
print(self._comment1Text)
print(self._comment1Name)
// Update your collectionView
})
Asynchronous call's are loaded in a different network thread, so it takes some time to retrieve the DB from the server.
If you are looking for communicating between a custom class and you viewController look at my this answer :- https://stackoverflow.com/a/40160637/6297658

Resources