Update multiple properties in Realm without touching the other (saved) values - swift

When reading the docs, it says I should do the following to update a single (or multiple) field through a dictionary:
var person = ["personID": "My-Primary-Key", "name": "Tom Anglade"]
// Update `person` if it already exists, add it if not.
let realm = try! Realm()
try! realm.write {
realm.add(person, update: true)
}
I have no idea how they can get to compile, because I get this error:
Cannot convert value of type '[String : String]' to expected argument type 'Object'
I want to dynamically update multiple field throughout a dictionary, is this possible?

I don't think that tutorial is correct, even though it's from the official website written by Realm engineers. If you look at the official documentation of RealmSwift, you can see that both versions of the add function accept an argument subclassing Object.
The function you are looking for and that should be mentioned in the tutorial is public func create<T: Object>(_ type: T.Type, value: Any = [:], update: Bool = false) -> T, which you can use to update an existing object from a Dictionary.
Your code should look like:
var person = ["personID": "My-Primary-Key", "name": "Tom Anglade"]
// Update `person` if it already exists, add it if not.
let realm = try! Realm()
try! realm.write {
realm.create(Person.self, value: person, update: true)
}

Related

SwiftUI Query Realm Database

In my Realm DB for ChatMessage I have the following data object:
When i follow the tutorial on retrieving data/query : https://www.mongodb.com/docs/realm/sdk/swift/crud/read/
let realm = try! Realm()
let specificPerson = realm.object(ofType: ChatMessage.self,
forPrimaryKey: ObjectId("6369ee9db15ac444f96eb5d6"))
print(specificPerson!.author as String)
I receive fatal error Nil, it cannot find anything. from the line
print(specificPerson!.author as String)
LiveChat/ChatView.swift:59: Fatal error: Unexpectedly found nil while unwrapping an Optional value
When I do a broader query for all,
let chatM = realm.objects(ChatMessage.self)
print(chatM)
print(type(of: chatM))
I receive empty object
Results<ChatMessage> <0x7fe4d163efa0> (
)
Results<ChatMessage>
I am adding the Chat messages Via
#ObservedResults(ChatMessage.self,
sortDescriptor: SortDescriptor(keyPath: "timeStamp", ascending: true)) var messages
private func addMessage(){
let message = ChatMessage(room: room, author: userName, text: messageText)
$messages.append(message)
messageText = ""
}
Similar to https://github.com/mongodb-developer/LiveTutorialChat/blob/main/iOS/LiveChat/LiveChat/Views/ChatsView.swift
Regarding your updated code:
So yes, you are indeed creating the objects (using the .append(_:) method on an #ObservedResults collection) in a correct way. This means that you are likely opening the wrong realm database when you're querying for the objects. Please have a look at Realm's documentation regarding realm configuration, specifically on how to set the default configuration. Calling try! Realm() without any parameters will use this default configuration to open the database.
Original reply
If let chatM = realm.objects(ChatMessage.self) returns an empty Results object, the realm you're querying does not contain any objects of the ChatMessage type. It's as simple as that. Logically, let specificPerson = realm.object(ofType: ChatMessage.self, forPrimaryKey: ObjectId("6369ee9db15ac444f96eb5d6")) would then also return nil.
Without seeing how and where you're creating the missing ChatMessage objects, it's hard to say what's going wrong. Some common missteps:
You are querying the wrong realm database: If you are only every accessing realm through let realm = try! Realm() this shouldn't be a problem.
You haven't actually added any ChatMessage object to the realm database: Simply initializing an object is not enough, You need to explicitly add objects to the Realm database using either Realm.create() or Realm.add():
let realm = try! Realm()
let exampleObject = ChatMessage()
print(realm.objects(ChatMessage.self).count) // Prints "0"
// Add to the realm using either create() or add()
realm.add(exampleObject)
print(realm.objects(ChatMessage.self).count) // Prints "1"

Chained RxSwift request with Realm add() / setValue()

Say I have 2 functions with 2 different Observable return types :
func getWatchedMovies() -> Observable<[TraktMovie]>
func getDetails(id: Int, language: String) - > Observable<TMDbMovie>
I'd like to flatMap each value in my getWatchedMovies() request to be able to request the details of each movie like this (I'm not sure it's the best way to do it though..)
traktDataManager?
.getWatchedMovies()
.flatMap({ (traktMovies) -> Observable<[TraktMovie]> in
let moviesObs = Observable.from(traktMovies)
let movieDetails = moviesObs.flatMap {
self.tmdbDataManager!.getMovieDetails(id: $0.ids.tmdb, language: Device.lang)
}
})
The thing is, I need to add each TraktMovie to Realm AND update a TraktMovie property, named tmdbMovie, with the nested request value of type TMDbMovie in Realm too.
What I mean is :
first, I need to loop in my [TraktMovie] array to save each value of it in Realm (say an object named traktMovie)
for traktMovie in traktMovies {
let realm = try! Realm()
realm.write {
realm.add(traktMovie)
}
}
second, I need to retrieve the details of each TraktMovie object with the second request (e.g. getDetails(_ , _)) : with something like flatMap ?
third, I need to update each traktMovie object property as follow with the value retrieved with the getDetails request (say tmdbMovie for the retrieved value):
traktMovie.setValue(tmdbMovie, forKeyPath: "tmdbMovie")
Here I have an object retrieved from the first request(getWatchedMovies()) named traktMovie and I update one of its property named tmdbMovie with the object retrieved from the second request (getDetails(_, _)) also named tmdbMovie
The thing is my first request returns an array and the second only a single object.
If I return the TMDbMovie object, I got only one object with onNext event and I loose my [TraktMovie] array.
Hope I'm clear enough.
Help is really appreciated ! 😅
You can try to use Observable.zip for this as in example below:
getWatchedMovies()
.flatMap({ [unowned self] (traktMovies) -> Observable<[TraktMovie]> in
let movieDetails = traktMovies.flatMap { movie in
// you can save in realm here
return Observable.just(movie)
.withLatestFrom(self.getMovieDetails(id: 0, language: "")) { movie, details in
// here you have both movie & movieDetails
return movie
}
}
return Observable.zip(movieDetails, { return $0 })
})
It may be a bit risky, if one of getMovieDetails will fail it will fail whole stream, as well it will require all getMovieDetails to emit onNext event in order that zipped Observable to emit a value.

RealmSwift. Difficulties initializing List after converting to Swift 4

I've got a task to convert an existing project to Swift4 (from Swift 2.3, don't ask O_o). Managed to fix almost everything except this issue using RealmSwift:
Old code:
class func myFunc() -> List<MyClass> {
let realm = try! Realm()
return List(realm.objects(MyClass).sorted("name", ascending: true))
}
Getting compiler error for the return statement:
Argument passed to call that takes no arguments
When I trying to fix like this, compiler is silent, but the function doesn't do its job:
return List<MyClass>()
So which is then the right way to initialize List with a collection of custom objects? Please, help!
List doesn't have an initializer accepting a Results instance in RealmSwift 3.1.0 (I'm not sure since when though). List's only initializer doesn't take any input arguments and it creates an empty List instance.
You can work around this by creating an empty List using the only initializer, then calling append to add the elements of the Results collection to the list.
func myFunc() -> List<MyClass> {
let realm = try! Realm()
let list = List<MyClass>()
list.append(objectsIn: realm.objects(MyClass.self).sorted(byKeyPath: "name", ascending: true))
return list
}

How to fetch core data objects into Dictionary in Swift?

I've saved objects in core data, and I am looking how to fetch those objects as a Dictionary
Here is an example of my code where sections is keys for the dictionary and Company as an array of core data objects.
private var companies = Dictionary<String, Array<Company>>()
private var sections: [String] = ["Pending", "Active", "Pending"]
override func viewWillAppear(_ animated: Bool) {
let fetchRequest : NSFetchRequest<Company> = Company.fetchRequest()
let moc = DatabaseController.getContext()
do {
let request = try moc.fetch(fetchRequest)
for case let (index, object) in request.enumerated() {
companies[sections[index]]!.append(object)
}
} catch let error as NSError {
print("Could not fetch. \(error), \(error.userInfo)")
}}
When I am trying to execute my code, I have an error:
fatal error: unexpectedly found nil while unwrapping an Optional value
Could anyone help me with that issue?
That error message means that you're force unwrapping an optional that doesn't have a value. In other words you're using ! where you shouldn't. (You should basically never use the force unwrap operator (!).)
Let's look at the line where you do that:
companies[sections[index]]!.append(object)
If we break this down and add the inferred types we have:
let section: String? = sections[index]
let companyArray: Array<Company> = companies[section]!
You're crashing because companies starts off empty, so asking for any of the arrays will return nil. (Actually, I'm not sure how your code is compiling, since you can't subscript into the dictionary with an optional.)
However, if you fix that, we still have a problem because you're using the index of the fetched array to look up the section. If you have more than three companies, that will start to fail.
I suspect you want something like:
for let company in result {
if var companyArray = companies[company.status] {
companyArray.append(company)
} else {
companies[company.status] = [company]
}
}
Where status is an invented property on Company that returns a String like "Pending" or "Active".
I've found the solution, just need to use NSFetchResultController in order to display data in TableView by different sections.

Delete all data from specific Realm Object Swift

Before i get too far into my question. My goal, which may influence your answers, is to remove Object data if it is no longer in the cloud.
So if I have an array ["one", "two", "three"]
Then in my server I remove "two"
I want my realm to update the change.
I figure the best way to do this is to delete all data in the specific Object, then call my REST API to download the new data. If there is a better way, please let me know.
Okay so here is my problem.
I have an Object Notifications()
every time my REST API is called, before it downloads anything I am running this:
let realm = Realm()
let notifications = Notifications()
realm.beginWrite()
realm.delete(notifications)
realm.commitWrite()
I get this error after running: Can only delete an object from the Realm it belongs to.
so i tried something like this:
for notification in notifications {
realm.delete(notification)
}
realm.commitWrite()
The error I get within xcode is this: "Type Notifications does not conform to protocol 'SequenceType'
Not really sure where to go from here.
Just trying to figure out realm. Completely new to it
Note: realm.deleteAll() works, but I don't want all of my realm deleted, just certain Objects
You're looking for this:
let realm = Realm()
let deletedValue = "two"
realm.write {
let deletedNotifications = realm.objects(Notifications).filter("value == %#", deletedValue)
realm.delete(deletedNotifications)
}
or perhaps this:
let realm = Realm()
let serverValues = ["one", "three"]
realm.write {
realm.delete(realm.objects(Notifications)) // deletes all 'Notifications' objects from the realm
for value in serverValues {
let notification = Notifications()
notification.value = value
realm.add(notification)
}
}
Although ideally, you'd be setting a primary key on Notifications so that you can simply update those existing objects rather than taking the extreme approach of nuking all your local objects simply to recreate them all (or almost).