I need to migrate my Realm DB in Swift and getting errors due to duplicate values in regards to primary key.
I have reviewed both models and the migration block several times and cannot find my error. That's why I'd be thankful for any hints that might help.
Old data model:
class LabelObject: Object, Codable {
#objc dynamic var identifier: String = UUID().uuidString
#objc dynamic var geo_latitude: Double = 0
#objc dynamic var geo_longitude: Double = 0
#objc dynamic var geo_radius: Double = 300
#objc dynamic var info_text: String = ""
#objc dynamic var info_icon: String = ""
#objc dynamic var info_placemark: String = ""
#objc dynamic var color_red: Float = Float(UIColor.systemBlue.cgColor.components?[0] ?? 0)
#objc dynamic var color_green: Float = Float(UIColor.systemBlue.cgColor.components?[1] ?? 0)
#objc dynamic var color_blue: Float = Float(UIColor.systemBlue.cgColor.components?[2] ?? 0)
#objc dynamic var color_alpha: Float = 1
override class func primaryKey() -> String? {
return "identifier"
}
}
New data model:
class LabelObject: Object {
#Persisted var identifier: String = UUID().uuidString
#Persisted var geo_latitude: Double = 0
#Persisted var geo_longitude: Double = 0
#Persisted var geo_radius: Double = 150
#Persisted var info_icon: String = "tag"
#Persisted var info_text: String = ""
#Persisted var info_placemark: String = ""
#Persisted var color_red: Double = Double(UIColor.systemBlue.cgColor.components?[0] ?? 0)
#Persisted var color_green: Double = Double(UIColor.systemBlue.cgColor.components?[1] ?? 0)
#Persisted var color_blue: Double = Double(UIColor.systemBlue.cgColor.components?[2] ?? 0)
#Persisted var color_alpha: Double = Double(UIColor.systemBlue.cgColor.components?[3] ?? 1)
override class func primaryKey() -> String? {
return "identifier"
}
}
Migration block:
migration.enumerateObjects(ofType: "LabelObject") { oldObject, _ in
guard let oldObject = oldObject else { return }
let newLabelObject: MigrationObject = migration.create("LabelObject")
newLabelObject["identifier"] = oldObject["identifier"] as? String ?? UUID().uuidString
newLabelObject["geo_latitude"] = oldObject["geo_latitude"] as? Double ?? 0
newLabelObject["geo_longitude"] = oldObject["geo_longitude"] as? Double ?? 0
newLabelObject["geo_radius"] = oldObject["geo_radius"] as? Double ?? georyCurrentConfiguration.preferenceLabelRadius
newLabelObject["info_text"] = oldObject["info_text"] as? String ?? ""
newLabelObject["info_icon"] = oldObject["info_icon"] as? String ?? ""
newLabelObject["info_placemark"] = oldObject["info_placemark"] as? String ?? ""
newLabelObject["color_red"] = newLabelObject["color_red"] as? Float ?? UIColor.systemBlue.cgColor.components?[0] ?? 0
newLabelObject["color_green"] = newLabelObject["color_green"] as? Float ?? UIColor.systemBlue.cgColor.components?[1] ?? 0
newLabelObject["color_blue"] = newLabelObject["color_blue"] as? Float ?? UIColor.systemBlue.cgColor.components?[2] ?? 0
newLabelObject["color_alpha"] = newLabelObject["color_alpha"] as? Float ?? UIColor.systemBlue.cgColor.components?[3] ?? 0
}
But I keep getting
Primary key property 'class_LabelObject.identifier' has duplicate values after migration.
Thanks for any hints!
You're telling your new object to use the same primary key as the old object, which isn't allowed
Let the new object populate the primary key on its own, which will copy the properties but assign it a unique ID.
It also appears the only difference is the new model is Codable and if that's the case it can be removed without a migration block as it's unrelated to Realm
Note that you only need local migrations for destructive changes like changing a property name or deleting a property.
Related
I want to fetch the offerApplied value from the struct in another class. Here is the struct block:
struct Offer: JsonDeserilizer {
var offerDesC:String = ""
var discount:Double = 0.0
var offerId:String = ""
var offerCode:String = ""
var offerApplied:Int = 0
mutating func deserilize(values: Dictionary<String, Any>?) {
self.offerDesC = values?["offer_desc"] as? String ?? ""
self.discount = values?["discount"] as? Double ?? 0.0
self.offerId = values?["_id"] as? String ?? ""
self.offerCode = values?["offer_code"] as? String ?? ""
self.offerApplied = values?["is_applied"] as? Int ?? 0
}}
And an explanation will be very helpful.
You can create getter function in your struct, that can return specific value as you want. In target class initialise your struct properties (by calling deserilize(), or however you wish) and call the getter function to fetch values.
Update-:
As #Joakim mentioned, your properties are not private, so there is no need to create getter function. You could infact directly refer the property from created object.
struct Offer {
var offerDesC:String = ""
var discount:Double = 0.0
var offerId:String = ""
var offerCode:String = ""
var offerApplied:Int = 0
mutating func deserilize() {
self.offerDesC = "xy"
self.discount = 20
self.offerId = "okkk"
self.offerCode = "12"
self.offerApplied = 245
}
// func returnOfferApplied() -> Int{
// return offerApplied
// }
}
class xyz{
var obj = Offer()
func printOffer(){
obj.deserilize()
print(obj.offerApplied)
}
}
let obj = xyz()
obj.printOffer()
First create struct class at global file.
Look in my example
struct ColorPalette {
static let UEMColor = hexStringToUIColor(hex: "72279C")
static let uemIconPrimary = hexStringToUIColor(hex: "282D68")
}
Access Struct Like this:
ColorPalette.uemIconPrimary
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
I already search in the internet, I still don't understand how to insert a value to a variable. im trying to insert a value to a variable so i can append it into an array and then put it inside a tableviewcell.
I understand how the cells works, i just wanted to know how can i insert a data to this variable
Here's the code for my struct
import Foundation
import UIKit
enum issueType: String {
case major = "Major", blocker = "Blocker", minor = "Minor"
}
struct Issue {
var id: String
var tester: String
var type: issueType
var title: String
var appName: String
var desc: String
var date: Date
var bgColor: UIColor?
init(){
id = ""
tester = ""
type = .minor
title = ""
appName = ""
desc = ""
date = Date()
bgColor = UIColor.main()
}
init(item: [String:Any]){
self.init()
id = item["id"] as? String ?? ""
tester = item["tester"] as? String ?? ""
title = item["title"] as? String ?? ""
appName = item["appName"] as? String ?? ""
desc = item["desc"] as? String ?? ""
if type == .major {
bgColor = UIColor.main()
}
else if type == .blocker {
bgColor = UIColor.blue
}
else {
bgColor = UIColor.green
}
}
}
Here's the code for the variable in the superDidLoad from different a class
override func viewDidLoad() {
super.viewDidLoad()
var issue1 = Issue(id: "id", tester: "tester", type: .minor, title: "title", appName: "appName", desc: "desc", date: Date())
issue1.bgColor = UIColor.main()
array.append(issue1)
}
Your UITableViewCell subclass should have some variable of type Issue
class YourSubclass: UITableViewCell {
var issue: Issue?
...
}
then in cellForRowAt TableView data source method assign cell's variable as element from array with index equal to indexPath.row
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
...
cell.issue = array[indexPath.row]
...
}
After adding your own initializer to the struct you lost default memberwise initializer.
To avoid this use extension:
struct Issue {
var id: String
var tester: String
var type: issueType
var title: String
var appName: String
var desc: String
var date: Date
var bgColor: UIColor?
}
extension Issue {
init(){
id = ""
tester = ""
type = .minor
title = ""
appName = ""
desc = ""
date = Date()
bgColor = UIColor.main()
}
init(item: [String:Any]){
self.init()
id = item["id"] as? String ?? ""
tester = item["tester"] as? String ?? ""
title = item["title"] as? String ?? ""
appName = item["appName"] as? String ?? ""
desc = item["desc"] as? String ?? ""
if type == .major {
bgColor = UIColor.main()
}
else if type == .blocker {
bgColor = UIColor.blue
}
else {
bgColor = UIColor.green
}
}
}
But even in this case you must to init all properties by default initializer including 'bgColor'.
var issue1 = Issue(id: "id", tester: "tester", type: .minor, title: "title", appName: "appName", desc: "desc", date: Date(), bgColor: nil)
I created a Core Data object as follows:
#objc(Gates)
public class Gates : NSManagedObject {
public class func getFetchRequest() -> NSFetchRequest<Gates> {
let request = NSFetchRequest<Gates>(entityName: "Gates")
request.returnsObjectsAsFaults = false
return request
}
#NSManaged var updatedAt: String
#NSManaged var objectId: String
#NSManaged var identifier: String
#NSManaged var name: String
#NSManaged var address: String
#NSManaged var dueDate: String
#NSManaged var productionCode: String
#NSManaged var locationCountry: String
#NSManaged var locationCity: String
#NSManaged var locationBuilding: String
#NSManaged var locationLevel: String
#NSManaged var locationRoom: String
#NSManaged var locationRange: String
#NSManaged var isFavorite: Bool
public func setGateData(gateDict: [String: Any]) {
updatedAt = gateDict["updatedAt"] as? String ?? ""
objectId = gateDict["objectId"] as? String ?? ""
identifier = gateDict["identifier"] as? String ?? ""
name = gateDict["name"] as? String ?? ""
isFavorite = gateDict["isFavorite"] as? Bool ?? false
address = gateDict["address"] as? String ?? ""
dueDate = gateDict["dueDate"] as? String ?? ""
productionCode = gateDict["productionCode"] as? String ?? ""
locationCountry = gateDict["locationCountry"] as? String ?? ""
locationCity = gateDict["locationCity"] as? String ?? ""
locationBuilding = gateDict["locationBuilding"] as? String ?? ""
locationLevel = gateDict["locationLevel"] as? String ?? ""
locationRoom = gateDict["locationRoom"] as? String ?? ""
locationRange = gateDict["locationRange"] as? String ?? ""
}
}
I also set this up in the xcdatamodeld:
Now, after I have saved the object in core data and I'm using the getFetchRequest() method that is part of the class which sets
request.returnsObjectsAsFaults = false on the request but I still getting the following result when I try to print the fetched objects:
<Gates: 0x60c0000959a0> (entity: Gates; id: 0xd000000005e40000 <x-
coredata://B9C33A5D-BF96-433A-9186-F51AA253F488/Gates/p377> ; data: <fault>)
As you can see in this case the data is still data: <fault>.
Why is the object parameters are not retrieved even though I set request.returnsObjectsAsFaults = false? What am I missing?
I'm having this issue and I found in my case instead of using the objects value in line, I initialize a variable with it first and then use that variable.
I would love to know if this is a Core Data bug or if I'm doing something wrong.
public class Person: NSManagedObject, Identifiable {
#NSManaged public var firstName: String
#NSManaged public var lastName: String
#NSManaged public var emailAddress: String
}
This does not work all the time:
CoreDataManager.shared.persistentContainer.performBackgroundTask{ context in
context.automaticallyMergesChangesFromParent = true
context.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
do {
let email = "123Tester#gmail.com"
let request = Person.getPersonWith(email: email)
request.returnsObjectsAsFaults = false //DOES NOT WORK
if let person = try context.fetch(request).first{
print(person.fullName)
}
} catch{
fatalError()
}
}
However this does
CoreDataManager.shared.persistentContainer.performBackgroundTask{ context in
context.automaticallyMergesChangesFromParent = true
context.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
do {
let email = "123Tester#gmail.com"
let request = Person.getPersonWith(email: email)
if let person = try context.fetch(request).first{
let fullName = person.fullName
print(fullName)
}
} catch{
fatalError()
}
}
I'm trying to save my array of objects in Realm, but Realm appears to be saving only the last object.
This is my model class:
class ContactEntry: Entry {
dynamic var contactId: Int = 0
dynamic var email: String? = ""
dynamic var phone: String? = ""
dynamic var building: String? = ""
dynamic var room: String? = ""
dynamic var contactDescription: String? = ""
dynamic var worktime: String? = ""
dynamic var address: String? = ""
dynamic var personEntries: PersonEntry?
}
This is the base class:
class Entry: Object {
dynamic var id: Int = 0
override static func primaryKey() -> String? { return "id" }
}
This is code I'm using for saving:
func createOrUpdate(entities: [Entity]) {
let entries = toEntries(entities)
let realm = try! Realm()
try! realm.write {
realm.add(entries, update: true)
}
}
func toEntry(entity: Contact) -> ContactEntry {
let entry = ContactEntry()
entry.contactId = entity.id
entry.email = entity.email
entry.phone = entity.phone
entry.building = entity.building
entry.room = entity.room
entry.contactDescription = entity.description
entry.worktime = entity.worktime
entry.address = entity.address
entry.personEntries = personDAO.toEntry(entity.person)
return entry
}
func toEntry(entity: Person) -> PersonEntry {
let entry = PersonEntry()
entry.personId = entity.id
entry.firstname = entity.firstname
entry.middlename = entity.middlename
entry.lastname = entity.lastname
entry.photo = entity.photo
return entry
}
I think that it may be because I have relationship in my model, but I'm not sure why they'd be a problem.
I'm using them as described in the Realm documentation.