After several hours of trying to figure out what's happening without finding an answer to fill my void anywhere, I finally decided to ask here.
While I assume I do have a concurrency issue, I have no clue as to how to solve it...
I have an application trying to pull data from a Firebase Realtime Database with the following content:
{
"products" : {
"accessory" : {
"foo1" : {
"ean" : 8793462789134,
"name" : "Foo 1"
},
"foo2" : {
"ean" : 8793462789135,
"name" : "Foo 2"
}
},
"cpu" : {
"foo3" : {
"ean" : 8793462789115,
"name" : "Foo 3"
}
},
"ios" : {
"foo4" : {
"ean" : 8793462789120,
"name" : "Foo 4"
},
"foo5" : {
"ean" : 8793462789123,
"name" : "Foo 5"
}
}
}
}
I have a data model in Product.swift:
class Product {
var identifier: String
var category: String
var ean: Int
var name: String
init(identifier: String, category: String, ean: Int, name: String) {
self.init(identifier: identifier)
self.category = category
self.ean = ean
self.name = name
}
}
I want to fetch the data in another class called FirebaseFactory.swift. I plan to use to communicate with Firebase:
import Foundation
import FirebaseDatabase
class FirebaseFactory {
var ref = Database.database().reference()
func getAvailableProducts() -> [Product] {
var products = [Product]()
var data: DataSnapshot = DataSnapshot()
self.ref.child("products").queryOrderedByKey().observeSingleEvent(of: .value) { (snapshot) in
data = snapshot
// 1st print statement
print("From within closure: \(data)")
}
// 2nd print statement
print("From outside the closure: \(data)")
// Getting the products yet to be implemented...
return products
}
}
For now, I am simply trying to call the getAvailableProducts() -> [Product] function from one of my view controllers:
override func viewWillAppear(_ animated: Bool) {
let products = FirebaseFactory().getAvailableProducts()
}
My Problem now is that the 2nd print is printed prior to the 1st – which also means that retrieving the data from the snapshot and assigning it to data variable does not take place. (I know that the code to create my Product objects is missing, but that part actually is not my issue – concurrency is...)
Any hints – before I pull out any more of my hairs – is highly appreciated!!
You're on the right track with your theory: the behavior you're describing is how asynchronous data works with closures. You've experienced how this causes problems with returning the data you want. It's a very common question. In fact, I wrote a blog on this recently, and I recommend you check it out so you can apply the solution: incorporating closures into your functions. Here's what that looks like in the particular case you've shown:
func getAvailableProducts(completion: #escaping ([Product])-> Void) {
var products = [Product]()
var data: DataSnapshot = DataSnapshot()
self.ref.child("products").queryOrderedByKey().observeSingleEvent(of: .value) { (snapshot) in
data = snapshot
// do whatever you were planning on doing to return your data into products... probably something like
/*
for snap in snapshot.children.allObjects as? [DataSnapshot] {
let product = makeProduct(snap)
products.append(product)
}
*/
completion(products)
}
}
Then in viewWillAppear:
override func viewWillAppear(_ animated: Bool) {
FirebaseFactory().getAvailableProducts(){ productsArray in
// do something with your products
self.products = productsArray
// maybe reload data if you have a tableview
self.tableView.reloadData()
}
}
If I understand your question, you need to return the data after the event occurs, because is an async event
class FirebaseFactory {
var ref = Database.database().reference()
func getAvailableProducts() -> [Product] {
var products = [Product]()
var data: DataSnapshot = DataSnapshot()
self.ref.child("products").queryOrderedByKey().observeSingleEvent(of: .value) { (snapshot) in
data = snapshot
// 1st print statement
print("From within closure: \(data)")
// Process here the snapshot and insert the products in the array
return products
}
}
}
Related
How to display the below data in a view:
[
"parties" {
"2016-12-30" {
"uid" {
"name": "joe",
"age": "32"
},
"uid" {
"name": "kevin",
"age": "29"
}
},
"2016-12-25" {
"uid" {
"name": "ben"
"age": "44"
}
}
}
]
In my controller I have
var parties = [Party]()
DataService.ds.REF_PARTIES.observe(.value, with: { (snapshot) in
if let snapshot = snapshot.children.allObjects as? [FIRDataSnapshot] {
print(snapshot)
for snap in snapshot {
if let partyDict = snap.value as? [String: AnyObject] {
let key = snap.key // the party date
let party = Party(date: key, partyData: partyDict)
self.parties.append(party)
}
}
}
self.tableView.reloadData()
})
class PartyFeedCell: UITableViewCell:
func configureCell(party: Party) {
dateLabel.text = part.date // date as expected
}
In react, I could use Object.key to go deeper in the loop and get the users for those dates (keys). Any way to accomplish this? Under, say xxx date, I should see a list of users.
Party.swift:
import Foundation
class Party {
private var _name: String!
private var _age: String!
var name: String {
return _name
}
[...]
init(name: String, age: String) {
self._name = name
self._age = age
}
init(date: String, partyData: [String: AnyObject]) {
self._date = date
[...]
}
}
DataService.swift:
import Foundation
import FirebaseDatabase
let DB_BASE = FIRDatabase.database().reference()
class DataService {
static let ds = DataService()
private var _REF_BASE = DB_BASE
private var _REF_PARTIES = DB_BASE.child("parties").queryOrderedByKey()
var REF_BASE: FIRDatabaseReference {
return _REF_BASE
}
var REF_PARTIES: FIRDatabaseQuery {
return _REF_PARTIES
}
}
Two things:
Using dates as key's... while it may be ok, should probably be changed - best practice is to disassociate keys from the data contained in the children.
parties
-Y9809s0asas
date: "2016-12-30"
users:
uid_0: true
uid_1: true
-Y89js99skks
date: "2016-12-15"
users:
uid_1: true
uid_3: true
users
uid_0:
name: "ben"
age: "42"
uid_1:
name: "frank"
age: "28"
etc
The keys within parties are generated by childByAutoId (Y9809s0asas etc)
Second thing is that the code looks pretty much ok. The question appears to be how to get a value from a Dictionary?
In this case the code assigns the snap.value as a dictionary of [String: AnyObject] to partyDict. So at that point partyDict is just a Dictionary of key:value pairs
So in your Party class just a simple assignment is all that needs to be done:
init(date: String, partyData: [String: AnyObject]) {
self._date = date
self._age = partyData["age"]
self._name = partyData["name"]
}
I didn't run your code but it's pretty close to being usable.
Oh - with this change in structure you will want to add one more property to the Party class, key. Make sure you pass the parent node name (snap.key) into the class so if it needs to be updated etc it will have the parent node name stored (the Y9809s0asas from the above structure)
I'm working on a Swift app using Firebase Database. I have a structure like this:
"Groups" : {
"-KWewQiWc6QPfiq5X1AY" : {
"Members" : [ "emmaturner", "maxturner", "ethanturner" ],
"Teacher" : "ethanturner",
"Title" : "Kimbra Followers",
"groupId" : "-KWewQiWc6QPfiq5X1AY"
}
}
I'm trying to access the data in Members, in order to see if the user is in that Group.
I was previously using ref.child("Groups").queryOrdered(byChild: "Teacher").queryEqual(toValue: userId) However, I learned that this is only returning the group if the user is the Teacher. I tried "members/"
and "members/0" in place of Teacher, but of course these either do not work or only return access for the first user. How can I get Firebase to return the group as long as their name is mentioned in the array of group members?
Try this:
var ref: FIRDatabaseReference!
ref = FIRDatabase.database().reference()
var membersArray = [String]()
ref.child("Groups").observeSingleEvent(of: .value, with: { (snapshot) in
if let snapshots = snapshot.children.allObjects as? [FIRDataSnapshot] {
for snap in snapshots
{
let teacher = snap.childSnapshot(forPath: "Teacher").value as! String
self.ref.child("Groups").child(snap.key).child("Members").observeSingleEvent(of: .value, with: { (member) in
if let members = member.children.allObjects as? [FIRDataSnapshot] {
for mem in members
{
print(mem.value as! String)
let currentMember = mem.value as! String
membersArray.append(currentMember)
}
}
if(membersArray.contains(teacher))
{
print(membersArray)
}
})
}
}
})
I'm new to Firebase. Not good at DB also.
I'm really hoping that people could give me advices about my DB design and retrieving data codes.
I designed the DB as below.
The comment have a createTs for ordering.But the content shouldn't ordered by create time. So I put a "index" key into contents info in the post.(I'm not sure if it is a good way)
About the data retrieving, I made a dataManager object to load data.
DataManger.swift
func loadPosts(completeBlock : ([Post]) -> Void ) {
ref.child("posts").observeEventType(.Value , withBlock: { snapshot in
var postList : [Post] = []
if snapshot.value is NSNull {
} else {
for post in snapshot.children {
let postSnap = post as! FIRDataSnapshot
let post = Post()
post.setValue(withSanpShot: postSnap)
post.commentList = []
for key in post.comments.keys {
self.loadPostComment(withKey: key, completeBlock: { (comment) in
post.commentList.append(comment)
})
}
post.contentList = []
for key in post.contents.keys {
self.loadPostContent(withKey: key, completeBlock: { (content) in
post.contentList.append(content)
})
}
postList.append(post)
}
}
completeBlock(postList)
})
}
func loadPostComment(withKey key: String, completeBlock:(Comment) -> Void) {
ref.child("post-comments").child(key).observeEventType(.Value, withBlock: { commentSnapShot in
let comment = Comment()
comment.setValue(withSanpShot: commentSnapShot)
completeBlock(comment)
})
}
func loadPostContent(withKey key: String, completeBlock:(PostContent) -> Void) {
ref.child("post-contents").child(key).observeEventType(.Value, withBlock: { contentSnapShot in
let content = PostContent()
content.setValue(withSanpShot: contentSnapShot)
completeBlock(content)
})
}
And struct of the Post.swift.
class Post: NSObject {
var postID : String
var authorID: String
var title: String
var createTS : NSNumber
var comments : [String : AnyObject]
var contents : [String : AnyObject]
var commentList : [Comment] {
didSet {
//TODO: add some closure to update view.Maybe also need sorting codes
print("commentList : \(commentList)")
}
}
var contentList : [PostContent] {
didSet {
//TODO: add some closure to update view.Maybe also need sorting codes
print("contentList : \(contentList)")
}
}
}
I'm not sure the work I'v done is the right way to using it.Although I could got the data I want.
Please give me advices. That will be really helpful to me. Thanks.
I am new to Firebase and relatively new to Swift.
I have firebase set up as below. I have users, followers and blocked users. I take care of the followers in the UITableViewCell class.
I am wondering, before I go any further: how does performance get affected by putting observers in observers in queries in queries. (Hope these are the correct terms) . Is below the right way to go about it?(the most efficient way). It works, but also seems to stutter a bit. I appreciate any feedback.
{
"BlockedByUsers" : {
"ba1eb554-9a81-4a74-bfd9-484a32eee13d" : {
"97fee08f-19b2-4eb5-9eab-4b1985c22595" : true
}
},
"Dates" : {
"1457635040" : {
"97fee08f-19b2-4eb5-9eab-4b1985c22595" : true
},
},
"Locations" : {
"97fee08f-19b2-4eb5-9eab-4b1985c22595" : {
".priority" : "u14dkwm41h",
"g" : "u14dkwm41h",
"l" : [ 51.05521018175982, 3.720297470654139 ]
},
},
"Users" : {
"97fee08f-19b2-4eb5-9eab-4b1985c22595" : {
"blockedUsers" : {
"ba1eb554-9a81-4a74-bfd9-484a32eee13d" : true
},
"following" : {
"51879163-8b35-452b-9872-a8cb4c84a6ce" : true,
},
"fullname" : "",
"dates" : 1457635040,
"location" : "",
},
}
}
my Swift code with the multiple queries I'm worried about:
var usersRef: Firebase!
var userFollowingRef: Firebase!
var blockedByUsersRef: Firebase!
var datesRef: Firebase!
var geofireEndRef: Firebase!
var geoFireEnd: GeoFire? {
return GeoFire(firebaseRef: geofireEndRef)
}
var dateRangeStart = Int()
var dateRangeEnd = Int()
override func viewDidLoad(){
super.viewDidLoad()
usersRef = DataService.ds.REF_USERS
userFollowingRef = DataService.ds.REF_CURRENTUSER_FOLLOWING
blockedByUsersRef = DataService.ds.REF_BLOCKED_BY_USERS
datesRef = DataService.ds.REF_DATES
geofireEndRef = DataService.ds.REF_GEOFIREREF_END
}
override func viewWillAppear(animated: Bool){
super.viewWillAppear(animated)
if userdefaultsUid != nil
{
geoFireEnd!.getLocationForKey(userID, withCallback: { (location, error) in
if (error != nil)
{
print("An error occurred getting the location for \(self.userID) : \(error.localizedDescription)")
} else if (location != nil)
{
self.updateUsersWithlocation(location)
} else
{
print("GeoFire does not contain a location for \(self.userID)")
self.updateUsersWithoutLocation()
}
})
}
}
func updateUsersWithlocation(location: CLLocation)
{
var allKeys = [String]()
let locationQuery = self.geoFireEnd!.queryAtLocation(location, withRadius: 100.0)
locationQuery.observeEventType(GFEventType.init(0), withBlock: {(key: String!, location: CLLocation!) in
allKeys.append(key)
self.datesRef.queryOrderedByKey().queryStartingAtValue(String(self.dateRangeStart)).queryEndingAtValue(String(self.dateRangeEnd)).observeEventType(.ChildAdded, withBlock: {
snapshot in
self.users.removeAll(keepCapacity: true)
self.newKeys.removeAll()
self.tableView.reloadData()
for datesKey in snapshot.children
{
self.usersRef.childByAppendingPath(datesKey.key!).observeSingleEventOfType(.Value, withBlock: { snapshot in
if let key = datesKey.key where key != self.userID
{
if allKeys.contains(key!) {
let newuser = FBUser(userKey: key!, dictionary: snapshot.value as! [String : AnyObject])
self.blockedByUsersRef.childByAppendingPath(key).childByAppendingPath(self.userID).observeSingleEventOfType(.Value, withBlock: { (snapshot) -> Void in
if let _ = snapshot.value as? NSNull
{
// we have not blocked this one
self.blockedByUsersRef.childByAppendingPath(self.userID).childByAppendingPath(key).observeSingleEventOfType(.Value, withBlock: { snapshot in
if let _ = snapshot.value as? NSNull
{
// we are not blocked by this one
if self.newKeys.contains(newuser.userKey) {}
else
{
self.users.append(newuser)
self.newKeys.append(newuser.userKey)
}
}
self.tableView.reloadData()
})
}
})
}
}
})
}
})
})
}
In essence users can be at a certain place at a certain date. They put down the date they are going to be there, as explained in code below. that date may overlap with other users that are going to be in that area, in a period ranging of say 7 days before until 21 days after. those users can be followed, blocked. but I’m getting those to display in the tableView. If they put in a different date or place, a different set of users will pop up.
if let userStartDate = beginningDate as? Double
{
let intUserStartDate = Int(userStartDate)
dateRangeStart = intUserStartDate - 604800
dateRangeEnd = intUserStartDate + 1814400
print(dateRangeStart, intUserStartDate, dateRangeEnd)
updateUsers()
}
else
{
updateUsersWithoutDate()
}
This may or may not be an answer or help at all but I want to throw it out there.
Given that you want to really look for two things: locations and times, we need some mechanics to handle it.
The locations are more static; i.e. the bowling ally will always be the bowling ally and the times are dynamic and we need a range. So, given a structure
{
"events" : {
"event_0" : {
"loc_time" : "bowling_5"
},
"event_1" : {
"loc_time" : "tennis_7"
},
"event_2" : {
"loc_time" : "tennis_8"
},
"event_3" : {
"loc_time" : "dinner_9"
}
}
}
This structure handles both criteria. You can easily query for all nodes that have location of tennis at a time of 7. You can also query the range for tennis from start time of 6 and end time of 9, which will return tennis_7 and tennis_8
Here's some ObjC code to do just that
Firebase *ref = [self.myRootRef childByAppendingPath:#"events"];
FQuery *query = [[[ref queryOrderedByChild:#"loc_time"]
queryStartingAtValue:#"tennis_6"] queryEndingAtValue:#"tennis_8"];
[query observeEventType:FEventTypeChildAdded withBlock:^(FDataSnapshot *snapshot) {
NSLog(#"%#", snapshot);
}];
You can extrapolate from this substituting your locations for location and distance or timestamps for the time.
Another modification (and this may be obvious but stating it for clarity) is to use a reference to your locations instead of the actual name (bowling, tennis); i.e.
events
"event_0" : {
"loc_time" : "-JAY8jk12998f_20160311140200" // locationRef_timestamp
},
locations
-JAY8jk12998f : {
"loc_name": "Fernando's Hideaway"
}
Structuring your data in the way to want to get to it can significantly reduce your code (and the complexity of queries within queries etc).
Hope that helps.
I use Alamofire to download big json data which was about around 7MB and use RealmSwift to store data at realmobject and SwiftyJSON to parse.My realm object insertion after it finished download the json seems really slow at insertion.Was something wrong with my bad code?Please guide me.
First of all I will show simplest Json :
{
{
"Start" : "40000",
"End" : "1000000",
"Name" : "Smith",
"Address" : "New York"
},{...},more than 7000 records...
}
Here is my AlamofireAPI Protocol
import Foundation
import Alamofire
import SwiftyJSON
protocol RequestDataAPIProtocol{
func didSuccessDownloadingData(results:JSON,statusCode : Int)
func didFailDownloadingData(err : NSError)
}
class RequestDataAPI{
var delegate : RequestDataAPIProtocol
init(delegate: RequestDataAPIProtocol){
self.delegate=delegate
}
func post(requestURL:String,param:[String:String]){
Alamofire.request(.GET, requestURL, parameters: param)
.validate(statusCode: [200,500])
.responseJSON(completionHandler: { (response: Response<AnyObject, NSError>) -> Void in
if let error = response.result.error {
self.delegate.didFailDownloadingData(error)
} else if let jsonObject: AnyObject = response.result.value {
let json = JSON(jsonObject)
self.delegate.didSuccessDownloadingData(json,statusCode: (response.response?.statusCode)!)
}
})
}
func doRequestSiteData(token : String){
post(REQUEST_DATA,param:["data":"All","token":token])
}
}
Here is my Realm DB Helper
import Foundation
import RealmSwift
class DBHelper{
func insertUserData(list: UserList){
do {
let realm = try! Realm()
try realm.write({ () -> Void in
realm.add(list,update: true)
})
} catch let error as NSError {
print("Insert Error : \(error)")
}
}
}
Here is my realm modelObject
import Foundation
import RealmSwift
class UserList: Object {
dynamic var start : String = ""
dynamic var end : String = ""
dynamic var name : String = ""
dynamic var address : String = ""
}
And Final Code,View Controller,
class ViewController : UIViewController , RequestDataAPIProtocol{
var dbHelper = DBHelper()
var requestDataAPI : RequestDataAPI!
override func viewDidLoad() {
super.viewDidLoad()
requestDataAPI = RequestDataAPI(delegate : self)
}
override func viewDidAppear(animated : Bool){
//assume there is one token to request data
requestDataAPI.doRequestSiteData(token)
}
func didSuccessDownloadingData(results: JSON, statusCode: Int){
dispatch_async(dispatch_get_main_queue(), {
print("Downloaded JSON")
switch statusCode{
case 200 :
if results.count > 0{
if let users = results.array {
for user in users{
let userList=UserList()
userList.start=user["Start”].stringValue
userList.end=user[“End”].stringValue
userList.name=user[“Name”].stringValue
userList.address =user[“Address”].stringValue
self.dbHelper.insertUserData(userList)
}
}
}
// took about more than 7 mins
print(“Insertion Done”)
break
case 500,401,400 :
//TODO:
default : break
}
})
}
}
I know its really stupid about describing all the code steps,I write as simple as i could for my working flow for inserting json data into realm swift.
I just want all to know about my working flow is good or bad when handling so many json data,and also insertion.
The reason why I am asking this was the data insertion took about more than 7 mins to finish.
So,I really need help,to make optimize at my code.
Any guide?
UPDATE : I use Delegate and Protocol from RequestDataAPI which i learn that style from JamesQueue Tutorial because I am completely beginner who is still learning Swift.ViewController is updated.That is my whole process detail,no more code left.Editing my question or answer a new is appreciated for code optimizing.
insertUserData method method opens transactions so many times in the loop. To commit transaction is a little bit expensive operation.
Can you try to put out to open/commit a transaction outside of the loop?
In other words, open the transaction before entering the loop, and commits the transaction once after the end of the loop. Like the following:
if results.count > 0 {
if let users = results.array {
let realm = try! Realm()
try realm.write {
for user in users{
let userList=UserList()
userList.start=user["Start”].stringValue
userList.end=user[“End”].stringValue
userList.name=user[“Name”].stringValue
userList.address =user[“Address”].stringValue
realm.add(userList,update: true)
}
}
}
}
I have fixed slow insertion issue by using this code.
func addAsBatch<T: Object>(_ data: [T]) {
if !isRealmAccessible() { return }
let realm = try! Realm()
realm.refresh()
realm.beginWrite()
for object in data {
realm.add(object)
}
try? realm.commitWrite()
}
Showing function use with your example -
let userList = UserList()
userList.start = user["Start”].stringValue
userList.end = user[“End”].stringValue
userList.name = user[“Name”].stringValue
userList.address = user[“Address”].stringValue
addAsBatch(userList)