I have this function that is grabbing events from firebase that a specific user is attending. Everything seems to be working up until the point where I try to alter an element relative to the event snapshot.
I get the error
Cannot assign to property: 'event' is a 'let' constant
//will show the vents that a user is attending
static func Events(for user: User, completion: #escaping ([Event]) -> Void)
{
var currentEvents = [Event]()
//Getting firebase root directory
let ref = Database.database().reference().child("users").child(user.uid).child("Attending")
ref.observe(.value, with: { (snapshot) in
// print(snapshot)
// guard snapshot.children.allObjects is [DataSnapshot] else {
// return completion([])
// }
guard let eventDictionary = snapshot.value as? [String: Any] else {
return completion([])
}
// print(snapshot)
let dispatchGroup = DispatchGroup()
eventDictionary.forEach({ (key,value) in
// print(key)
// print(value)
EventService.show(forEventKey: key , completion: { (event) in
dispatchGroup.enter()
AttendService.isEventAttended(event, byCurrentUserWithCompletion: { (isAttended) in
//error happens here
event?.isAttending = isAttended
dispatchGroup.leave()
})
currentEvents.append(.init(currentEventKey: key , dictionary: (event?.eventDictionary)!))
completion(currentEvents)
})
})
})
}
It is really confusing to me because I can't see where I declared it as a let constant.
This is the code for the method I am using to gather event info
static func show(forEventKey eventKey: String, completion: #escaping (Event?) -> Void) {
// print(eventKey)
let ref = Database.database().reference().child("events").child(eventKey)
// print(eventKey)
//pull everything
ref.observeSingleEvent(of: .value, andPreviousSiblingKeyWith: { (snapshot,eventKey) in
// print(snapshot.value ?? "")
guard let event = Event(snapshot: snapshot) else {
return completion(nil)
}
completion(event)
})
}
Below is my model for an event object
import Foundation
import FirebaseDatabase.FIRDataSnapshot
struct Event: Keyed {
var key: String?
let currentEventName: String
let currentEventImage: String
let currentEventPromo: String?
let currentEventDescription: String
//nested properties
let currentEventStreetAddress: String
let currentEventCity: String
let currentEventState: String
let currentEventDate: String?
let currentEventTime: String?
let currentEventEndTime: String?
let currentEventZip: Int
var category: String
//nested properties stop
var currentAttendCount: Int
var isAttending = false
var eventDictionary: [String: Any]{
let dateDict = ["start:date":currentEventDate, "start:time": currentEventTime,"end:time":currentEventEndTime]
return ["event:name":currentEventName,"event:imageURL" : currentEventImage,
"event:description": currentEventDescription, "attend:count": currentAttendCount,
"event:street:address": currentEventStreetAddress,"event:zip": currentEventZip,
"event:state": currentEventState, "event:city": currentEventCity, "event:promo": currentEventPromo ?? "", "event:date": dateDict, "event:category":category]
}
init(currentEventKey: String, dictionary: [String:Any]) {
self.key = currentEventKey
self.currentEventName = dictionary["event:name"] as? String ?? ""
self.currentEventImage = dictionary["event:imageURL"] as? String ?? ""
self.currentEventDescription = dictionary["event:description"] as? String ?? ""
self.currentEventPromo = dictionary["event:promo"] as? String ?? ""
self.currentAttendCount = dictionary["attend:count"] as? Int ?? 0
self.category = dictionary["event:category"] as? String ?? ""
//nested properties
self.currentEventStreetAddress = dictionary["event:street:address"] as? String ?? ""
self.currentEventCity = dictionary["event:city"] as? String ?? ""
self.currentEventState = dictionary["event:state"] as? String ?? ""
self.currentEventZip = dictionary["event:zip"] as? Int ?? 0
let eventDate = dictionary["event:date"] as? [String: Any]
self.currentEventDate = eventDate?["start:date"] as? String ?? ""
self.currentEventTime = eventDate?["start:time"] as? String ?? ""
self.currentEventEndTime = eventDate?["end:time"] as? String ?? ""
}
init?(snapshot: DataSnapshot) {
guard let dict = snapshot.value as? [String : Any],
let currentEventName = dict["event:name"] as? String,
let currentEventImage = dict["event:imageURL"] as? String,
let currentEventDescription = dict["event:description"] as? String,
let currentEventPromo = dict["event:promo"] as? String,
let category = dict["event:category"] as? String,
let currentEventStreetAddress = dict["event:street:address"] as? String,
let currentEventCity = dict["event:city"] as? String,
let currentEventState = dict["event:state"] as? String,
let currentEventZip = dict["event:zip"] as? Int,
let currentAttendCount = dict["attend:count"] as? Int,
let eventDate = dict["event:date"] as? [String: Any],
let currentEventDate = eventDate["start:date"] as? String,
let currentEventTime = eventDate["start:time"] as? String,
let currentEventEndTime = eventDate["end:time"] as? String
else { return nil }
self.key = snapshot.key
self.currentEventName = currentEventName
self.currentEventImage = currentEventImage
self.currentEventDescription = currentEventDescription
self.currentEventStreetAddress = currentEventStreetAddress
self.currentEventCity = currentEventCity
self.currentEventState = currentEventState
self.currentEventZip = currentEventZip
self.currentAttendCount = currentAttendCount
self.currentEventPromo = currentEventPromo
self.currentEventDate = currentEventDate
self.currentEventTime = currentEventTime
self.currentEventEndTime = currentEventEndTime
self.category = category
}
}
Event is a struct, so its instances are passed by value. That is, the call completion(event) passes a copy of event to completion.
Since completion doesn't take the Event as an inout, it is treated as a let, not a var, so you cannot modify it.
You could change the type of completion to #escaping (inout Event?) -> Void. Then the Event will be treated as a var, and changes to it will be effectively copied back out to the caller.
However, that won't make a difference in your case, because you're creating a brand new Event instance for the call to completion (by saying guard let event = Event(snapshot: snapshot)). So even if you let completion modify the Event it receives, you just throw it away after. You also need to write it back to your database somehow after completion returns, if you want the change to isAttending to have any effect.
Related
I am trying to fetching conversation from firebase but it's showing failedTofetch data. It is fetching from firebase but in guard let statement it fails.
On successful i get a 2D array but on failure I get dictionary as output.
public func getAllMessageForConversation(with id:String,completion: #escaping (Result<[Message], Error>) -> Void){
print("folder for conversation \(id)/message")
database.child("\(id)/message").observe(.value, with: {snapshot in
guard let value = snapshot.value as? [[String:Any]] else{
completion(.failure(databaseError.failedToFetch))
print(snapshot.value!)
return
}
let messages: [Message] = value.compactMap({dictionary in
guard let name = dictionary["name"] as? String,
//let isread = dictionary["is_read"] as? Bool,
let messageId = dictionary["id"] as? String,
let content = dictionary["content"] as? String,
let senderEmail = dictionary["sender_email"] as? String,
let dateString = dictionary["date"] as? String,
//let type = dictionary["type"] as? String,
let date = ChatViewController.dataFormater.date(from: dateString)else{
return nil
}
let sender = Sender(photoUrl: " ", senderId: senderEmail, displayName: name)
return Message(sender: sender,
messageId: messageId,
sentDate: date,
kind: .text(content))
})
completion(.success(messages))
})
}
}
database structure
I am trying to get an array of users(strings) from a comment, but am having trouble since it is kinda deep in my database, here is the implementation I've tried, it is returning 0 for the count.
REF_FEEDMESSAGES.child(messageKey).child("comments").observeSingleEvent(of: .value) { (commentSnapshot) in
guard let commentSnapshot = commentSnapshot.children.allObjects as? [DataSnapshot] else {return}
for comment in commentSnapshot {
let theComment = comment.value as? [String: Any]
let theContent = theComment?["content"] as? String ?? ""
let theIcon = theComment?["icon"] as? String ?? ""
let theColor = theComment?["color"] as? String ?? ""
let theDate = theComment?["date"] as? String ?? "0"
let theName = theComment?["userName"] as? String ?? ""
let theVerified = theComment?["isVerified"] as? String ?? "no"
let profileImageURL = theComment?["profileImageURL"] as? String ?? ""
let postedBy = theComment?["postedBy"] as? String ?? ""
let likes = theComment?["likes"] as? String ?? ""
let key = comment.key
let likers = comment.childSnapshot(forPath: "likedBy").value as? [String] ?? []
print(likers.count)
guard let likers = comment.childSnapshot(forPath: "likedBy").children.allObjects as? [DataSnapshot] else {return}
for like in likers {
let theLike = like.value as? [String:Any]
print(theLike!["user"] as? String ?? "")
commentLiked.append(theLike!["user"] as? String ?? "")
}
I am manually entering in data into my database and the only variable not getting passed from my database is the author and I do not know where I am going wrong.
func getAllArticles(handler: #escaping (_ articles: [Article])-> ()){
var articleArray = [Article]()
REF_ARTICLES.observeSingleEvent(of: .value) { (articleMessageSnapshot) in
guard let articleMessageSnapshot = articleMessageSnapshot.children.allObjects as? [DataSnapshot] else {return}
for article in articleMessageSnapshot {
let content = article.childSnapshot(forPath: "content").value as? String ?? "no content"
let author = article.childSnapshot(forPath: "author").value as? String ?? "no author"
let twitterHandle = article.childSnapshot(forPath: "twitterHandle").value as? String ?? "none"
let articleTitle = article.childSnapshot(forPath: "articleTitle").value as? String ?? "no title"
let date = article.childSnapshot(forPath: "date").value as? String ?? "no date"
let article = Article(content: content, author: author, twitterHandle: twitterHandle, ArticleTitle: articleTitle, date: date)
articleArray.append(article)
}
handler(articleArray)
}
}
Please check out below code
var articleArray = [Article]()
//REF_ARTICLES
let ref = Database.database().reference().child(“articles”)
ref.observe(.childAdded, with: { (snapshot) in
print(snapshot)
guard let dictionary = snapshot.value as? [String : AnyObject] else {
return
}
let articleObj = Article()
articleObj.Content = dictionary["content"] as? String
articleObj.Author = dictionary["author"] as? String
articleObj.Twitterhandle = dictionary["twitterHandle"] as? String
articleObj.Title = dictionary["articleTitle"] as? String
articleObj.Date = dictionary["date"] as? String
self. articleArray.append(articleObj)
}, withCancel: nil)
}
I am also working on similar app where i am storing data to firebase and retrieving. Below approach i used to fetch the data from firebase database. Please try once.
func getAllArticles(handler: #escaping (_ articles: [Article])-> ()) {
Database.database().reference().child("Articles").observe(.childAdded, with: { (snapshot) in
print("articles = \(snapshot)")
if let dict = snapshot.value as? [String: Any] {
let article = Article()
article.articleTitle = dict["articleTitle"] as? String
article.author = dict["author"] as? String
article.twitterHandle = dict["twitterHandle"] as? String
article.date = dict["date"] as? String
article.content = dict["content"] as? String
self.articleArray.append(article)
}
handler(articleArray)
}, withCancel: nil)
}
im not sure what the underlying issue was, but i fixed it by deleting "author" from the firebase tree and then adding it back
I'm comfortable with initializing objects in general but my method only seems to work when all parameters must exist on the database. I have looked at this which works. But I now don't know how to initialize it with a nest array.
This is how I would normally initialise an object:
class User {
var firstName: String
var lastName: String
var summary: String?
var groups = [Group]()
var ref: DatabaseReference?
var key: String
init?(from snapshot: DataSnapshot) {
let snapshotValue = snapshot.value as? [String: Any]
guard let firstName = snapshotValue?["firstName"] as? String, let lastName = snapshotValue?["lastName"] as? String, let summary = snapshotValue?["summary"] as? String else { return nil }
self.firstName = firstName
self.lastName = lastName
self.summary = summary
self.key = snapshot.key
self.groups(snapshot: snapshot.childSnapshot(forPath: "groups"))
}
func groups(snapshot: DataSnapshot) {
for child in snapshot.children {
guard let group = child as? DataSnapshot else { continue }
}
}
Just don't make the optional properties a part of that guard (that's what prevents the execution to finish it:
init?(from snapshot: DataSnapshot) {
let snapshotValue = snapshot.value as? [String: Any]
guard let firstName = snapshotValue?["firstName"] as? String,
let lastName = snapshotValue?["lastName"] as? String else { return nil }
self.firstName = firstName
self.summary = snapshotValue?["summary"] as? String
self.key = snapshot.key
self.groups(snapshot: snapshot.childSnapshot(forPath: "groups"))
}
Optionally, you can use if let syntax:
if let summary = snapshotValue?["summary"] as? String {
self.summary = summary
}
But in this case this is simpler:
self.summary = snapshotValue?["summary"] as? String
Im attempting to paginate my home feed of pictures as to not load all of my elements in one shot. I have an event model
import Foundation
import FirebaseDatabase.FIRDataSnapshot
struct Event: MGKeyed {
let currentEventKey: String
let currentEventName: String
let currentEventImage: String
let currentEventPromo: String?
let currentEventDescription: String
//nested properties
let currentEventStreetAddress: String
let currentEventCity: String
let currentEventState: String
let currentEventDate: String?
let currentEventTime: String?
let currentEventZip: Int
//nested properties stop
var currentAttendCount: Int
var isAttending = false
var eventDictionary: [String: Any]{
let dateDict = ["start:date":currentEventDate, "start:time": currentEventTime]
return ["event:name":currentEventName,"event:imageURL" : currentEventImage,
"event:description": currentEventDescription, "attend:count": currentAttendCount,
"event:street:address": currentEventStreetAddress,"event:zip": currentEventZip,
"event:state": currentEventState, "event:city": currentEventCity, "event:promo": currentEventPromo ?? "", "event:date": dateDict]
}
init(currentEventKey: String, dictionary: [String:Any]) {
self.currentEventKey = currentEventKey
self.currentEventName = dictionary["event:name"] as? String ?? ""
self.currentEventImage = dictionary["event:imageURL"] as? String ?? ""
self.currentEventDescription = dictionary["event:description"] as? String ?? ""
self.currentEventPromo = dictionary["event:promo"] as? String ?? ""
self.currentAttendCount = dictionary["attend:count"] as? Int ?? 0
//nested properties
self.currentEventStreetAddress = dictionary["event:street:address"] as? String ?? ""
self.currentEventCity = dictionary["event:city"] as? String ?? ""
self.currentEventState = dictionary["event:state"] as? String ?? ""
self.currentEventZip = dictionary["event:zip"] as? Int ?? 0
let eventDate = dictionary["event:date"] as? [String: Any]
self.currentEventDate = eventDate?["start:date"] as? String ?? ""
self.currentEventTime = eventDate?["start:time"] as? String ?? ""
}
init?(snapshot: DataSnapshot) {
guard let dict = snapshot.value as? [String : Any],
let currentEventName = dict["event:name"] as? String,
let currentEventImage = dict["event:imageURL"] as? String,
let currentEventDescription = dict["event:description"] as? String,
let currentEventPromo = dict["event:promo"] as? String,
let currentEventStreetAddress = dict["event:street:address"] as? String,
let currentEventCity = dict["event:city"] as? String,
let currentEventState = dict["event:state"] as? String,
let currentEventZip = dict["event:zip"] as? Int,
let currentAttendCount = dict["attend:count"] as? Int,
let eventDate = dict["event:date"] as? [String: Any],
let currentEventDate = eventDate["start:date"] as? String,
let currentEventTime = eventDate["start:time"] as? String
else { return nil }
self.currentEventKey = snapshot.key
self.currentEventName = currentEventName
self.currentEventImage = currentEventImage
self.currentEventDescription = currentEventDescription
self.currentEventStreetAddress = currentEventStreetAddress
self.currentEventCity = currentEventCity
self.currentEventState = currentEventState
self.currentEventZip = currentEventZip
self.currentAttendCount = currentAttendCount
self.currentEventPromo = currentEventPromo
self.currentEventDate = currentEventDate
self.currentEventTime = currentEventTime
}
}
This Event Wants Me to Conform To A Protocol of MGKeyed which is listed below
import Foundation
import UIKit
protocol MGKeyed {
var key: String? { get set }
}
class MGPaginationHelper <T: MGKeyed> {
let pageSize: UInt
let serviceMethod: (UInt, String?, #escaping (([T]) -> Void)) -> Void
var state: MGPaginationState = .initial
var lastObjectKey: String?
init(pageSize: UInt = 1, serviceMethod: #escaping (UInt, String?, #escaping (([T]) -> Void)) -> Void) {
self.pageSize = pageSize
self.serviceMethod = serviceMethod
}
// 1
func paginate(completion: #escaping ([T]) -> Void) {
// 2
switch state {
// 3
case .initial:
lastObjectKey = nil
fallthrough
// 4
case .ready:
state = .loading
serviceMethod(pageSize, lastObjectKey) { [unowned self] (objects: [T]) in
// 5
defer {
// 6
if let lastObjectKey = objects.last?.key{
self.lastObjectKey = lastObjectKey
}
// 7
self.state = objects.count < Int(self.pageSize) ? .end : .ready
}
// 8
guard let _ = self.lastObjectKey else {
return completion(objects)
}
// 9
let newObjects = Array(objects.dropFirst())
completion(newObjects)
}
// 10
case .loading, .end:
return
}
}
func reloadData(completion: #escaping ([T]) -> Void) {
state = .initial
paginate(completion: completion)
}
}
enum MGPaginationState {
case initial
case ready
case loading
case end
}
This method is responsible for the pagination. My first problem is that the pagination never stops and continues to load the same 1 or 2 elements I have made the assumption that it is because of the conforming of the protocol. How can I conform my event model to that protocol so it is getting a different key and so it knows when to stop?