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
Related
I'm quite new to Swift and currently dealing with the Firebase-Database.
I managed to realise the functions that I want to have, but my implementation feels not right.
Most I am struggling with the closures, that I need to get data from Firebase.
I tried to follow the MVC approach and have DataBaseManager, which is getting filling my model:
func getCollectionData(user: String, callback: #escaping([CollectionData]) -> Void) {
var dataArray: [CollectionData] = []
var imageArray:[String] = []
let db = Firestore.firestore()
db.collection(user).getDocuments() { (QuerySnapshot, err) in
if let err = err {
print("Error getting documents : \(err)")
}
else {
for document in QuerySnapshot!.documents {
let album = document.get("album") as! String
let artist = document.get("artist") as! String
let genre = document.get("genre") as! String
let location = document.get("location") as! String
var a = CollectionData(album: album, artist: artist, imageArray: imageArray, genre: genre, location: location)
a.imageArray!.append(document.get("fronturl") as? String ?? "No Image")
a.imageArray!.append(document.get("backurl") as? String ?? "No Image")
a.imageArray!.append(document.get("coverlurl") as? String ?? "No Image")
dataArray.append(a)
}
callback(dataArray)
}
}
}
With this I'm getting the information and the downloadlinks, which I later use in a gallery.
Is this the right way?
I feel not, because the fiddling starts, when I fetch the data from my ViewController:
var dataArray = []
dataBaseManager.getCollectionData(user: user) { data in
self.dataArray = data
I can see, that I sometimes run into problems with timing, when I use data from dataArray immediately after running the closure.
My question is, this a valid way to handle the data from Firebase or is there a more elegant way to achieve this?
You are on the right track. However, using dataArray immediately is where the issue could be.
Let me provide a high level example with some pseudo code as a template:
Suppose you have an ToDo app; the user logs in and the first view they see is all of their current To Do's
class viewController
var dataArray = [ToDo]() //a class property used as a tableViewDataSource
#IBOutlet toDoTableView
viewDidLoad {
loadToDos()
}
func loadToDos() {
thisUsersToDoCollection.getDocuments() { documents in
self.array.append( the to Do Documents)
self.toDoTableView.reloadData()
}
}
}
With the above template you can see that within the Firebase .getDocuments function, we get the To Do documents from the colleciton, populate the dataSource array and THEN reload the tableView to display that data.
Following this design pattern will alleviate this issue
I sometimes run into problems with timing, when I use data from
dataArray immediately after running the closure
Because the user cannot interact with the data until it's fully loaded and populated within the tableView.
You could of course do a callback within the loadToDos function if you prefer so it would then be called like this - only reload the tableView once the loadToDos function has completed
viewDidLoad {
loadToDos {
toDoTableView.reloadData()
}
}
The big picture concept here is that Firebase data is ONLY VALID WITHIN THE CLOSURE following the Firebase call. Let that sequence provide pacing to your app; only display info if it's valid, only allow the user to interact with the data when it's actually available.
I have a button and below it is the table view. Table view cell has some random data.On button click I am calling the the api(function name is : api.urlRequest(userID: 80, businessUnitID: 2) ) .I have an API that has 35,0000 entries. What I want is to save that data in Realm database. The problem is that, when I am calling the save function, my UI freezes. I am appending the JSON data to Model and then saving it to database. I can get the start index and end index of the the JSON data.
What I tried was to call the API on background thread and when saving function is called, I am calling it on main thread. But this didn't worked.
class ViewController: UIViewController,getAdhocJSONDelegate{
let realm = try! Realm()
#IBOutlet weak var tableViewRef: UITableView!
var array = [NSDictionary]()
var adhocData : [AdhocModel] = []//for appending the JSON data to the model
var adhocDB : Results<AdhocDB>?// for accessing the database
let api = AdhocAPIParamteres()
var adhocJSONDatafromAPI : NSDictionary!
override func viewDidLoad() {
super.viewDidLoad()
adhocDB = realm.objects(AdhocDB.self)
}
#IBAction func buttonPressed(_ sender: Any) {
print("BUtton Tapped")
api.urlRequest(userID: 80, businessUnitID: 2)
api.delegate = self
}
func appTutorialData(json: NSDictionary) {
adhocJSONDatafromAPI = json
let apiData = adhocJSONDatafromAPI.value(forKey: "data") as! [NSDictionary]
print("Start Index of the data : ",apiData.startIndex)
print("End Index of the data : ",apiData.endIndex)
apiData.forEach { (abc) in
let model = AdhocModel()
model.site_id = abc.value(forKey: "site_id") as! Int
model.atm_id = abc.value(forKey: "atm_id") as! String
model.site_address = abc.value(forKey: "site_address") as! String
adhocData.append(model)
print("data appended")
DispatchQueue.main.async {
self.saveToDb(data:model)
}
}
func saveToDb(data: AdhocModel) {
let adhoc = AdhocDB()
try! realm.write {
adhoc.SiteId = data.site_id
adhoc.AtmId = data.atm_id
adhoc.SiteAdress = data.site_address
realm.add(adhoc)
}
}
}
I want to save data in such a way that my UI doesn't freeze.
There are a few issues with the code and writing data to Realm on a background thread is covered in the documentation so I won't address that. Following that design pattern will correct the UI lockup.
This is another issue
func saveToDb(data: AdhocModel) {
**let adhoc = AdhocDB()**
You want to write your populated model to realm, but AdhocDB is a Results object, not a Realm model object. Additionally the realm object created in appTutorialData which is model, is passed to saveToDb, then another object is created and then populated with data from the first object. There's no reason to do that (in this code)
Assuming AdHocModel is a Realm object, this is much cleaner
func appTutorialData(json: NSDictionary) {
adhocJSONDatafromAPI = json
let apiData = adhocJSONDatafromAPI.value(forKey: "data") as! [NSDictionary]
print("Start Index of the data : ",apiData.startIndex)
print("End Index of the data : ",apiData.endIndex)
apiData.forEach { (abc) in
let model = AdhocModel()
model.site_id = abc.value(forKey: "site_id") as! Int
model.atm_id = abc.value(forKey: "atm_id") as! String
model.site_address = abc.value(forKey: "site_address") as! String
try! realm.write {
realm.add(model)
}
}
}
You're going to want to wrap that write within a background thread (again, see the documentation) something like this
DispatchQueue(label: "background").async {
autoreleasepool {
.
.
.
try! realm.write {
realm.add(model)
}
}
}
You may ask about populating your array adhocData.append(model). We don't know what you're doing with it but if you're using it as perhaps a dataSource for a table view or some other UI element, you may want to consider using a Results object instead of an Array.
A significant advantage is, if you have 35,000 objects, that's a pretty sizable array and if you have more, it could overwhelm the device as ALL of that data is stored in memory. However, Results objects are lazily loaded so you could have a much larger dataset without overwhelming the device.
Additionally, when Realm objects are stored in an array, they 'Disconnect' from Realm and loose Realm functionality - they will not auto-update nor will changes to the actual object in Realm be reflected in that array nor can you just update the object - it doesn't appear to have a primary key.
However, if you populate a Results object with those models, they will be live updating - so if for example the atm_id changes in Realm, that object will automatically be updated. If you need to change a property you can change it directly on that object within a write transaction.
So the pattern would be to have a class var of Results and load your objects into those results within viewDidLoad. As you add more models, the results object will automatically be updated.
To keep your UI fresh, you would want to add observers (aka Notifications)to those Results so you can be notified when an object is updated so you can reload your tableView for example.
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
i am a newbie in iOS development, and i am learning a tutorial about read and write data to firebase. I want to retrieve data from Firebase and populate the tableView with it.
I am confused when retrieving the data from real time database using .value data event type when observing the reference. here is the simplified code
class Story
{
var text = ""
var numberOfLikes = 0
var numberOfAngry = 0
let ref: FIRDatabaseReference!
init(snapshot: FIRDataSnapshot)
{
ref = snapshot.ref
if let value = snapshot.value as? [String : Any] {
text = value["text"] as! String
numberOfLikes = value["numberOfLikes"] as! Int
numberOfAngry = value["numberOfAngry"] as! Int
} else {
numberOfAngry = 0
numberOfLikes = 0
}
}
}
class StoriesTableViewController: UITableViewController
{
// MARK: - Properties
var stories = [Story]()
private let storiesRef = FIRDatabase.database().reference().child("stories")
#IBOutlet weak var composeBarButtonItem: UIBarButtonItem!
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
storiesRef.observe(.value, with: { snapshot in
self.stories.removeAll()
for child in snapshot.children {
let story = Story(snapshot: child as! FIRDataSnapshot)
self.stories.append(story)
}
self.tableView.reloadData()
})
}
my questions are...
i have looked other tutorial which almost the same, just write simple data and populate the tableview with it, but the other tutorial just use .childadded as the event type. i don't understand why in this tutorial .value event type is used ? because it looks more complicated.
why we have to loop the snapshot.children ? is snapshot.children is just the same as child when we create database reference?
actually i am not really comfortable with term snapshot(FIRDataSnapshot) and reference (FIRDatabasereference). is there any article or video explaining about this term?
i am sorry if i am asking too many questions and it seems silly, just a rookie who wants to really grasp of this code. Thanks in advance
When you observe the .value event, your completion handler gets called once; with a snapshot of all child nodes. This allows you to handle all child nodes at once, which can be handy to do things like updating counters, or reducing the number of updates to a table view.
When you observe a list of items from the Firebase Database with a .value event, there will potentially be multiple child nodes. So the snapshot contains a list of those results. Even if there is only a single child node, the snapshot will contain a list of one child node. So your completion handler needs to loop over those results, which you do by iterating over snapshot.children.
A FIRDataSnapshot/DataSnapshot is a Firebase object that contains a snapshot of the data you requested at a specific time.
I tried a lot of methods from stackoverflow and from googling
but I got no luck of reaching what I need.
simply I want to store the response from api request to some variable
so that I can use it freely.
here is the code
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var responseLabel: UILabel!
let apiUrlStr = "http://jsonplaceholder.typicode.com/posts"
var resData = NSArray()
override func viewDidLoad() {
super.viewDidLoad()
request(.GET, apiUrlStr, encoding: .JSON).responseJSON { (_, _, JSON, _) in
let arr = JSON as NSArray
let user = arr[0] as NSDictionary
self.responseLabel.text = user.objectForKey("title") as? String
self.resData = JSON as NSArray //i cant get this from outside
}
println(resData) //prints nothing but empty parentheses while inside the request it prints the data right
}
}
because I have multiple classes and every class need to handle the response in a different way I want to create an api class with a response method that will return the response.
something like this maybe:
class api{
func req(url: String, params: NSDictionary){
//method goes here
}
func res() -> NSDictionary{
//method goes here
var response = NSDictionary()
return response
}
}
then use it like this in viewdidload
let params = ["aaa","bbb"]
let api = api()
api.req(apiUrlStr, params)
let res = api.res()
***by the way the request method I'm using right now is from Alamofire.swift and I dont mind using another method
here is some of the posts and sites I tried to follow without a luck
proper-way-to-pull-json-api-resoponse
link2
all lead to the same result response only from inside the method.
The problem is that the call to the network (looks like you're using alamofire) is asynchronous. So the network request is started and before it completes, your
println(resData)
line executes. At this point the response from the network hasn't returned so resData is still an empty array.
Try doing this instead:
Add a new function
func handleResponse(resData: NSArray) {
self.resData = resData
println(resData)
}
Then in the response code replace
self.resData = JSON as NSArray
with
let resData = JSON as NSArray
self.handleResponse(resData)
Assuming you need more than just a println statement to change when the data is loaded, you will also need to inform things like table views or other UI components to reload their data source in this method.