Realm partial update using createOrUpdate doesn't work - ios

I am trying to use Realm for my iOS app. When updating the local Realm DB using createOrUpate, it rewrites the unprovided properties with default values, rather than keep them unchanged. The Realm I use is up to date, 0.93. Anybody has the same issue?
let realm = RLMRealm.defaultRealm()
realm.beginWriteTransaction()
for matchedUser in matchedUsers {
let newMatchedUser = MatchedUser()
newMatchedUser.objectId = matchedUser.objectId
newMatchedUser.username = matchedUser.username
newMatchedUser.email = matchedUser.email
newMatchedUser.fullname = matchedUser["fullname"] as! String
//they are other properties unprovided here.
MatchedUser.createOrUpdateInDefaultRealmWithValue(newMatchedUser)
}
realm.commitWriteTransaction()

So, I figured out what the issue was. It turns out you cannot user newMachtedUser to update the DB because it will initialize it first and the default values will be provided for this initialization process. The right way is to using individual values to update, or create an dictionary/array for that update.

Related

How to create initial Realm objects that get added upon installation of app

Say I am creating an object that takes two strings and acts like a dictionary.
class WordInDictionary: Object {
#objc dynamic var word: String = ""
#objc dynamic var meaning: String = ""
What should I do if I wanted to have some initial objects that get added to the database just once upon installation/update of the app?
Also, is there a way to make it so that just those initial objects can't be deleted?
"What should I do if I wanted to have some initial objects that get added to the database just once upon installation/update of the app?"
One option would be to have some code near the realm initialisation that checks if there are any WordInDictionary objects already in the realm - if not then add the required default objects.
E.g.
let realm = try! Realm()
if realm.objects(WordInDictionary.self).isEmpty
{
// Add required words here
}
"Also, is there a way to make it so that just those initial objects can't be deleted?"
I don't know of a way to make realm objects read-only. You'd have to implement this in code in some way, e.g. have a isDeletable boolean member which is true for every user-created object and false for your default members, then only delete those from realm.
E.g. for your deletion code:
func deleteWords(wordsToDelete: Results<WordInDictionary>)
{
try! realm.write
{
realm.delete(wordsToDelete.filter("isDeletable = true")
}
}

Crash on assigning value to a new property using Realm

I basically want to increment a value on performing an action. So what I'm doing is when the action is performed the first time, I assign the value of 1 to a new property in Realm and then I add it to the realm database.
The second time the action is performed, the max value is taken from all the values in the property and that max value is incremented by 1 and then added to the database.
But what happens is the first time itself, when I reach the line realm.add(chatMsgObj) a crash occurs saying Can only add, remove, or create objects in a Realm in a write transaction - call beginWriteTransaction on an RLMRealm instance first.
This is the code..
func incrementID() -> Int {
let realm = try! Realm()
var myvalue = realm.objects(ChatMessage.self).map{$0.mainId}.max() ?? 1
myvalue = myvalue + 1
chatMsgObj.mainId = myvalue
realm.add(chatMsgObj) //CRASH HERE
return myvalue
}
Can only add, remove, or create objects in a Realm in a write transaction - call beginWriteTransaction on an RLMRealm instance first.
You need to open realm then write.
Read more about realm here, and this code should work for you now.
try! realm.write {
realm.add(chatMsgObj)
}
EDIT: from the comments below you having another problem: which is writing on the same primary key, take a peek into the documentation of realm again, and this could be a configuration problem however,
you can try to delete the application from the (Simulator/Device) that you are testing on it, and rerun the application again.
And you came a cross a
Attempting to create an object of type 'ChatMessage' with an existing
primary key value '0'
so with this new crash you have 2 options.
1- change the PrimaryKey of the new object that it being written.
2- fetch the object of that PrimaryKey and modify it.
Now for the fetching and modifying part, take a peek on this code you should have an idea.
let realm = try! Realm()
let myChatObject = realm.objects(Dog.self).filter("youPrimaryKey == 1").first
try! realm.write {
myChatObject!.something = newValue
}

Firebase overwriting entries

I am new to Firebase and not sure how to best explain this but I will try.
I am trying to have my app create an entry for each user. Then each user entry has multiple (0 through n) sub-entries where each sub-entry is a simple string. Basically there is a user-id (the main entry) and their tasks are the sub-entries.
Now my problem is whenever I push data (the sub-entries) to the main entries, all of the previous sub-entries are deleted and only the most recent one is pushed. I have been looking through the documentation and Googling like crazy but nothing seems to work.
I have tried this:
#IBAction func testWrite(sender: AnyObject) {
let def = NSUserDefaults.standardUserDefaults()
let uid = def.valueForKey("uid")
let root = Firebase(url: getFirebaseURL())
let text = self.tempText.text!
let dataRef = root.childByAppendingPath(uid as! String)
let data = ["test": String(text)]
dataRef.setValue(data)
}
Which appends to the user-id entry fine, with a key of "test" and a value of the 'text'
So then I kill the app and change it to:
#IBAction func testWrite(sender: AnyObject) {
let def = NSUserDefaults.standardUserDefaults()
let uid = def.valueForKey("uid")
let root = Firebase(url: getFirebaseURL())
let text = self.tempText.text!
let dataRef = root.childByAppendingPath(uid as! String)
let data = ["CHANGED": String(text)]
dataRef.setValue(data)
}
And it pushes fine, but then the previous entry was just deleted and the only entry left is this one.
What I am trying to do is maybe incrementally (having a numbered key possibly?) add items one by one without having other entries deleted.
I hope this makes sense :P and any help is greatly appreciated!
What is happening here is, you are setting the entire branch (Users/UserID##), to a value, and that value is a single node Changed:<somestring>
Conceptually, it may help to think of the key you want to set as being just another branch e.g (Users/UserID##/TaskID##)
So conceptually, instead of approaching it like this:
Users/UserID = Key:Value
Approach it like this:
Users/UserID/Key = Value
Note: the branch Users/UserID/Key does not have to exist prior to you assigning it a value.
e.g you could change your reference to point at the subkey you want to add or change:
let dataRef = root.childByAppendingPath(uid as! String + "/Task001")
dataref.setValue(String(text))
I concur that what you are doing is a great way to start learning Firebase, and how it works. But once you get going, instead of generating and using your own key for your list of subtasks, do look into childByAutoId, it will automatically create the subkeys for you, plus much more, and is much easier to manage and code.
Documentation Here
Edit: Suggest referring to Frank's better answer below.
An alternative to #MtlDev's answer would be to use updateChildValues():
let data = ["CHANGED": String(text)]
dataRef.updateChildValues(data)
While setValue() replaces the current data with the new value, updateChildValues() updates it in place.
See the Firebase documentation on updating saved data.

Fetching Single Row in Realm

I'm having a problem in fetching a single row in a table. My problem is that I cannot display it. Here is what I have so far
let feed: RLMObject = FeedsModel.objectsWhere("id = 1").firstObject()!
print(feed.title)
Thank you very much!
You should fetch the single object with primary key like this:
let realm = try! Realm()
let feeds = realm.objectForPrimaryKey(FeedsModel.self, key: "1")
You're attempting to access the title property of RLMObject, which doesn't exist. This property only exists on FeedsModel. Instead, you should cast the object as a FeedsModel:
let feed = FeedsModel.objectsWhere("id = 1").firstObject() as! FeedsModel
print(feed.title)
If you'd like to use Realm from Swift with nicer generics, I'd encourage you to consider using Realm Swift instead of Realm Objective-C.

Use Realm with Collection View Data Source Best Practise

I'll make it short as possible.
I have an API request that I fetch data from (i.e. Parse).
When I'm getting the results I'm writing it to Realm and then adding them to a UICollectionView's data source.
There are requests that take a bit more time, which run asynchronous. I'm getting the needed results after the data source and collection view was already reloaded.
I'm writing the needed update from the results to my Realm database.
I have read that it's possible to use Realm's Results. But I honestly didn't understood it. I guess there is a dynamic and safe way working with collection views and Realm. Here is my approach for now.
This is how I populate the collection view's data source at the moment:
Declaration
var dataSource = [Realm_item]()
where Realm_item is a Realm Object type.
Looping and Writing
override func viewDidLoad() {
super.viewDidLoad()
for nowResult in FetchedResultsFromAPI
{
let item = Realm_item()
item.item_Title = nowResult["Title"] as! String
item.item_Price = nowResult["Price"] as! String
// Example - Will write it later after the collectionView Done - Async request
GetFileFromImageAndThanWriteRealm(x.image)
// Example - Will write it later after the collectionView Done - Async request
dataSource.append(item)
}
//After finish running over the results *Before writing the image data*
try! self.realm.write {
self.realm.add(self.dataSource)
}
myCollectionView.reloadData()
}
After I write the image to Realm to an already created "object". Will the same Realm Object (with the same primary key) automatically update over in the data source?
What is the right way to update the object from the data source after I wrote the update to same object from the Realm DB?
Update
Model class
class Realm_item: Object {
dynamic var item_ID : String!
dynamic var item_Title : String!
dynamic var item_Price : String!
dynamic var imgPath : String?
override class func primaryKey() -> String {
return "item_ID"
}
}
First I'm checking whether the "object id" exists in the Realm. If it does, I fetch the object from Realm and append it to the data source. If it doesn't exist, I create a new Realm object, write it and than appending it.
Fetching the data from Parse
This happens in the viewDidLoad method and prepares the data source:
var query = PFQuery(className:"Realm_item")
query.limit = 100
query.findObjectsInBackgroundWithBlock { (respond, error) -> Void in
if error == nil
{
for x in respond!
{
if let FetchedItem = self.realm.objectForPrimaryKey(Realm_item.self, key: x.objectId!)
{
self.dataSource.append(FetchedItem)
}
else
{
let item = Realm_item()
item.item_ID = x.objectId
item.item_Title = x["Title"] as! String
item.item_Price = x["Price"] as! String
let file = x["Images"] as! PFFile
RealmHelper().getAndSaveImageFromPFFile(file, named: x.objectId!)
self.dataSource.append(item)
}
}
try! self.realm.write {
self.realm.add(self.dataSource)
}
self.myCollectionView.reloadData()
print(respond?.count)
}
}
Thank you!
You seem to have a few questions and problems here, so I'll do my best.
I suggest you use the Results type as your data source, something like:
var dataSource: Results<Realm_item>?
Then, in your viewDidLoad():
dataSource = realm.objects(Realm_item).
Be sure to use the relevant error checking before using dataSource. We use an optional Results<Realm_item> because the Realm object you're using it from needs to be initialised first. I.e., you'll get something like "Instance member * cannot be used on type *" if you try declaring the results like let dataSource = realm.objects(Realm_item).
The Realm documentation (a very well-written and useful reference to have when you're using Realm as beginner like myself), has this to say about Results...
Results are live, auto-updating views into the underlying data, which means results never have to be re-fetched. Modifying objects that affect the query will be reflected in the results immediately.
Your mileage may vary depending on how you have everything set up. You could try posting your Realm models and Parse-related code for review and comment.
Your last question:
What is the right way to update the "object" from the Data Source after i wrote the update to same object from the Realm DB?
I gather you're asking the best way to update your UI (CollectionView) when the underlying data has been updated? If so...
You can subscribe to Realm notifications to know when Realm data is updated, indicating when your app’s UI should be refreshed for example, without having to re-fetch your Results.

Resources