Error "InvalidFirebaseData" while sending Data to Firebase - ios

*** Terminating app due to uncaught exception 'InvalidFirebaseData', reason: '(setValue:) Cannot store object of type DemoApp.SendMessageInput at . Can only store objects of type NSNumber, NSString, NSDictionary, and NSArray.
This is how I'm trying to send.
Above error does not occur if I use normal dictionary to pass data but I want to do it via Model as one or more time I can simply call that model for input rather than dic os if changes happens in future then I have make changes in one file only.
let messageItem = SendMessageInput(content: text, displayName: currrentUser.name, fileLength: 0, fileUrl: "", fromID: currrentUser.userId, isRead: false, messageStatus: 0, messageType: MessageType.text.rawValue, timestamp: Timestamp, toID: self.toUserID)
itemRef.setValue(messageItem)
SendMessageInput.swift
class SendMessageInput: Reflect{
var content: String = ""
var displayName: String = ""
var fileLength: Int = 0
var fileUrl: String = ""
var fromID: String = ""
var isRead: Bool = false
var messageStatus: Int = 0
var messageType: String = ""
var timestamp: Int = 0
var toID: String = ""
var values:[String: AnyObject]{
let arrayValue: [Any] = [content, displayName, fileLength, fileUrl, fromID, isRead, messageStatus, messageType, timestamp, toID]
var dic: [String: AnyObject] = [:]
for index in 0..<self.getProperties().count{
dic[self.getProperties()[index]] = arrayValue[index] as AnyObject?
}
return dic
}
init(content: String, displayName: String, fileLength: Int, fileUrl: String, fromID: String, isRead: Bool, messageStatus: Int, messageType: String, timestamp: Int, toID: String) {
self.displayName = displayName
self.fileLength = fileLength
self.fileUrl = fileUrl
self.fromID = fromID
self.isRead = isRead
self.messageStatus = messageStatus
self.messageType = messageType
self.timestamp = timestamp
self.toID = toID
}
}
Reflect.swift
class Reflect: NSObject {
func getProperties()->[String] {
let mirror = Mirror(reflecting: self)
var properties = [String]()
for childern in mirror.children {
if childern.label == "super"{continue}
properties.append(childern.label!)
}
return properties
}
func getClassName() -> String {
let mirror = Mirror(reflecting: self)
return String(describing: mirror.subjectType)
}
}

You can't call .setValue() on arbitrary objects.
It looks like you're forgetting to call .values on your object. Try this
itemRef.setValue(messageItem.values)

Related

Unexpectedly found nil while unwrapping... MessageKit

I have a struct set up for Messages and each time I got to load messages in app, I receive an unexpectedly found nil error on this line of code -->
var chatPartnerId: String {
return isFromCurrentUser ? toID! : fromID! // where I get the error
}
I can't figure out what Im doing wrong here at all.
Here's the class setup:
struct Message: MessageType {
let id: String?
var messageId: String {
return id ?? UUID().uuidString
}
var content: String?
var toID: String?
var fromID: String?
var isFromCurrentUser = Bool()
var chatPartnerId: String {
return isFromCurrentUser ? toID! : fromID!
}
let sentDate: Date
let sender: SenderType
var image: UIImage?
var downloadURL: URL?
var kind: MessageKind {
if let image = image {
let mediaItem = ImageMediaItem(image: image)
return .photo(mediaItem)
} else {
return .text(content ?? "")
}
}
init(user: User, content: String, fromID: String, toID: String) {
sender = Sender(senderId: user.uid!, displayName: user.name!)
self.content = content
self.fromID = fromID
self.toID = toID
sentDate = Date()
id = nil
}
init(user: User, image: UIImage) {
sender = Sender(senderId: user.uid!, displayName: user.name!)
self.image = image
content = ""
fromID = ""
toID = ""
sentDate = Date()
id = nil
}
init?(document: QueryDocumentSnapshot) {
let data = document.data()
guard
let sentDate = data["created"] as? Timestamp,
let senderId = data["senderId"] as? String,
let fromID = data["fromID"] as? String,
let toID = data["toID"] as? String,
let senderName = data["senderName"] as? String
else {
return nil
}
id = document.documentID
self.sentDate = sentDate.dateValue()
sender = Sender(senderId: senderId, displayName: senderName)
self.isFromCurrentUser = fromID == Auth.auth().currentUser?.uid
if let content = data["content"] as? String {
self.content = content
downloadURL = nil
} else if let urlString = data["url"] as? String, let url = URL(string: urlString) {
downloadURL = url
content = ""
} else {
return nil
}
}
}
// MARK: - DatabaseRepresentation
extension Message: DatabaseRepresentation {
var representation: [String: Any] {
var rep: [String: Any] = [
"created": sentDate,
"senderId": sender.senderId,
"fromID": fromID,
"toID": toID,
"senderName": sender.displayName
]
if let url = downloadURL {
rep["url"] = url.absoluteString
} else {
rep["content"] = content
}
return rep
}
}
// MARK: - Comparable
extension Message: Comparable {
static func == (lhs: Message, rhs: Message) -> Bool {
return lhs.id == rhs.id
}
static func < (lhs: Message, rhs: Message) -> Bool {
return lhs.sentDate < rhs.sentDate
}
}
Both toID and fromID are optionals and may be nil. Avoid force unwrapping the optional (and actually avoid force unwrapping anything else, with very rare exceptions), like you do in the problematic statement.
Instead, you can:
Don't be afraid to return an optional:
var chatPartnerId: String? { // <-- returns optional
return isFromCurrentUser ? toID : fromID
}
In many cases it's much better to deal with the nil as a condition that helps you understand the state of the app. For example nil may mean you should skip the processing of such message.
You can return a default bogus ID, or an empty string:
var chatPartnerId: String {
guard let id = isFromCurrentUser ? toID : fromID else {
return "" // <-- returns bogus ID
}
return id
}
You can change the property to be required:
var toID: String // <-- not optional
var fromID: String // <-- not optional
Looking at all of your inits I see none of them allows these paramters to be nil. So you don't need to make them optional.

Swift - How to write complex objects to Firebase realtime database

I'm new to the Firebase realtime database and relatively new to Swift in general. I am attempting to build a song request app in which users can create events for guests to request songs from the Spotify API. I'm trying to write an Event object to Firebase, which contains nested objects and arrays of different types. However, when it writes to the database, it only writes the strings and none of the arrays or objects. What is the best way to write all this information to the Firebase Database in a nested structure, so that whenever users add song requests, I can edit the array of requests for the given event in firebase.
Here is my code:
Event.swift
struct Event: Codable{
var code: String
var name: String
var host: String
var description: String
var hostUserId: String
var guestIds: [String]
var requests: [Request]
var queue: [Request]
var played: [Request]
//private var allowExplicit: Bool
//private var eventLocation
init(code: String, name: String, host: String, description: String, hostUserId: String){
self.code = code
self.name = name
self.host = host
self.description = description
self.hostUserId = hostUserId
self.guestIds = []
self.requests = []
self.queue = []
self.played = []
}
func toAnyObject() -> Any{
var guestIdsDict: [String:String] = [:]
for id in guestIds{
guestIdsDict[id] = id
}
var requestsDict: [String: Any] = [:]
for request in requests{
requestsDict[request.getId()] = request.toAnyObject()
}
var queueDict: [String: Any] = [:]
for request in queue{
queueDict[request.getId()] = request.toAnyObject()
}
var playedDict: [String: Any] = [:]
for request in played{
playedDict[request.getId()] = request.toAnyObject()
}
return [
"code": code,
"name": name,
"host": host,
"description": description,
"hostUserId": hostUserId,
"guestIds": guestIdsDict,
"requests": requestsDict,
"queue":queueDict,
"played":playedDict
]
}
}
Request.swift
struct Request: Codable{
private var name: String
private var id: String
private var explicit: Bool
private var album: Album
private var artists: [Artist]
private var likes: Int
init(name: String, id: String, explicit: Bool, album: Album, artists: [Artist]){
self.name = name
self.id = id
self.explicit = explicit
self.album = album
self.artists = artists
self.likes = 1
}
func toAnyObject() -> Any{
var artistsDict: [String:Any] = [:]
for artist in artists {
artistsDict[artist.id] = artist.toAnyObject()
}
return [
"name": name,
"id": id,
"explicit": explicit,
"album": album.toAnyObject(),
"artists": artistsDict,
"likes": likes
]
}
mutating func like(){
self.likes += 1
}
mutating func unlike(){
self.likes -= 1
if(self.likes < 0){
self.likes = 0
}
}
mutating func setLikes(count: Int){
self.likes = count
}
func getLikes() -> Int{
return self.likes
}
func getName() -> String{
return self.name
}
func getId() -> String{
return self.id
}
func getExplicit() -> Bool{
return self.explicit
}
func getAlbum() -> Album {
return self.album
}
func getImages() -> [Image] {
return self.album.images
}
func getArtists() -> [Artist] {
return self.artists
}
func getArtistString() -> String{
var artistString = ""
for (i, artist) in self.artists.enumerated(){
artistString += artist.name
if(i != self.artists.endIndex-1){
artistString += ", "
}
}
return artistString
}
}
Album.swift
struct Album: Codable{
let name: String
let images: [Image]
func toAnyObject() -> Any{
var imagesDict: [String: Any] = [:]
for image in images{
imagesDict[image.url] = image.toAnyObject()
}
return [
"name": name,
"images": imagesDict
]
}
}
Artist.swift
struct Artist: Codable{
let id: String
let name: String
func toAnyObject() -> Any{
return ["id": id, "name": name]
}
}
Image.swift
struct Image: Codable{
let height: Int
let url: String
let width: Int
func toAnyObject() -> Any{
return ["height": height, "url": url, "width": width]
}
}
As you are using Codable, you can create a dic out of it as follows:
Step 1: Add this extension to your code
extension Encodable {
var dictionary: [String: Any]? {
guard let data = try? JSONEncoder().encode(self) else { return nil }
return (try? JSONSerialization.jsonObject(with: data, options: .allowFragments)).flatMap { $0 as? [String: Any] }
}
}
Step 2: Write below code in your Struct (this you have to do in every struct or you can modify code as per your need).
func createDic() -> [String: Any]? {
guard let dic = self.dictionary else {
return nil
}
return dic
}
Now with the help of struct obj, call createDic() method and you will get a dictionary.
And you can send this dictionary to the firebase.
FULL CODE EXAMPLE:
extension Encodable {
var dictionary: [String: Any]? {
guard let data = try? JSONEncoder().encode(self) else { return nil }
}
struct LoginModel: Codable {
let email: String
let password: String
func createDic() -> [String: Any]? {
guard let dic = self.dictionary else {
return nil
}
return dic
}
}
Please comment if you have any questions.
Happy to help!

Type 'MenuItem' does not conform to protocol 'Decodable'

Why am I getting the error: Type 'MenuItem' does not conform to protocol 'Decodable'? It was working before. One of the things I changed was modifiers from [String]? to [Modifier]? Is that producing the error? If so, why? Stackoverflow wants me to keep saying things because it's too little description relative to the amount of code that I displayed below. Still needing to add text.
struct MenuItem: Codable {
let itemId: String
let name: String
var modifiers: [Modifier]?
var photoUrl: String?
var quantity: Int
var unitPrice: Int
var sizeAddOnPrice: Int
var toppingsAddOnPrice: Int
var totalPrice: Int
var totalModifiersPrice: Int
let description: String
var size: String
var toppings: [String]?
let category: String
init(itemId: String, name: String, modifiers: [Modifier]?, photoUrl: String?, quantity: Int, unitPrice: Int, sizeAddOnPrice: Int, toppingsAddOnPrice: Int, totalPrice: Int, totalModifiersPrice: Int, description: String, size: String, toppings: [String]?, category: String) {
self.itemId = itemId
self.name = name
self.modifiers = modifiers
self.photoUrl = photoUrl
self.quantity = quantity
self.unitPrice = unitPrice
self.sizeAddOnPrice = sizeAddOnPrice
self.toppingsAddOnPrice = toppingsAddOnPrice
self.totalPrice = totalPrice
self.totalModifiersPrice = totalModifiersPrice
self.description = description
self.size = size
self.toppings = toppings
self.category = category
}
init?(itemId: String, payload: JSON) {
guard
let name = payload[ParamKey.name].string,
let photoUrl = payload[ParamKey.photoUrl].string,
let description = payload[ParamKey.description].string,
let category = payload[ParamKey.categoryName].string,
let unitPrice = payload[ParamKey.basePrice].int,
let size = payload[ParamKey.size].string
else { return nil }
self.itemId = itemId
self.name = name
self.photoUrl = photoUrl
self.description = description
self.category = category
self.unitPrice = unitPrice
self.size = size
self.sizeAddOnPrice = 0
self.toppings = nil
self.toppingsAddOnPrice = 0
self.totalPrice = (unitPrice + sizeAddOnPrice + toppingsAddOnPrice) * quantity
self.totalModifiersPrice = (sizeAddOnPrice + toppingsAddOnPrice) * 2
self.quantity = 1
self.modifiers = payload[ParamKey.modifiers].arrayObject as? [Modifier]
}
}
You need to make the inner structs conform to Decodable/Codable also and remove struct init it'll be automatically generated
struct Modifier: Codable {
// add properties
}
struct MenuItem: Codable {
let itemId: String
let name: String
var modifiers: [Modifier]?
var photoUrl: String?
var quantity: Int
var unitPrice: Int
var sizeAddOnPrice: Int
var toppingsAddOnPrice: Int
var totalPrice: Int
var totalModifiersPrice: Int
let description: String
var size: String
var toppings: [String]?
let category: String
init?(itemId: String, payload: JSON) {
guard
let name = payload[ParamKey.name].string,
let photoUrl = payload[ParamKey.photoUrl].string,
let description = payload[ParamKey.description].string,
let category = payload[ParamKey.categoryName].string,
let unitPrice = payload[ParamKey.basePrice].int,
let size = payload[ParamKey.size].string
else { return nil }
self.itemId = itemId
self.name = name
self.photoUrl = photoUrl
self.description = description
self.category = category
self.unitPrice = unitPrice
self.size = size
self.sizeAddOnPrice = 0
self.toppings = nil
self.toppingsAddOnPrice = 0
self.totalPrice = (unitPrice + sizeAddOnPrice + toppingsAddOnPrice) * quantity
self.totalModifiersPrice = (sizeAddOnPrice + toppingsAddOnPrice) * 2
self.quantity = 1
self.modifiers = payload[ParamKey.modifiers].arrayObject as? [Modifier]
}
}
You can also use
try content.rawData() // for payload: JSON) {
to JSON to data and then supply it to the Decodable class

How to store an array of objects in Firestore

I can't find online how to store an array of objects so that the key "line_items" presents numbers for each menuItem with values for each menuItem corresponding to its own number. In other words, I need the numbers to come after line_items rather than the nested key so that each individual MenuItem object can be quickly referenced. I found online how to make it so each key has an array of values, but I need line_items to have an array of MenuItem objects. The following code crashes:
public func uploadTransactionData(_ menuItems: [MenuItem], balanceId: String, subTotal: Int, completion: #escaping (() -> ())) {
guard let userId = Auth.auth().currentUser?.uid else { completion(); return }
let utilitiesManager = UtilitiesManager()
let timestamp = utilitiesManager.timestamp()
let params: [String: Any] = ["date": "\(timestamp)",
"balance_id": "\(balanceId)",
"subtotal": "\(subTotal)",
"user_id": "\(userId)",
"line_items": menuItems
]
Firestore.firestore().document("transaction_history/\(timestamp)").setData(params)
{ err in
if let e = err {
print("$-- error creating user \(e)")
completion()
} else {
completion()
}
}
}
Here's the MenuItem model:
struct MenuItem {
let itemId: String
let name: String
var modifiers: [String]?
var photoName: String?
var photoUrl: String?
var quantity: Int
var price: Int
var sizeAddOnPrice: Int
var toppingsAddOnPrice: Int
let description: String
var size: String
let category: String
init(itemId: String, name: String, modifiers: [String]?, photoName: String?, photoUrl: String?, quantity: Int, price: Int, sizeAddOnPrice: Int, toppingsAddOnPrice: Int, description: String, size: String, category: String) {
self.itemId = itemId
self.name = name
self.modifiers = modifiers
self.photoName = photoName
self.photoUrl = photoUrl
self.quantity = quantity
self.price = price
self.sizeAddOnPrice = sizeAddOnPrice
self.toppingsAddOnPrice = toppingsAddOnPrice
self.description = description
self.size = size
self.category = category
}
Problem:
Your app is crashing because you are trying to save user defined object MenuItem to Firestore. Firestore doesn't allow it. Firestore only supports this datatypes.
Solution:
You can convert your custom object MenuItem to Firestore supported datatypes.
You can do this by making following changes to your code.
Make MenuItem confirm to Codable protocol.
struct MenuItem: Codable {
// Your code as it is.
}
Make following changes to your uploadTransactionData() function:
public func uploadTransactionData(_ menuItems: [MenuItem], balanceId: String, subTotal: Int, completion: #escaping (() -> ())) {
let userId = Auth.auth().currentUser?.uid else { completion(); return }
let utilitiesManager = UtilitiesManager()
let timestamp = utilitiesManager.timestamp()
var list_menuItem = [Any]()
for item in menuItems {
do {
let jsonData = try JSONEncoder().encode(item)
let jsonObject = try JSONSerialization.jsonObject(with: jsonData, options: [])
list_menuItem.append(jsonObject)
}
catch {
// handle error
}
}
let params: [String: Any] = ["date": "\(timestamp)",
"balance_id": "\(balanceId)",
"subtotal": "\(subTotal)",
"user_id": "\(userId)",
"line_items": list_menuItem
]
Firestore.firestore().document("transaction_history/\(timestamp)").setData(params)
{ err in
if let e = err {
print("$-- error creating user \(e)")
completion()
} else {
completion()
}
}
}
This is because Firestore doesn't know how to save the value: menuItems
you can map it like this: "objectExample": [
"a": 5,
"b": [
"nested": "foo"
]
]
or:
.setData([
"name": "Frank",
"favorites": [ "food": "Pizza", "color": "Blue", "subject": "recess" ],
"age": 12
])

How to handle array in model class?

How can make model class for this json data
{
total: 41,
totalPages: 4,
valueData: [
{
id: "23",
lastLogin: "0-Jul-2011 11:27:36 AM",
name: "varii"
},
{
id: "24",
lastLogin: "0-Jul-2015 11:27:36 AM",
name: "sarii"
},
{
id: "25",
lastLogin: "0-Jul-2018 11:27:36 AM",
name: "narii"
} ]
}
class OnResponse {
var total: Int?
var totalPages: Int?
init(response: [String: Any]) {
self.total = response["total"]
self.totalPages = response["totalPages"]
}
}
It's not working how can make it ready for work
and how to pass values to controller to model and how to get value from model
Follow the below class structure
class Response {
var total: Int?
var totalPages: Int?
var valueData: [LoginData]?
init(response: [String: Any]) {
self.total = response["total"]
self.totalPages = response["totalPages"]
var items:[LoginData] = ()
for (data in response["valueData"]) {
let login = LoginData(name: data["name"], lastLogin: data["lastLogin"])
items.append(login)
}
self.valueData = items
}
}
class LoginData {
var name: String?
var lastLogin: String?
init(name: String, lastLogin: String) {
self.name = name
self.lastLogin = lastLogin
}
}
you should use "reverseMatches" to retrieve the array, not the "data". May be you can use a third library to convert your json data to a model, such as Unbox, https://github.com/JohnSundell/Unbox.
Model for Your response :
struct ModelName {
var total: Int?
var totalPage: Int?
var reverseMatches: [LoginDetails]?
}
struct LoginDetails {
var id: String?
var lastLogin: String?
var name: String?
}
Parse the api response and assign the values on the appropriate fields. I have made, all the variables are optional.
Assign values like below.
var obj = Model()
obj.total = response["total"] as? Int
obj should be var, because you are going to mutate the struct values. because struct is value based, not reference based
class DataModel{
var total : Int?
var totalPages : Int?
var valueData : [UserModel]?
init(JSON: [String:Any?]){
self = parser.doParse(JSON: JSON)
}
}
class UserModel{
var id : String?
var lastLogin : String?
var name : String?
}
class parser : NSObject{
class func doParse(JSON: [String:Any?])->DataModel{
let dataModel = DataModel()
dataModel.total = JSON["total"] as? Int
dataModel.totalPages = JSON["totalPages"] as? Int
var userInfo : [UserModel] = []
let valueData : [String:String?]? = JSON["valueData"] as? [String:String?]
if let valueData = valueData{
for dataDict : [String:String?] in valueData{
let itemModel = UserModel()
itemModel.id = dataDict["id"] as? String
itemModel.lastLogin = dataDict["lastLogin"] as? String
itemModel.name = dataDict["name"] as? String
userInfo.append(itemModel)
}
dataModel.valueData = userInfo
}
return dataModel
}
}
class LoginData: NSObject {
let total: Int = 0
let totalPages: Int = 0
let valueData: Array<ValueData> = Array<ValueData>()
override init(total: Int!, totalPages: Int, valueData: Array<ValueData>!) {
self.total = total
self.totalPages = totalPages
self.valueData = valueData
}
}
struct ValueData {
let id: int?
let lastLogin: String?
let name: String?
init(id: int!, lastLogin: string, name: string!)
self.id = id ?? 0
self.lastLogin = lastLogin ?? ""
self.name = name ?? ""
}
}
you should use struct instead of class for creating model object...
advantages of struct over class refer
Why Choose Struct Over Class?
class/24232845
use two struct for holding your data one is for your single total count
and other is for last login detail
struct lastLogin {
let totalCount: (total: Int, totalPages: Int)
let valueArray: [lastLoginDetail]
}
struct lastLoginDetail {
let valueData: (id: String, lastLogin: String,name: String)
}
extension lastLoginDetail {
init(json: [String : String]) throws {
let id = json["id"]
let lastLogin = json["lastLogin"]
let name = json["name"]
let value = (id,lastLogin,name)
self.valueData = value as! (id: String, lastLogin: String, name: String)
}
}
extension lastLogin {
init(json: [String : Any]) throws {
let total = (json["total"] as! NSNumber).intValue
let totalPages = (json["totalPages"] as! NSNumber).intValue
let totalCounts = (total,totalPages)
var userInfo : [lastLoginDetail] = []
// Extract and validate valueData
let valueData = json["valueData"] as? NSArray
if let valueData = valueData{
for dataDict in valueData{
let dic : [String : String] = dataDict as! [String : String]
let lastLoginDeatails = try! lastLoginDetail(json: dic)
userInfo.append(lastLoginDeatails)
}
}
self.valueArray = userInfo
self.totalCount = totalCounts
}
}
func HowToUseModelClass(){
let jsonDic = NSDictionary()
// jsonDic // your json value
let dataValue = try! lastLogin(json: (jsonDic as! [String : Any])) // parsing the data
print(dataValue.totalCount.total)
print(dataValue.totalCount.totalPages)
print(dataValue.valueArray[0].valueData.id)
}

Resources