Preserving Realm property value when updating object - ios

I'm working on a Swift 3 Realm app and I have this Realm model class:
class User: Object {
dynamic var userName = ""
dynamic var userURL = ""
dynamic var userThumbnailURL = ""
dynamic var isRegistered = false
var userID = RealmOptional<Int>()
override class func primaryKey() -> String? {
return "userID"
}
}
Then I'm adding values as follows. I'm getting them from my server and saving them in the local database:
Query 1
let user = User()
user.userName = fields["userName"] as! String
user.userURL = fields["userURL"] as! String
user.userThumbnailURL = fields["userThumbnailURL"] as! String
user.userID.value = fields["userID"] as? Int
try! uiRealm.write {
uiRealm.add(user, update: true)
}
Then when the user finishes the registration I'm updating the specific user in the local Realm database as being a registered user (isRegistered = true). This value is only saved in the local Realm database:
uiRealm.beginWrite()
let updatingUser = uiRealm.objects(User).filter("userID = %d", self.userId)
let user = updatingUser.first
book?.isRegistered = true
try! uiRealm.commitWrite()
But my problem is that when the new server response is received and I re-run Query 1 the isRegistered property becomes false. How can I avoid this?

The short answer is that partial updates of an object cannot be performed by passing an instance of an Object subclass to Realm.add(_:update:). The reason for this is that there's no way to represent don't update this property for a property on your Object subclass. You'll instead want to pass a dictionary to Realm.create(_:value:update:), where the absence of a given key represents don't update this property.

Related

How to create model like firebase structure using RealmSwift in iOS swift?

I have been trying to create model like firebase database structure. I can able to create normal string, bool and int value but not able to do array and dictionary.
Here is my firebase structure screenshot:
Here i am trying to add groupMembers and to in model like firebase structure.
Here is my Model i tried to create with array and dictionary:
import Foundation
import RealmSwift
class RealmMessages: Object {
#objc dynamic var messageText : String?
#objc dynamic var sender: String?
let chatCreatedDateTimee = List<timeStampValue>()
#objc dynamic var chatId: String?
#objc dynamic var from: String?
#objc dynamic var groupMemberss : [String: String]!
let groupMemebersCount = RealmOptional<Int>()
#objc dynamic var task: Bool = false
#objc dynamic var to: Array = [String]()
}
class timeStampValue: Object {
let timestamp = RealmOptional<Int>()
}
Here is my contoller code: Trying to add value into realm database.
var dic : [String : String] = [:]
var cont = ["one", "two", "three"]
var oneVal = ["909090": "SELF", "808080": "Other"]
override func viewDidLoad() {
super.viewDidLoad()
let realm = try! Realm()
print("realm location:::\(String(describing: Realm.Configuration.defaultConfiguration.fileURL))")
let myMessage = RealmMessages()
myMessage.messageText = "Diva"
myMessage.sender = "yIvq1mQxjfZpjs1ybRTTlDOmUKV2"
let timevalue = timeStampValue()
timevalue.timestamp.value = 123123131
myMessage.chatId = "+918000080000"
myMessage.from = "+918000080000"
myMessage.groupMemberss = oneVal
myMessage.to = cont
try! realm.write {
realm.add(myMessage)
}
}
How to get groupMemberss and to structure in realm database like firebase. And how to create array and dictionary in realm
There are a number of solutions but here's two.
Assuming the data has been read in and the data from the groupMembers snapshot is sitting in a dictionary var that looks like this
let groupMembersDict = [
"919": "participant",
"111": "observer",
"222": "participant"
]
To store that in Realm, you can work with primitives and store each key and value in a separate List (think: Array) or you can leverage a managed Realm object and store those in a List.
If you want to keep the data within an object; here's what it would look like.
class GroupData: Object {
#objc dynamic var num = ""
#objc dynamic var type = ""
convenience init(withNum: String, andType: String) {
self.init()
self.num = withNum
self.type = andType
}
}
Here's the main object showing both options; either option 1: store the key value pairs in two arrays, or option 2: use the above GroupData object to store the key value pairs together
class Messages: Object {
#objc dynamic var messageText = ""
//option 1: two lists of primative strings that can be accessed like an array.
// downside is managing two lists
let groupNum = List<String>()
let groupType = List<String>()
//option 2: a list of members using another Realm object
let groupNumType = List<GroupData>()
}
And some code to create two messages, one of each type
let msg0 = Messages()
msg0.messageText = "My message"
for member in groupMembersDict {
msg0.groupNum.append( member.key )
msg0.groupType.append( member.value )
}
let msg1 = Messages()
msg1.messageText = "This message"
for member in groupMembersDict {
let aGroup = GroupData(withNum: member.key, andType: member.value)
msg1.groupNumType.append(aGroup)
}
store them in realm
realm.add(msg0)
realm.add(msg1)
read them both in an display the message from option 2. Option 1 would be just iterating over the arrays to print the group data
let messageResults = realm.objects(Messages.self)
for msg in messageResults {
print(msg.messageText)
for group in msg.groupNumType {
print(group.num, group.type)
}
}
Keep in mind that all managed properties must be primitives: NSString, NSDate, NSData, NSNumber or List, Results, RLMLinkingObjects, or subclasses of RLMObject like the GroupData shown above.

Realm Typecast issue Swift

I have to declare realm string property for to save the value get from API, but the issue is, I don't know which type of data will come from the server.
Sometimes I am getting String value and sometime Int.
Now how I will save data to the realm.
class Fields: Object {
#objc dynamic var default_value: String? = nil
}
API Response
{
access = 1;
default_value = " ";
},
{
access = 1;
default_value = 20;
}
This is the safest (where stringOrInt is the value you're receiving from the API):
fieldsObject.default_value = stringOrInt as? String
But you can also use string interpolation and inject the value directly into a string literal:
fieldsObject.default_value = "\(stringOrInt)"
You can try this solution
1- Relam object class
class Fields: Object {
#objc dynamic private var default_value: String? = nil
#objc var defaultValue: Any?{
didSet{
self.default_value = "\(defaultValue!)"
}
}
open override class func ignoredProperties()->[String] {
return ["defaultValue"]
}
}
1- Test add object in you'r DB
let obj = Fields()
obj.defaultValue = "ahmad"
let obj2 = Fields()
obj2.defaultValue = 1
let realm = try! Realm()
try! realm.write {
realm.add([obj,obj2])
}
3- Result

using ThreadSafeReference can only get dynamic value

I have a Store, Area table, After called api get the Store Area data, then will create Store , Area table;
Goal: For render ui, i need Store with areaName. fetch stores from db and then compose to stores with areaName, then compose to a 2d array based on areaName in background thread. then pass the 2d array to render ui in main thread
Implementation:
In the background thread read Store, using areaCode get Area, then get areaName, then store.areaName = area.areaName get the composed Store, and using ThreadSafeReference to get the reference of store
In the main thread using realm.resolve to get the store in main thread, but can only get the store.address value, can not get the store.areaName
class Store: Object {
dynamic var storeCode: String = ""
dynamic var areaCode: String = ""
dynamic var address: String = ""
dynamic var phone: String = ""
var areaName: String = ""
var userId: Int = 0
}
class Area: Object {
dynamic var areaCode: String = ""
dynamic var areaName: String = ""
}
//// in background thread//////
let realmInBack = try! Realm()
let stores = getStore(by userId: Int) // here get a list of stores from db
stores.forEach{ (store) in
let area = getArea(by areaCode: String) // get area
let areaName = area.areaName // get the areaName from area
store.areaName = areaName // assign areaName to store
let storeRef = ThreadSafeReference(to: store) // get the storeRef
}
//// in main thread /////
let realm = try! Realm()
let storeInMain = realm.resolve(storeRef)
// then use storeInMain to render ui
Could please list the ways to pass realm object through threads?
I currently know the following ways:
1. ThreadSafeReference(failed in my case)
2. transform the realm object to normal class by myself
For this simple case, you don't actually need to use a ThreadSafeReference object. You should just save a property of your object to a variable you can use to identify that specific object (preferably its primaryKey if you are using primaryKeys), then retrieve the object using its primary key from Realm on the main thread.
var storeCodeRef = "" //make sure this variable is accessible in the scope of where your code on the main thread is called from as well
let realmInBack = try! Realm()
let stores = getStore(by userId: Int) // here get a list of stores from db
stores.forEach{ (store) in
let area = getArea(by areaCode: String) // get area
let areaName = area.areaName // get the areaName from area
store.areaName = areaName // assign areaName to store
//save the unique identifier for the store
storeCodeRef = store.storeCode
}
//// in main thread /////
let realm = try! Realm()
let store = realm.object(ofType: Store.self).filter("storeCode == %#",storeCodeRef).first //assuming storeCodes are unique
If you are using a primary key, you can use
let store = realm.object(ofType: Store.self, forPrimaryKey: storeCodeRef)
Since you declare a new reference to your Realm instance on the main thread and access the Store object from that reference, you won't receive any errors.
Update based on comments:
Your Class model is flawed. All properties that you want to persist in Realm need to be declared using the dynamic keyword, otherwise they cannot be dynamically dispatched to the Obj-C runtime, which Realm uses.
class Store: Object {
dynamic var storeCode: String = ""
dynamic var areaCode: String = ""
dynamic var address: String = ""
dynamic var phone: String = ""
dynamic var areaName: String = ""
dynamic var userId: Int = 0
}
Second of all, why do you store ThreadSafeReferences to individual stores if you want to modify the actual Store objects, you can do that in a write transaction and store a ThreadSafeReference to the Results instance, so you only need to resolve a single reference and not one for each individual Store.
Below code has been tested and is working in a Realm playground.
class Store: Object {
dynamic var storeCode: String = ""
dynamic var address: String = ""
dynamic var phone: String = ""
dynamic var userId: Int = 0
dynamic var areaCode: String = ""
dynamic var areaName: String = ""
}
class Area: Object {
dynamic var areaCode: String = ""
dynamic var areaName: String = ""
}
try! realm.write {
realm.add([Store(value: ["userId":1,"storeCode":"1"]),Store(value: ["userId":1,"storeCode":"2"]),Store(value: ["userId":2,"storeCode":"1"])])
}
print(realm.objects(Store.self))
//// in some thread//////
var storesRef: ThreadSafeReference<Results<Store>>?
DispatchQueue(label: "background").sync {
autoreleasepool{
let realmInBack = try! Realm(configuration: Realm.Configuration(inMemoryIdentifier: "TemporaryRealm"))
let stores = realmInBack.objects(Store.self).filter("userId == 1")
print("Stores for user 1:",stores)
try! realmInBack.write {
let area = Area(value: ["areaCode":"a","areaName":"AreaA"])
realmInBack.add(area)
stores.forEach{ (store) in
store.areaCode = area.areaCode
}
}
print("Modified stores:",stores)
storesRef = ThreadSafeReference(to: stores)
}
}
//// in main thread /////
DispatchQueue.main.async {
let mainRealm = try! Realm(configuration: Realm.Configuration(inMemoryIdentifier: "TemporaryRealm"))
guard let storesRef = storesRef, let storeInMain = mainRealm.resolve(storesRef) else {
print("Couldn't resolve stores reference"); return
}
print("Stores from ThreadSafeReference:",storeInMain)
}

Array appending overwrites last index of Realm object

I have an array of Realm objects and before i save them in Realm DB i have my own array of objects in for loop:
var objs = [self.friendsObject] //0 values at first
for i in (0..<json.count) { //counts 2
let _id = json[i]["_id"] as? String
let userName = json[i]["userName"] as? String
let profile_pic = json[i]["profile_pic"] as? String
let phone = json[i]["phone"] as? String
self.friendsObject.id = _id!
self.friendsObject.username = userName!
self.friendsObject.profilepic = profile_pic!
self.friendsObject.phone = phone!
objs.append(self.friendsObject) //2nd element overwrites 1st one
}
self.friendsObject.save(objects: objs)
So i can see the first object with correct items inside objs before i insert second array, but in second index there are 2 array of objects with same values. i appreciate any help.
Note: It is not duplicate, i have already checked some similar questions but it doesn't apply to my issue.
As Vadian commented, the problem is that the code is not creating new instances of friendsObject but appending the same instance with different values.
Edit
Below an example of how to copy JSON to a class based on the information provided in the question:
// Simulating a JSON structure filled with some data.
var jsonData = [Int: [String: String]]()
for index in 0..<10 {
var values = [String: String]()
values["id"] = "id\(index)"
values["username"] = "username\(index)"
values["profilepic"] = "profilepic\(index)"
values["phone"] = "phone\(index)"
jsonData[index] = values
}
// Friend sample class where JSON data will be copied to.
class Friend {
var id: String
var username: String
var profilepic: String
var phone: String
init(_ id: String, _ username: String, _ profilepic: String, _ phone: String) {
self.id = id
self.username = username
self.profilepic = profilepic
self.phone = phone
}
}
// The array where to copy the values from the JSON data.
var friends = [Friend]()
// Looping through the JSON data with a sorted key.
for jsonSortedKey in jsonData.keys.sorted(by: <) {
// Obtaining a JSON element containing friend data.
let jsonFriend = jsonData[jsonSortedKey]!
// Creating a new friend's instance from the JSON friend's data.
let friend = Friend((jsonFriend["id"]!), jsonFriend["username"]!, (jsonFriend["profilepic"]!), (jsonFriend["phone"]!))
friends.append(friend)
}
The result is this:

Insert object in RLMArray property on already persisted object?

I have a fairly simple and straightforward issue I'm trying to solve with Realm. I have objects which have an array property on them (threads). When I fetch all the threads via our API, they're all persisted into Realm since the parent objects are individually saved and thus all child objects (messages & users) within the array property are properly persisted as well. But during the lifecycle of the app I need to add new messages into that array property. Here's what I'm attempting to do:
func addPubNubMessageToThread(notification: NSNotification) {
if let info = notification.userInfo as? Dictionary<String, AnyObject> {
var embeddedMessage = Message(json: (info["data"] as? NSDictionary)!)
let threadId = (info["thread"]! as String)
// Persist the message to Realm for future use
var respectiveThread = Thread(forPrimaryKey: threadId)
let realm = RLMRealm.defaultRealm()
realm.beginWriteTransaction()
respectiveThread.conversation.insertObject(embeddedMessage, atIndex: UInt(0)) // Always fails here in XCode with the error below
realm.addOrUpdateObject(respectiveThread)
realm.commitWriteTransaction()
}
}
But each time I get the following error:
*** Terminating app due to uncaught exception 'RLMException', reason: 'Setting unique property '_id' with existing value '540729b543dd5d1868a42b5d''
For more context, here are my Realm models:
class Message: RLMObject {
dynamic var _id = ""
dynamic var type = ""
dynamic var text = ""
dynamic var author = User()
dynamic var created = NSDate()
dynamic var lastUpdated = NSDate()
}
class Thread: RLMObject {
dynamic var _id = ""
dynamic var name = ""
dynamic var conversation = RLMArray(objectClassName: Message.className())
dynamic var participants = RLMArray(objectClassName: User.className())
dynamic var created = NSDate()
dynamic var lastUpdated = NSDate()
}
class User: RLMObject {
dynamic var _id = ""
dynamic var name = ""
dynamic var firstName = ""
dynamic var lastName = ""
dynamic var email = ""
dynamic var phone = ""
dynamic var username = ""
dynamic var avatar = NSData()
dynamic var created = NSDate()
dynamic var lastUpdated = NSDate()
}
Each message has a property called author, and the _id it's complaining about is the _id of the author (or user object) of the message. The error message is hard to decipher. I think it's saying that I'm trying to create a new user object with a primary key that already exists. If that's the issue, what should I do instead to add new Realm objects to an array property on an already persisted object?
Edit
I am setting the primary key for each model like so:
override class func primaryKey() -> String {
return "_id"
}
And _id is a GUID generated by MongoDB...so it's globally unique.
The problem is that when calling
insertObject
your object and all child objects are created in the Realm instead of updated if they already exist. If you first explicitly update your object (which applies to all child objects as well), then this should avoid the issue:
var persistedMessage = realm.addOrUpdateObject(embeddedMessage)
respectiveThread.conversation.insertObject(persistedMessage, atIndex: UInt(0))
Your problem is probably in your the Message(json:) initializer. When you process the JSON for the message, I'm guessing the author ID is passed (and perhaps the rest of the author data). In your initializer you are probably instantiating a new User, rather than getting an instance of the existing User in the Realm. I recreated your model objects, and then created an existing user, thread and message. I was able to deserialize a message from JSON and add the new message to the existing conversation array, as long as my deserialization logic grabbed an existing user if it was provided. Here is my example initializer:
init(json : NSDictionary) {
self._id = json["_id"] as String
self.type = json["type"] as? String ?? ""
self.text = json["text"] as? String ?? ""
self.created = json["created"] as? NSDate ?? NSDate()
self.lastUpdated = json["lastUpdated"] as? NSDate ?? NSDate()
if let author = json["author"] as? NSDictionary {
if let authorId = author["_id"] as? String {
self.author = User(forPrimaryKey: authorId)
}
}
super.init()
}
In this case, my example JSON:
{
"_id": "2",
"text": "new message",
"author": {"_id": "1"}
}
And finally, the code that reads the JSON and adds to an already saved thread:
var error: NSError?
var jsonDictionary : NSDictionary!
if let dictionary = NSJSONSerialization.JSONObjectWithData(jsonData, options: NSJSONReadingOptions.allZeros, error: &error) as? NSDictionary {
jsonDictionary = dictionary
} else {
NSLog("\(error)")
return
}
let realm = RLMRealm.defaultRealm()
realm.beginWriteTransaction()
let newMessage = Message(json: jsonDictionary)
self.thread.conversation.addObject(newMessage)
realm.addOrUpdateObject(self.thread)
realm.commitWriteTransaction()

Resources