Trouble with embeddd Codable objects and Firestore in SwiftUI - swift

I have the following
class OrderBasket: Identifiable, Codable {
#DocumentID var id: String!
var credits: Int! = 200
var basketTotal: Int
var orderLines: [OrderLine] = []
enum CodingKeys: String, CodingKey {
case id
case credits
case basketTotal
case orderLines
}
}
and
class OrderLine: Identifiable, Equatable, Codable {
#DocumentID var id: String! = UUID ().uuidString
var sku : String!
var quantity : Int
var lineTotal: Int
enum CodingKeys: String, CodingKey {
case id
case sku
case quantity
case lineTotal
}
}
I am able to successfully read the data from Firestore. However, how do I extract the orderLines?
The following code works to read the OrderBasket. How do I get the orderLines?
db.collection("Basket").document(Auth.auth().currentUser!.uid).addSnapshotListener { (querySnapshot, error) in
guard let basket = querySnapshot?.data() else {
print("No documents for basket")
return
}
self.orderBasket.credits = (basket["credits"] as! Int)
self.orderBasket.basketTotal = basket["basketTotal"] as! Int
}
basket["orderLines"] contains the data but how do I get it to the object?
I am using FirebaseFirestoreSwift BTW.

Related

How can i get a specific element of a collection with specific id in SwiftUI?

I have a function where i pass a collection (reversed) of Messages with an ID. How can i get the specific message corresponding to this ID?
My Struct :
import Foundation
import FirebaseFirestoreSwift
struct Message_M: Codable, Identifiable, Hashable {
#DocumentID var id: String?
var msg: String
var dateCreated: Date
var userFrom: String
...
enum CodingKeys: String, CodingKey {
case id
case msg
case dateCreated
case userFrom
...
}
}
I want to do something like this... messages(#DocumentID: ID).userFrom is not working here and i don't know how to get the correct syntax.
func checkRead(from: String, messages: ReversedCollection<[Message_M]>, ID: String) {
if currentUserID != messages(#DocumentID: ID).userFrom {
// Do something here...
}
}
Here is a way for you:
func checkRead(from: String, messages: ReversedCollection<[Message_M]>, ID: String) {
if let message: Message_M = messages.first(where: { element in element.id == ID }) {
print(message.msg)
}
}

Swift ui codable struct with empty fields

I have the following structs:
struct ResponseToken: Identifiable, Codable, Hashable {
var id: UUID { return UUID() }
let access_token: String
enum CodingKeys: String, CodingKey {
case access_token
}
}
struct ResponseUserInfo: Identifiable, Codable, Hashable {
var id: UUID { return UUID() }
let login, avatar_url, html_url, created_at, updated_at: String
let public_repos, public_gists, followers, following: Int
enum CodingKeys: String, CodingKey {
case login, avatar_url, html_url, public_repos, public_gists, followers, following, created_at, updated_at
}
}
I would like to avoid doing this every time to declare empty objs:
var token: ResponseToken = ResponseToken(access_token: "")
var userInfo: ResponseUserInfo =
ResponseUserInfo(login: "", avatar_url: "", html_url: "", created_at: "", updated_at: "", public_repos: 0, public_gists: 0, followers: 0, following: 0)
The result I would like to have is something like this:
var token: ResponseToken = ResponseToken()
var userInfo: ResponseUserInfo = ResponseUserInfo()
Can you give me a hand?
A possible solution is a static property which creates an empty struct for example
struct ResponseToken: Identifiable, Codable, Hashable {
let id = UUID()
let accessToken: String
enum CodingKeys: String, CodingKey {
case accessToken = "access_token"
}
static let empty = ResponseToken(accessToken: "")
}
and use it
let token = ResponseToken.empty
Notes:
The computed property to return an UUID is pointless, declare a constant.
If you are specifying CodingKeys anyway, use them also to convert the snake_case keys.

How to declare different structs inside one array in Swift

Is it possible to create an array of different structs?
My data structure looks like this:
enum MovementType: String, Codable {
case WeightMovement
case RepsMovement
}
struct Movement: Identifiable, Codable {
let id: UUID = UUID()
let name: String
let type: MovementType
let workouts: [WeightMovement, RepsMovement] ---> A solution for this line based on the Type above
}
struct WeightMovement: Identifiable, Codable {
let id: UUID = UUID()
let weight: Double
let sets: Int
let reps: Int
}
struct RepsMovement: Identifiable, Codable {
let id: UUID = UUID()
let sets: Int
let reps: Int
let seconds: Int
}
A brief example of what is should do:
A user can create multiple movements with a name and movementType. A user can add workouts to a movement but since each movementType holds different data i create different structs for that.
ideally the array will always hold only one type of Movement based on the type of the movement.
The easiest method would be to declare the properties whose presence is uncertain as optional and create a common struct that combines both of the optional properties. Since we already have a MovementType that would provide certainty about the availability of a particular property we could probably force-unwrap, although safe-unwrap is safer at any point.
struct Movement: Identifiable, Codable {
var id: UUID = UUID()
let name: String
let type: MovementType
let workouts: [MovementDetail]
}
struct MovementDetail: Identifiable, Codable {
var id: UUID = UUID()
let weight: Double?
let sets: Int
let reps: Int
let seconds: Int?
}
enum MovementType: String, Codable {
case WeightMovement
case RepsMovement
}
I would do it this way:
struct Movement: Identifiable, Codable {
var id: UUID = UUID()
var name: String
var type: MovementType
var weightWorkouts: [WeightMovement]
var repsWorkouts: [RepsMovement]
var workouts: [Codable] {
switch type {
case .WeightMovement:
return weightWorkouts
case .RepsMovement:
return repsWorkouts
}
}
}
This doesn't do exactly what you describe, as there are multiple array types on the struct, but for the object's consumer, you have access to a single property workouts that will return one of multiple possible types.
As Tushar points out in his comment, though, it is fragile to specify what the return type is in two different places (type and the actual type returned in the array). A subclass or protocol would probably be better.
ideally the array will always hold only one type of Movement based on the type of the movement.
In this case I recommend to decode the JSON manually and declare the type enum with associated types
enum MovementType {
case weight([WeightMovement])
case reps([RepsMovement])
}
struct Movement: Identifiable, Decodable {
private enum CodingKeys : String, CodingKey { case name, type, workouts }
let id: UUID = UUID()
let name: String
let type: MovementType
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.name = try container.decode(String.self, forKey: .name)
let movementType = try container.decode(String.self, forKey: .type)
switch movementType {
case "WeightMovement":
let workouts = try container.decode([WeightMovement].self, forKey: .workouts)
type = .weight(workouts)
case "RepsMovement":
let workouts = try container.decode([RepsMovement].self, forKey: .workouts)
type = .reps(workouts)
default: throw DecodingError.dataCorruptedError(forKey: .type, in: container, debugDescription: "Invalid movement type")
}
}
}
struct WeightMovement: Identifiable, Decodable {
let id: UUID = UUID()
let weight: Double
let sets: Int
let reps: Int
}
struct RepsMovement: Identifiable, Decodable {
let id: UUID = UUID()
let sets: Int
let reps: Int
let seconds: Int
}
The explicit UUID assignment implies that id is not going to be decoded.

Getting an array of objects using FirebaseFirestoreSwift

I have a struct that looks like this:
struct Joint: Identifiable, Codable {
#DocumentID var id : String? = UUID().uuidString
var serviceType : Int
var businessName : String
var keywords: [String]
var menu : [DishItems]
enum CodingKeys : String, CodingKey {
case businessName
case serviceType = "jointType"
case keywords
case menu
}
}
struct DishItems: Codable {
var listOrder : Int
var menuItemName : String
var menuItemDescription : String
var menuItemPrice : Double
}
The DishItems is a custom object and I am using FirebaseFireStoreSwift JSON decoder to retrieve the documents.
let db = Firestore.firestore().collection("Joints")
db.getDocuments { (querySnapshot, error) in
guard let documents = querySnapshot?.documents else {
print("No restaurants")
return
}
let jointsArray : [Joint] = documents.compactMap {
return try? $0.data(as: Joint.self)
}
completion(jointsArray)
}
I am able to query all of the documents if I exclude "menu" from the parent struct but as soon as I include it, then nothing is queried. How do I go about obtaining the array of objects inside the JSON?

Exclude CodingKeys that doesn't need to be altered?

Say I have a struct User model which has many properties in it.
struct User: Codable {
let firstName: String
let lastName: String
// many more properties...
}
As you can see above it conforms to Codable. Imagine if the lastName property is should be encoded/decoded as secondName and I would like to keep it as lastName at my end, I need to add the CodingKeys to the User model.
struct User: Codable {
//...
private enum CodingKeys: String, CodingKey {
case firstName
case lastName = "secondName"
// all the other cases...
}
}
Is there any possible way to avoid including all the cases in CodingKeys that have the same value as rawValue like the firstName in the above example (Feels redundant)? I know if I avoid the cases in CodingKeys it won't be included while decoding/encoding. But, is there a way I could override this behaviour?
There is a codable way, but the benefit is questionable.
Create a generic CodingKey
struct AnyKey: CodingKey {
var stringValue: String
var intValue: Int?
init?(stringValue: String) { self.stringValue = stringValue; self.intValue = nil }
init?(intValue: Int) { self.stringValue = String(intValue); self.intValue = intValue }
}
and add a custom keyDecodingStrategy
struct User: Codable {
let firstName: String
let lastName: String
let age : Int
}
let jsonString = """
{"firstName":"John", "secondName":"Doe", "age": 30}
"""
let data = Data(jsonString.utf8)
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .custom({ keyPath -> CodingKey in
let key = keyPath.last!
return key.stringValue == "secondName" ? AnyKey(stringValue:"lastName")! : key
})
let result = try decoder.decode(User.self, from: data)
print(result)
} catch {
print(error)
}
There is not such a feature at this time. But you can take advantage of using computed properties and make the original one private.
struct User: Codable {
var firstName: String
private var secondName: String
var lastName: String {
get { secondName }
set { secondName = newValue }
}
}
So no need to manual implementing of CodingKeys at all and it acts exactly like the way you like. Take a look at their counterparts: