Preventing duplicate storage to realm - ios

I am using realm to store and persist my data. Everything works fine and I just discovered that users can actually store duplicate item which is bad. I would am unable to create a check to prevent duplicate items, any help would be appreciated
Function
func addData(object: OfflineModel) {
try! database.write {
database.add(object, update: true)
}
}
//MARK:- Get Offline
func getDataFromDB() -> Results<OfflineModel> {
offlineItems = database.objects(OfflineModel.self)
return offlineItems!
}
//MARK:- Create Offline
func createOfflineList(createdDate: Date, photo: Data, title: String, completion: #escaping CompletionHandler) -> Void {
REALM_QUEUE.sync {
let offlineList = OfflineModel()
offlineList.createdDate = createdDate
offlineList.photo = photo
offlineList.title = title
OfflineFunctions.instance.addData(object: offlineList)
completion(true, nil)
}
}
Model
#objc dynamic var id = UUID().uuidString
#objc dynamic var photo: Data? = nil
#objc dynamic var title : String = ""
#objc dynamic var createdDate: Date?
override static func primaryKey() -> String {
return "id"
}

The issue is that in the createOfflineList method you create a new OfflineModel, which generates a random id using UUID().uuidString and hence you cannot have duplicate models from Realm's point of view, since id, which is used as the primary key will always be different. You'll need to use title (or any other non-random property that you actually want to use to identify your model instances) as the primary key.
class OfflineModel: Object {
#objc dynamic var photo: Data? = nil
#objc dynamic var title : String = ""
#objc dynamic var createdDate: Date?
override static func primaryKey() -> String {
return "title"
}
}

Related

Inconsistent realm data between threads

I have a case where I need to populate realm with a series of objects upon user request. To populate realm I make a network call then write the objects to realm. When the write is complete, the background thread invokes a callback that switches to the main thread to process the results. I would much prefer to use realm notifications but this is a very specific use case and notifications are not an option.
I can't share my full project but I was able to reproduce this issue with a sample project. Here is my data model.
class Owner: Object {
#objc dynamic var uuid: String = ""
#objc dynamic var name: String = ""
convenience init(uuid: String, name: String) {
self.init()
self.uuid = uuid
self.name = name
}
func fetchDogs() -> Results<Dog>? {
return realm?.objects(Dog.self).filter("ownerID == %#", uuid)
}
override class func primaryKey() -> String? {
return "uuid"
}
}
class Dog: Object {
#objc dynamic var uuid: String = ""
#objc dynamic var name: String = ""
#objc dynamic var ownerID: String = ""
func fetchOwner() -> Owner? {
return realm?.object(ofType: Owner.self, forPrimaryKey: ownerID)
}
convenience init(uuid: String, name: String, ownerID: String) {
self.init()
self.uuid = uuid
self.name = name
self.ownerID = ownerID
}
override class func primaryKey() -> String? {
return "uuid"
}
}
I realize that a List would be appropriate for the Owner -> Dog relationship but that's not an option in this project.
In the sample project there are two buttons that we care about. One to add some Dog objects to realm and associate them with the Owner UUID. The other button deletes all of the Dog objects that belong to the owner.
The basic flow is like this:
Delete all of the owner's dogs
Create a new set of dogs on a background thread and add them to realm.
Invoke a callback
Switch back to the main thread
Print all of the owner's dogs
Nine times out of ten the dogs are printed out successfully. However, sometimes realm indicates that there are no dogs associated with the current owner. What could be the reason for this? My view controller logic is below.
EDIT: The last thing I should mention is that this issue is only only reproducible on a physical device. I can't reproduce it on the simulator. That makes me wonder if this is a realm bug.
// MARK: - Props and view controller life cycle
class ViewController: UIViewController {
var owner: Owner?
let ownerUUID = UUID().uuidString
override func viewDidLoad() {
super.viewDidLoad()
owner = makeOwner()
}
}
// MARK: - Action methods
extension ViewController {
#IBAction func onDeletePressed(_ sender: UIButton) {
print("Deleting...")
deleteDogs()
}
#IBAction func onRefreshPressed(_ sender: UIButton) {
print("Creating...")
createDogs {
DispatchQueue.main.async {
self.printDogs()
}
}
}
#IBAction func onPrintPressed(_ sender: UIButton) {
printDogs()
}
}
// MARK: - Helper methods
extension ViewController {
private func makeOwner() -> Owner? {
let realm = try! Realm()
let owner = Owner(uuid: ownerUUID, name: "Bob")
try! realm.write {
realm.add(owner)
}
return owner
}
private func deleteDogs() {
guard let dogs = owner?.fetchDogs() else { return }
let realm = try! Realm()
try! realm.write {
realm.delete(dogs)
}
}
private func createDogs(completion: #escaping () -> Void) {
DispatchQueue(label: "create.dogs.background").async {
autoreleasepool {
let realm = try! Realm()
let dogs = [
Dog(uuid: UUID().uuidString, name: "Fido", ownerID: self.ownerUUID),
Dog(uuid: UUID().uuidString, name: "Billy", ownerID: self.ownerUUID),
Dog(uuid: UUID().uuidString, name: "Sally", ownerID: self.ownerUUID),
]
try! realm.write {
realm.add(dogs)
}
}
completion()
}
}
private func printDogs() {
guard let dogs = owner?.fetchDogs() else { return }
print("Dogs: \(dogs)")
}
}
It appears the the dogs are created on a separate thread; a 'background thread' and because of that it won't have a run loop - so the two threads are looking at different states of the data.
When creating objects on a thread with no runloop, Realm.refresh() must be called manually in order to advance the transaction to the most recent state.
Check out the documentation on Seeing changes from other threads

Realm Update Deleting existing data

Using Swift 5 for iOS13.
I am trying to update an existing Realm record with a Contact Picker result. The function deletes all the object content except for the new content.
My code
class Person: Object {
#objc dynamic var personId = UUID().uuidString
#objc dynamic var firstName: String = ""
#objc dynamic var surname: String = ""
#objc dynamic var mobileNumber: Int = 0
#objc dynamic var password: String = ""
#objc dynamic var myContactID: String = ""
override static func primaryKey() -> String? {
return "personId"
}
}
extension HomeController: CNContactPickerDelegate {
func contactPicker(_ picker: CNContactPickerViewController, didSelect contact: CNContact) {
picker.dismiss(animated: true, completion: nil)
let person = Person()
let me = realm.objects(Person.self).filter("mobileNumber == %#", mobileNumber)
person.myContactID = contact.identifier
person.personId = me.first!.personId
try! realm.write {
realm.add(person, update: .modified)
}
self.viewWillAppear(true)
}
}
All the existing content of the Person class in the Realm database disappears except for myContactID and personID.
That is because you are updating the Person with new data.
The line let person = Person() creates a new instance with all the default values. (firstName: String = "" etc..)
So, when you assign myContactID and personId to this newly created person, it will look like this:
Person {
personId = FAE4C224-D37E-4C77-B6F1-C60A92F188D0;
firstName = ;
surname = ;
mobileNumber = ;
password = ;
myContactID = contactIdentifier;
}
And when you call realm.add(person, update: .modified), it will overwrite the record associated with the primary key with this newly created person.
You want to fetch an existing person and modify it. You can do something like this:
guard let me = realm.objects(Person.self).filter("mobileNumber == %#", mobileNumber).first else { return }
try! realm.write {
me.myContactID = contact.identifier
}

Set Table Section Titles from Realm Query

I've searched for the answer to this (I'm sure it's there somewhere) but can't find it.
I am trying to populate a UITableView's section headers from a Realm database, where the section title is in a related class.
My Realm classes:
class Person: Object {
#objc dynamic var personId = UUID().uuidString
#objc dynamic var firstName: String = ""
#objc dynamic var surname: String = ""
#objc dynamic var mobileNumber: Int = 0
#objc dynamic var password: String = ""
override static func primaryKey() -> String? {
return "personId"
}
}
class Group: Object {
#objc dynamic var groupId = UUID().uuidString
#objc dynamic var person: Person?
#objc dynamic var groupName: String = ""
let groupContent = List<String>()
override static func primaryKey() -> String? {
return "groupId"
}
}
I want to retrieve the groupName results for the current user and use them as table section headers. The number of groupNames is dynamic for each user.
My current code, which doesn't work at all is:
func getGroupNames() {
let mobileNumber = UserDefaults.standard.integer(forKey: "mobileNumber")
let personResult = realm.objects(Person.self).filter("mobileNumber == %#", mobileNumber)
let groupNames = realm.objects(Group.self).filter("person == %#", personResult.self.first)
return (groupNames)
}
I can't get groupNames to be useful as section headers.
Help will be appreciated!
Thanks.
UPDATE
I now have:
func getGroupNames() -> [String] {
let mobileNumberInt = mobileNumber
let groupNames = realm.objects(Group.self).filter("person.mobileNumber == %#", mobileNumberInt).map({$0.groupName})
return Array(groupNames)
}
This returns ["Group Name 1", "Group Name 2"] twice (no matter how many objects are in the results). Why twice, and now how do I get these into my section headers? I've tried:
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> [String] {
return getGroupNames()
}
The quantity of sections works, but the headers are not showing.
Assuming you already know the mobile number of a person, you can use following code to fetch groups a person belongs to.
func getGroupNames() -> [Group]? {
let realm = try Realm()
let mobileNumber = UserDefaults.standard.integer(forKey: "mobileNumber")
let groupNames = realm.objects(Group.self).filter("person.mobileNumber == %#", mobileNumber)
return Array(groupNames)
}

Adding a new relationship to an existing model in Realm

I'm developing an iOS application using Realm and have a object MedicationObject which contains the information about the Medication being used. This then has a many to one relationship class MedicationRecording which stores the information when the time the medication was taken.
import Foundation
import RealmSwift
class MedicationObject : Object {
//This model requires a Primary Key
#objc dynamic var id = 0
override static func primaryKey() -> String? {
return "id"
}
#objc dynamic var medicationName : String = ""
//stores the refrences to medication recoridngs
var messurements = List<MedicationRecording>()
public func addNewMesurment() {
print(self)
let medicationRecording = MedicationRecording()
medicationRecording.medicationTaken = self
medicationRecording.timeTaken = Date()// this saves the time to now
medicationRecording.medicationTaken = self
RealmAccess().updateMedicationObject(medicationObject: self, medicationRecord: medicationRecording) //this should work
}
}
here is my MedicationRecording:
import Foundation
import RealmSwift
class MedicationRecording : Object {
#objc dynamic var medicationTaken : MedicationObject? = MedicationObject()
#objc dynamic var timeTaken : Date = Date()
#objc dynamic var id = 0
override static func primaryKey() -> String? {
return "id"
}
}
and this is the method I'm calling to save the data to
func updateMedicationObject(medicationObject : MedicationObject, medicationRecord : MedicationRecording) {
let realm = try! getRealmAccess()
let mediObject = MedicationObject()
mediObject.medicationName = medicationObject.medicationName
do {
try! realm.write {
realm.add(medicationRecord, update: true)
medicationObject.messurements.append(medicationRecord)
}
}
}
At the moment when I'm saving the data it is then losing all the information in MedicationObject and only saving the last data.
Any help with be greatly appreciated
Thank you

EVReflection + Moya + Realm + RxSwift - Could not create an instance for type dict

I'm stuck putting all of the above together. I'll appreciate if I can get any input.
Here's my short setup:
typealias RealmObject = Object
/// Extension to ignore undefined keys when mapping
extension RealmObject : EVReflectable {
open override func setValue(_ value: Any?, forUndefinedKey key: String) { }
}
Sample Realm models:
class Product: RealmObject {
dynamic var productId: String = ""
let productLanguages = List<ProductLanguage>()
override static func primaryKey() -> String? {
return "productId"
}
}
class ProductLanguage: RealmObject {
dynamic var productLanguageId: String = ""
dynamic var languageCode: String = ""
dynamic var productName: String = ""
override static func primaryKey() -> String? {
return "productLanguageId"
}
}
To fetch product details I use Moya and RxSwift:
func getProduct(productItemKey: String) -> Observable<Product> {
return provider.request(.product(productId: productItemKey)).map(to: Product.self)
}
I think .map(to: Product.self) does not work with realm Lists out of the box. For each object inside the list I get an error:
ERROR: Could not create an instance for type
dict:{
CreateDate = "2015-10-12T11:11:50.013Z";
IngredientList = "Acao ingredient";
LanguageCode = "en-US";
ProductId = "d6bb0084-6838-11e5-9225-00ac14ef2300";
ProductLanguageId = "f96848d0-df77-4594-99b7-d390bb127891";
ProductName = Acao;
Tagline = "The smart drink - 100% organic, vegan energy booster with guara"
}
Is there any other way to map Moya response into Realm objects?
Thank you very much for any input!
Turns out it was a bug in EVReflection. Fixed in 4.17.0

Resources