firebase firestore not accepting my write function from swift encoded json - swift

this is my struct
struct CustomerDetail: Codable {
var customerID: String = ""
var firstname: String = ""
var lastname: String = ""
var age: Int = 0
var birthday: Int = 0
var country: String = ""
var pound: Bool = true
var feetboolean: Bool = true
var currentweight: Int = 0
var desiredweight: Int = 0
var sex: Bool = false
var feet: Int = 0
var inches: Int = 0
var cm: Float = 0.0
}
this is my write function
struct writetofirebase {
var customerdetails: CustomerDetail = CustomerDetail()
let db = Firestore.firestore()
var delegate: writefirebase?
func write(){
print(customerdetails)
do {
if let data = try? JSONEncoder().encode(customerdetails){
try db.collection("collection name").document("document name").setData(from: data)
delegate?.didSuccessfulWrite(true)
}else{
print("failed to encode")
}
} catch let error {
delegate?.didFailWithError(error)
}
}
}
this is my error
https://i.stack.imgur.com/cOpQK.png
this is my firebase structured data
https://i.stack.imgur.com/cOpQK.png

The error is saying that the data it is receiving inside the setData method is not compatible and therefore malformed for the request. You should be updating or setting the data appropriately by defining a custom dictionary or passing the object directly.
db.collection("collection name").document("document name").setData(data, merge: true)
Source: https://firebase.google.com/docs/firestore/manage-data/add-data#set_a_document

Related

passing a struct type as a parameter throwing initialization error

I have the following code which has 3 objects which are then part of a 4th object. I'm getting errors trying to create the init method for the aggregate object (GTFS) because I'm passing (or trying to pass) the type of the 3 component objects (Stop, Route, Trip). I'm not sure why those have to be initialized before just their types being used.
protocol GTFSObject {
static var fileName: String { get }
init(csvRow: [String: String])
}
struct Stop: GTFSObject {
static let fileName = "stops.txt"
let stopID: String
let stopCode: String
let stopName: String
let stopDesc: String
let stopLat: Double
let stopLon: Double
let locationType: Int
let parentStation: String
init(csvRow: [String: String]) {
self.stopID = csvRow["stop_id"]!
self.stopCode = csvRow["stop_code"]!
self.stopName = csvRow["stop_name"]!
self.stopDesc = csvRow["stop_desc"]!
self.stopLat = Double(csvRow["stop_lat"]!)!
self.stopLon = Double(csvRow["stop_lon"]!)!
self.locationType = Int(csvRow["location_type"]!)!
self.parentStation = csvRow["parent_station"]!
}
}
struct Trip: GTFSObject {
static let fileName = "trips.txt"
let routeID: String
let serviceID: String
let tripID: String
init(csvRow: [String: String]) {
tripID = csvRow["trip_id"] ?? ""
routeID = csvRow["route_id"] ?? ""
serviceID = csvRow["service_id"] ?? ""
}
}
struct Route: GTFSObject {
static let fileName = "trips.txt"
let routeID: String
let agencyID: String
let routeShortName: String
let routeLongName: String
let routeDesc: String
let routeType: Int
let routeURL: String
let routeColor: String
let routeTextColor: String
init(csvRow: [String: String]) {
routeID = csvRow["route_id"] ?? ""
agencyID = csvRow["agency_id"] ?? ""
routeShortName = csvRow["route_short_name"] ?? ""
routeLongName = csvRow["route_long_name"] ?? ""
routeDesc = csvRow["route_desc"] ?? ""
routeType = Int(csvRow["route_type"] ?? "0") ?? 0
routeURL = csvRow["route_url"] ?? ""
routeColor = csvRow["route_color"] ?? ""
routeTextColor = csvRow["route_text_color"] ?? ""
}
}
class GTFS {
let routes: [Route]
let stops: [Stop]
let trips: [Trip]
init(gtfsFolderUrl: URL) {
self.stops = init_struct_from_url(gtfsFolderUrl: gtfsFolderUrl, type: Stop.self)
self.trips = init_struct_from_url(gtfsFolderUrl: gtfsFolderUrl, type: Trip.self)
self.routes = init_struct_from_url(gtfsFolderUrl: gtfsFolderUrl, type: Route.self)
}
private func init_struct_from_url<GTFSType>(gtfsFolderUrl: URL, type: GTFSType.Type) -> [GTFSType] where GTFSType : GTFSObject{
var returnList: [GTFSType] = []
let rows = try! NamedCSV(url: GTFS_FOLDER_URL.appendingPathComponent(type.fileName), delimiter: CSVDelimiter.comma, loadColumns: false).rows
for row in rows {
returnList.append(type.init(csvRow: row))
}
return returnList
}
}
The error I get is
'self' used in method call 'init_struct_from_url' before all stored properties are initialized
I don't know why I have to initialize the properties of the struct just to pass the type of the struct to this other function. What am I missing?
It's not about the three types that you are passing to init_struct_from_url. It's about the call init_struct_from_url itself.
You are actually calling:
self.init_struct_from_url(...)
^^^^^
The error is saying that that use of self is not allowed, because self is not initialised. If init_struct_from_url uses a property in self, it could see an uninitialised value.
Since init_struct_from_url doesn't actually use self at all and is just a helper function, you can make it an inner function of init:
init(gtfsFolderUrl: URL) {
func structFromUrl<GTFSType>(gtfsFolderUrl: URL, type: GTFSType.Type) -> [GTFSType] where GTFSType : GTFSObject{
var returnList: [GTFSType] = []
let rows = try! NamedCSV(url: GTFS_FOLDER_URL.appendingPathComponent(type.fileName), delimiter: CSVDelimiter.comma, loadColumns: false).rows
for row in rows {
returnList.append(type.init(csvRow: row))
}
return returnList
}
self.stops = structFromUrl(gtfsFolderUrl: gtfsFolderUrl, type: Stop.self)
self.trips = structFromUrl(gtfsFolderUrl: gtfsFolderUrl, type: Trip.self)
self.routes = structFromUrl(gtfsFolderUrl: gtfsFolderUrl, type: Route.self)
}

Error 'Type of expression is ambiguous without more context' when fetching from Firebase

Trying to fetch data from the currently signed-in user, I have gotten it to work without involving users but I am implementing a profile system. I am getting the error 'Type of expression is ambiguous without more context', on the line:
guard let documents = snap?.documents else {
let db = Firestore.firestore()
guard let uid = FirebaseManager.shared.auth.currentUser?.uid else { return }
db.document(uid).addSnapshotListener { (snap, err) in
guard let documents = snap?.documents else {
print("No workouts")
return
}
self.userRoutines = documents.compactMap({ q -> UserRoutine? in
return try? q.data(as: UserRoutine.self)
})
}
I made sure the data model conformed to Identifiable:
struct UserRoutine: Identifiable, Codable {
#DocumentID var id: String? = UUID().uuidString
var uid: String
var email: String
var routineName: String
var numberOfWorkouts: Int
var workoutName: String
var sets: Int
var reps: Int
var weight: String
var workoutName2: String
var sets2: Int
var reps2: Int
var weight2: String
var workoutName3: String
var sets3: Int
var reps3: Int
var weight3: String
var workoutName4: String
var sets4: Int
var reps4: Int
var weight4: String
var workoutName5: String
var sets5: Int
var reps5: Int
var weight5: String
var workoutName6: String
var sets6: Int
var reps6: Int
var weight6: String
var workoutName7: String
var sets7: Int
var reps7: Int
var weight7: String
var workoutName8: String
var sets8: Int
var reps8: Int
var weight8: String
var workoutName9: String
var sets9: Int
var reps9: Int
var weight9: String
var workoutName10: String
var sets10: Int
var reps10: Int
var weight10: String
}
We've recently added a method to specify the type when mapping documents:
private func fetchBook(documentId: String) {
let docRef = db.collection("workouts").document(documentId)
docRef.getDocument(as: UserRoutine.self) { result in
switch result {
case .success(let userRoutine):
// A UserRoutine value was successfully initialized from the DocumentSnapshot.
self.userRoutine = userRoutine
self.errorMessage = nil
case .failure(let error):
// A UserRoutine value could not be initialized from the DocumentSnapshot.
self.errorMessage = "Error decoding document: \(error.localizedDescription)"
}
}
}
To use this, make sure to upgrade to Firebase 8.13.0
For a comprehensive guide to mapping Firestore documents to/from Swift, check out Mapping Firestore Data in Swift - The Comprehensive Guide.
Also, just a small nit: Identifiable is needed to make it easier to use your struct inside a List view, it is not required for mapping to/from Firestore using Codable. To make your models codable, you just have to conform them to the Codable protocol.
Another thing I'd like to point out: try to use an auth state change listener. I am working on a blog post about this topic, should go live in a couple of days.

Nil data returned when copying "working" json data to new struc array

Weird. I swear this was working but then it just stopped working .. or ... Please ignore the i+i ,I will clean this up...
I don't have a clue why but myrecords?[i].title is returning nil. The json.releases[i].date_adde is working fine and full of data. I can "print" it and get a result. but when I go to copy it to the myrecords it is returning nil.
I download the data from JSON, that works fine. then I try to copy the data to a struc array I can get to in other parts of my app and now my myrecords data is empty. what the heck am I doing wrong?
import Foundation
var numberOfRecords : Int = 0
struct routine {
var dateadded : String
var title : String
var artist : String
var year : Int
var recordlabel : String
}
var myrecords: [routine]?
//-------------------------------------------------------------
struct Response: Codable {
var pagination: MyResult
var releases: [MyReleases]
}
public struct MyResult: Codable {
var page: Int
var per_page: Int
var items: Int
}
public struct MyReleases: Codable {
var date_added: String
var basic_information: BasicInformation
}
public struct BasicInformation: Codable {
var title: String
var year: Int
var artists : [Artist]
var labels: [Label]
}
public struct Artist: Codable {
var name: String
}
public struct Label: Codable {
var name: String
}
let url = "https://api.discogs.com/users/douglasbrown/collection/folders/0/releases?callback=&sort=added&sort_order=desc&per_page=1000"
public func getData(from url: String) {
let task = URLSession.shared.dataTask(with: URL(string: url)!, completionHandler: { data, response, error in
guard let data = data, error == nil else {
print("something went wrong")
return
}
//HAVE DATA
var result: Response?
do {
result = try JSONDecoder().decode(Response.self, from: data)
}
catch {
print("Converion Error:\(error.localizedDescription)")
}
guard let json = result else {
return
}
numberOfRecords = json.pagination.items
var i: Int
i = -1
for _ in json.releases {
i = i + 1
myrecords?[i].dateadded = json.releases[i].date_added
myrecords?[i].title = json.releases[i].basic_information.title
myrecords?[i].artist = json.releases[i].basic_information.artists[0].name
myrecords?[i].year = json.releases[i].basic_information.year
myrecords?[i].recordlabel = json.releases[i].basic_information.labels[0].name
print(">>>>>>\(myrecords?[i].dateadded)")
}
})
task.resume()
}
You haven't initialized myrecords array.
Otherwise, you cannot use subscript like myrecords[i] when you don't know the capacity of array, it can be out of index.
First, initialize your array.
var myrecords: [routine]? = []
Second, append new element to array instead of using subscript
for _ in json.releases {
let newRecord = routine()
newRecord.dateadded = json.releases[i].date_added
newRecord.title = json.releases[i].basic_information.title
newRecord.artist = json.releases[i].basic_information.artists[0].name
newRecord.year = json.releases[i].basic_information.year
newRecord.recordlabel = json.releases[i].basic_information.labels[0].name
myrecords.append(newRecord)
}
This is the answer. :) THANK YOU All for pointing me in the right direction
struct Routine {
var dateadded : String
var title : String
var artist : String
var year : Int
var recordlabel : String
}
var myRecords: [Routine] = []
var i : Int
i = -1
for _ in json.releases {
var newRecord = Routine.self(dateadded: "", title: "", artist: "", year: 0, recordlabel: "")
i = i + 1
newRecord.dateadded = json.releases[i].date_added
newRecord.title = json.releases[i].basic_information.title
newRecord.artist = json.releases[i].basic_information.artists[0].name
newRecord.year = json.releases[i].basic_information.year
newRecord.recordlabel = json.releases[i].basic_information.labels[0].name
myRecords.append(newRecord)
}
print(">>>>\(myRecords[0].dateadded)")
I will clean up the bad code too but it works and that is good! :)

How to avoid "Key paths that include an array property must use aggregate operations" exception when querying a Realm?

I have a Realm object database with the following structure:
class ItemModel: Object {
dynamic var id: Int = 0
dynamic var catId: Int = 0
dynamic var subId: Int = 0
dynamic var itemName: String = ""
dynamic var sortOrder: Int = 0
dynamic var favorite: Bool = false
override static func primaryKey() -> String {
return "id"
}
}
class SubCategoryModel: Object {
dynamic var id: Int = 0
dynamic var catId: Int = 0
dynamic var subCatName: String = ""
dynamic var desc: String = ""
dynamic var sortOrder: Int = 0
let haveUnit = List<ItemModel>()
override static func primaryKey() -> String {
return "id"
}
}
class CategoryModel: Object {
dynamic var id: Int = 0
dynamic var catName: String = ""
dynamic var sortOrder: Int = 0
override static func primaryKey() -> String {
return "id"
}
}
To query the database I'm using the following code:
var data = List<ItemModel>()
let index = indexPath.section
var query = "catId == \(inCategory)"
if (searchTextValue != "") {
query += " AND itemName CONTAINS[c] \(searchTextValue)"
}
if index == 0 {
let unitFavorite = DBManager.realm.objects(ItemModel.self).filter("\(query) AND favorite == true")
data += unitFavorite
} else {
var subquery = "catId == \(inCategory)"
if (searchTextValue != "") {
subquery += " AND ANY haveUnit.itemName CONTAINS[c] %#"
let subCat = DBManager.realm.objects(SubCategoryModel.self).filter(subquery, searchTextValue).sorted(byKeyPath: "sortOrder", ascending: true)
data += subCat[indexPath.section - 1].haveUnit
} else {
let subCat = DBManager.realm.objects(SubCategoryModel.self).filter(subquery).sorted(byKeyPath: "sortOrder", ascending: true)
data += subCat[indexPath.section - 1].haveUnit
}
}
When running this code I'm always seeing the following exception:
Terminating app due to uncaught exception 'Invalid predicate', reason: 'Key paths that include an array property must use aggregate operations'
Can you please help me understand why I'm seeing this exception and what I can do to avoid it?

Realm/Swift: compilation error: Missing argument for parameter 'forPrimaryKey' in call

I'm trying to get my head around swift & realm, so I've created some kind of a test pad programme.
My model is defined like so
class RealmRecord: Object {
// properties
dynamic var id: Int = 0;
dynamic var text: String = ""
dynamic var var1: Double = 0.0
dynamic var var2: Int = 0
dynamic var var3: Double = 0.0
dynamic var var4: Int = 0
dynamic var cdate: Date = Date()
dynamic var cusr: String = ""
dynamic var mdate: Date = Date.distantPast
dynamic var musr: String = ""
dynamic var mcnt: Int = 0
// methods
convenience init(id: Int? = 0, text: String? = "", var1: Double? = 0.0,
var2: Int? = 0, var3: Double? = 0.0, var4: Int? = 0,
cusr: String? = "") {
self.init()
self.id = id!
self.text = text!
self.var1 = var1!
self.var2 = var2!
self.var3 = var3!
self.var4 = var4!
self.cdate = Date()
self.cusr = cusr!
self.mdate = Date.distantPast
self.musr = ""
self.mcnt = 0
} // init
override static func primaryKey() -> String? {
return "id"
} // primaryKey
} // RealmRecord
Persisting the data is accomplished by
try recRealm?.write {
recRealm?.add(self.rec, update: true)
} // try
But when adding the data retrieval via
if let inrec = self.recRealm?.object(RealmRecord.self) {
return inrec
} else {
return List<RealmRecord>()
} // if/else
I'm receiving an error message while compiling the code, reading
DataRealmRecord.swift:84:39: Missing argument for parameter 'forPrimaryKey' in call
Looking at the Realm documentation reveals only retrieving all persisted data--apparently without having a primary key defined--, or, alternatively, a single object, specified by the primary key.
Sifting through The Net brings up pretty much the same.
Given the model above, how can I retrieve all persisted data?
-- Sil68
EDIT
I've also defined a class facilitating this Realm model of mine, which basically carries out the following steps:
generate some random data;
persist data via the Realm model;
read data in again;
compare generated with read data.
The code
import Foundation
import RealmSwift
class DataRealmRecord {
// properties
private(set) var recDBPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first!
private(set) var recDBSubPath = "Persistency"
private(set) var recDBFile = "data.realm"
private(set) var recRealm: Realm?
private(set) var recRealmCfg: Realm.Configuration?
private(set) var rec = List<RealmRecord>()
private(set) var startTime = 0.0
private(set) var stopTime = 0.0
private(set) var runTime = 0.0
private(set) var outLog = ""
private(set) var realmOk = false
// methods
init() {
// assemble destination folder/database name
do {
try FileManager.default.createDirectory(atPath: recDBPath + "/" +
recDBSubPath,
withIntermediateDirectories: true,
attributes: nil)
recDBFile = recDBPath + "/" + recDBSubPath + "/" + recDBFile
realmOk = true
} catch let error as NSError {
outLog += error.localizedDescription
realmOk = false
} // do/try/catch
// configure realm database
if (realmOk) {
self.recRealmCfg = Realm.Configuration(fileURL: Foundation.URL(string: self.recDBFile))
do {
self.recRealm = try Realm(configuration: self.recRealmCfg!)
realmOk = true
} catch let error as NSError {
outLog += error.localizedDescription
realmOk = false
} // do/try/catch
} // if
} // init
// generate test data
func generateData(noRecs: Int? = 1000, simDat: SimulateData?) {
for i in 1...noRecs! {
let realmRec = RealmRecord(id: i,
text: String(format: "Record #%04d", i),
var1: simDat?.datnorm[i - 1] ?? 1.1,
var2: simDat?.datpois[i - 1] ?? 2,
var3: simDat?.datunif[i - 1] ?? 3.3,
var4: simDat?.datbern[i - 1] ?? 4,
cusr: "me")
self.rec.append(realmRec)
} // for
} // generateData
// retrieve test data from persistent storage
func loadData() -> List<RealmRecord> {
if let inrec = self.recRealm?.object(RealmRecord.self) {
return inrec
} else {
return List<RealmRecord>()
} // if/else
} // loadData
// save test data to persistent storage
func saveData() {
do {
try recRealm?.write {
recRealm?.add(self.rec, update: true)
} // try
} catch let error as NSError {
outLog += error.localizedDescription
} // do/try/catch
} // saveData
// compare two data sets
func compareData(rec1: List<RealmRecord>, rec2: List<RealmRecord>) -> Bool {
var rc = false
if rec1.count == rec2.count {
rc = true
for i in 0..<rec1.count {
rc = rc && (rec1[i] == rec2[i])
} // for
} // if
return rc
} // compareData
// run a full evaluation cycle
// (1) generate test data;
// (2) save test data to persistant storage;
// (3) retrieve test data from persistant storage;
// (4) compare generated data with retrieved data.
func fullCycle(noRecs: Int? = 1000, simDat: SimulateData?, prnData: Bool? = false) {
// start execution time measurement
self.startTime = Double(CFAbsoluteTimeGetCurrent())
// execute the full cycle
self.generateData(noRecs: noRecs, simDat: simDat) // (1)
self.saveData() // (2)
let rec2 = self.loadData() // (3)
let cmpRec = compareData(rec1: self.rec, rec2: rec2) // (4)
// stop execution time measurement & calculate elapsed time
self.stopTime = Double(CFAbsoluteTimeGetCurrent())
self.runTime = self.stopTime - self.startTime
} // fullCycle
} // DataRealmRecord
Issue at hand is, this code fails to compile due to the error message mentioned above (in method loadData()).
The Swift compiler is telling you that it thinks you're trying to call Realm.object(ofType:forPrimaryKey:), which retrieves a single object based on the value of its primary key. It sounds like you really want to call Realm.objects(_:) to retrieve all objects of a given type. Note that this returns a Results<T>, not a List<T>.