Realm.create will it update the object with the same primary key? - swift

I am just curious, if I call realm.create, will it auto update realm object from the realm results?
// Assuming a "Book" with a primary key of `1` already exists.
try! realm.write {
realm.create(Book.self, value: ["id": 1, "price": 9000.0], update: true)
// the book's `title` property will remain unchanged.
}
Currently it seems like I need to read from realm again to get the latest object. Do correct me if I'm wrong.
Thanks

Yes, specifying update: true when calling Realm.create(_:value:update:) will result in the existing object being updated.
Here's a snippet based on the code you provided that demonstrates this:
class Book: Object {
dynamic var id = ""
dynamic var title = ""
dynamic var price = 0.0
override class func primaryKey() -> String? { return "id" }
}
let realm = try! Realm()
let book = Book(value: ["1", "To Kill a Mockingbird", 9.99])
try! realm.write {
realm.add(book)
}
let results = realm.allObjects(ofType: Book.self)
try! realm.write {
realm.createObject(ofType: Book.self, populatedWith: ["id": "1", "price": 7.99], update: true)
}
print(book)
print(results)
This code produces the following output:
Book {
id = 1;
title = To Kill a Mockingbird;
price = 7.99;
}
Results<Book> (
[0] Book {
id = 1;
title = To Kill a Mockingbird;
price = 7.99;
}
)
As you can see the price property of the existing objects has updated to the new value.

In case someone stumbles upon this question again, there are two ways to upsert for Realm in Swift. And to answer your question, both will result in the existing object being updated.
If you have the object, upsert with Realm.add(_:update:).
try! realm.write {
realm.add(bookToUpsert, update: .modified)
}
If you have JSON, use Realm.create(_:value:update:)
try! realm.write {
realm.add(Book.self, value: ["id": "1", "price": 7.99], update: .modified)
}
I pass .modified to both, but update takes an UpdatePolicy. Which can be .error, .modified, .all.

Related

Filter an (Codable) array by another array

I'm trying to filter my json data by IDs (trying mark some favourites and filter using it)
struct workoutList : Codable {
let id : Int
let title : String
let tag : String
}
func selectedWorkoutGroup(libraryFilter: Int, jsonErgWorkouts:[workoutList], workoutGroupBox: UITextField) -> [workoutList] {
var selectedGroup = [workoutList]()
let workoutFav = [1,10,100]
if libraryFilter == 0 {
// This works because I'm filtering based on 1 specific item
selectedGroup = jsonErgWorkouts.filter { $0.tag == workoutGroupBox.text }
} else if libraryFilter == 1 {
// Here I want to filter and show only the favorites
selectedGroup = jsonErgWorkouts.filter { $0.id } //
print("selectedGroup:\(selectedGroup)")
}
return selectedGroup
}
in the above code, the filter works when I have 1(one) something specific item to filter and then I get the entire json array with that tag.
Now I want to implement a favorite list, where the user selects for example ID == [1, 10 ,100] as their favourite.
How can I use the filter command to do it? I tried a few things and searched through SO (but doesn't work). Most of the answers are based on filtering based on specific items eg:
selectedGroup = jsonErgWorkouts.filter { workoutFav?.contains($0.id) }
edit: (omitted that I am using/storing the favourites in userDefaults. This code gives the error of "type of expression is ambiguous without more context"
func selectedWorkoutGroup(libraryFilter: Int, jsonErgWorkouts:[workoutList], workoutGroupBox: UITextField) -> [workoutList] {
var selectedGroup = [workoutList]()
UserDefaults.standard.set([1,10,100], forKey: "workoutFavorite")
/// This one gets stored as [Any] so I cast it to [Int]
let workoutFav = UserDefaults.standard.array(forKey: "workoutFavorite") as? [Int]
if libraryFilter == 0 {
// This works because I'm filtering based on 1 specific item
selectedGroup = jsonErgWorkouts.filter { $0.tag == workoutGroupBox.text }
} else if libraryFilter == 1 {
selectedGroup = workoutFav.flatMap { favouriteId in // for each favourite ID
jsonErgWorkouts.filter { $0.id == favouriteId } // This returns Error "type of expression is ambiguous without more context"
} // flatMap joins all those arrays returns by "filter" together, no need to do anything else
print("selectedGroup:\(selectedGroup)")
}
return selectedGroup
}
Final Solution:
Changing from This
let workoutFav = UserDefaults.standard.array(forKey: "workoutFavorite") as? [Int]
to This (notice the as! instead of as?)
let workoutFav = UserDefaults.standard.array(forKey: "workoutFavorite") as! [Int]
works using #sweeper's answer. Thanks
Update:
Figured out why this error occurred "type of expression is ambiguous without more context" when casting the output of UserDefaults as? [Int] and had to use as! [Int]
But using as! [Int] force unwrapping it causes app to crash if the user did not have any favorites saved into the UserDefault. (Which I then had to code around) like below
var workoutFav = [Int]()
if !(UserDefaults.standard.array(forKey: "workoutFavorite") == nil) {
workoutFav = UserDefaults.standard.array(forKey: "workoutFavorite") as! [Int]
}
Which was then simplified and removed the force unwrapping based on this SO https://stackoverflow.com/a/37357869/14414215 to become this one-line
let workoutFav = UserDefaults.standard.array(forKey: "workoutFavorite") as? [Int] ?? [Int]()
You need to do that filter for each id in the favourites array. You get an array of arrays as a result. To get the final array, you need to join those arrays to a single array. This "map each thing to an array and join the arrays" operation is what a flatMap does:
workoutFav.flatMap { favouriteId in // for each favourite ID
jsonErgWorkouts.filter { $0.id == favouriteId } // find workouts that match the ID
} // flatMap joins all those arrays returns by "filter" together, no need to do anything else
First thing first please give a struct name with a capital so you can distinguish between instance of it. Second you need to have new array where you will store each favorite, and store permanently that array, core data or some base on server, form there you will fetch favorites.
The better way is to add property like isFavorite: Bool that is false by default, and if user change it you can set it to be true, in that way you can avoid using ids for that and you can store whole workout's in one array to core data or base that you use, after that you can fetch from there with
let favorites = workouts.compactMap { $0.isFavorite == true }
Here you go in that way, but just to mention it highly recommended that you store those type of data outside User defaults.
struct Fav {
let name: String
let id: String
}
let df = UserDefaults.standard
let jk = ["aaa", "bbb", "cccc"]
df.setValue(jk, forKey: "favorites")
let fav1 = Fav(name: "zzz", id: "aaa")
let fav2 = Fav(name: "bbb", id: "qqq")
let favs = [fav1, fav2]
let favIDs = df.value(forKey: "favorites") as? [String]
favIDs?.forEach({ (id) in
let f = favs.filter({$0.id == id}) // here it is
})

Realm query problem with sorted localized data

Consider the following Realm models:
class Fruit: Object {
#objc dynamic var name = ""
let localizations = List<Localization>()
/**
Returns the localized name of the fruit matching the preferred language of the app
or self.name if the fruit does not have a localization matching the user preferred language codes.
*/
var localizedName: String? {
guard !Locale.isPreferredLanguageDefaultAppLanguage else { return self.name }
let preferredLanguagesCodes = Locale.preferredLanguagesCodes
let localizations = preferredLanguagesCodes.compactMap({ languageCode in Array(self.localizations).filter({ $0.languageCode == languageCode }).first })
return localizations.first?.localizedName ?? self.name
}
}
class Localization: Object {
#objc dynamic var localizedName: String = ""
#objc dynamic var languageCode: String = ""
}
Let's say I have 2 fruits in my database (represented in JSON format for the sake of simplicity):
[
{
"name": "Apple",
"localizations": [
{
"localizedName": "Pomme",
"languageCode": "fr"
}
]
},
{
"name": "Banana",
"localizations": [
{
"localizedName": "Banane",
"languageCode": "fr"
}
]
}
]
Now I want to get all the fruits in my database, and sort them alphabetically by their localizedName.
var localizedNameSortingBlock: ((Fruit, Fruit) -> Bool) = {
guard let localizedNameA = $0.localizedName, let localizedNameB = $1.localizedName else { return false }
return localizedNameA.diacriticInsensitive < localizedNameB.diacriticInsensitive
}
let sortedFruits = Array(Realm().objects(Fruit.self)).sorted(by: localizedNameSortingBlock)
If the first preferred language of my device is "English", I get this:
Apple
Banana
If it's set to "French":
Banane
Pomme
It's quite simple, but this solution has a major inconvenient:
By casting the Results<Fruit> collection into an array, I'm loosing the ability to get live updates via Realm's notification token system.
The problem is, I can't sort using NSPredicate directly on the Results collection, because the localizedName property is a computed property, thus is ignored by Realm.
I thought about writing the localizedName value directly into the name property of the Fruit object, but doing so requires to loop through all fruits and change their name whenever the user change of preferred language. There must be a better way.
So my question is:
Is there a way to retrieve all the fruits in my database, get them sorted by their localizedName, without loosing the ability to receive batch updates from Realm?

Why can't I get the index of a filtered realm List?

I'm trying to find the index of an item in a List<> object after the item has been appended to it, so that I can insert into a tableview.
The tableview is sectioned with .filters so I have to apply the filter before looking for the indexPath. However, the filter appears to break the indexOf functionality.
I noticed that the function .map has the same effect.
import UIKit
import RealmSwift
class Model: Object {
#objc dynamic var title: String = ""
let items = List<Model>()
}
class ViewController: UIViewController {
var models: Results<Model>?
var parentModel: Model?
var items = List<Model>()
let realm = try! Realm()
override func viewDidLoad() {
super.viewDidLoad()
if !UserDefaults.standard.bool(forKey: "IsNotFirstTime") {
populateRealm()
UserDefaults.standard.set(true, forKey: "IsNotFirstTime")
}
models = realm.objects(Model.self)
parentModel = models!.first
items = parentModel!.items
let child = Model()
child.title = "Child"
try! realm.write {
parentModel!.items.append(child)
}
print(items.index(of: child)) // prints correct value
print(items.filter({ $0.title == "Child" }).index(of: child)) // prints nil
}
func populateRealm() {
let parent = Model()
parent.title = "Parent"
try! realm.write {
realm.add(parent)
}
}
}
The first print finds the object, but the second print doesn't, despite the mapping having no overall effect.
The strange thing is that the object IS in the filtered list, doing:
print(items.filter({ $0.title == "Child" }).first
Returns the object, so it is there.
Edit
On further inspection, it looks like it's not the filter but the conversion of array type that breaks the functionality, converting to array without a filter does the same thing.
print(Array(items).index(of: child)) // prints nil
When you want to use mapping you should add an attributes to map your objects according to it for example
print(items.map({ $0.id }).index(of: child.id))
if you use it on this way it will return what you expected
I figured out the solution. The filter syntax I used .filter({ $0.title == "Child" }) isn't the Realm filter, and converts the List to a LazyFilterCollection<List<Model>>, which doesn't seem to be compatible with searching for the index of a realm object.
The fix was to use the format .filter("title == %#", "Child"), which returns a realm Results object.

How to store data in form of object into firestore using swift

I want to store data into object form using swift language.The data structure of the data base is like
collection/
document/
collection/
document1/:
Invitess1(object) :
name :"santosh"
phone :1234567890
Invitee2(object) :
name :"sam"
phone:1234654768
.....
document 2/
Initee1(object) :
name:"red"
phone:4654343532
.......
is it possible to store data like this? if possible how to do it? i tried like this :
for var i in 0..<n { // n is no.of selected contacts
for var j in i...i {
print("amount is \(d[i])")
print("phone number is \(num[j])")
let dataToSave:[String: Any] = ["name" :"vijayasri",
"PhoneNumber":num[j],
"Amount": d[i],
]
}
}
var ref:DocumentReference? = nil
ref = self.db.collection("deyaPayUsers").document("nothing").collection("Split").addDocument(data: dataToSave){
error in
if let error = error {
print("error adding document:\(error.localizedDescription)")
} else {
print("Document ades with ID:\(ref!.documentID)" )
}
}
}
But it doesn't work. How to do it..
Your example code is never going to work as intended since dataToSave is overwritten every iteration of the j loop. Your inner j loop probably has a typo at i...i
To store multiple objects in one document, create the document in Swift with multiple objects in it. Since you know how to encode your object as [String:Any], just take those dictionaries combine into a larger [String:Any]document.
I would change your code to be more like:
var dataToSave: [String:Any] = []()
for var i in 0..<n { // n is no.of selected contacts
var inProcess: [String:Any] = []()
for var j in i...i {
print("amount is \(d[i])")
print("phone number is \(num[j])")
let detail: [String: Any] = ["name" :"vijayasri",
"PhoneNumber":num[j],
"Amount": d[i]]
inProcess["NextKey\(j)"] = detail
}
dataToSave["SomeKey\(i)"] = inProcess
}
var ref:DocumentReference? = nil
ref = self.db.collection("deyaPayUsers").document("nothing").collection("Split").addDocument(data: dataToSave){
error in
if let error = error {
print("error adding document:\(error.localizedDescription)")
} else {
print("Document ades with ID:\(ref!.documentID)" )
}
}
}

Firebase/Swift queryOrder then match value

I asked a question yesterday that was marked as a duplicate, and when I updated the question it was not unmarked. So I am asking again here (as per stackoverflow's recommendation).
I am trying to sort by multiple values in firebase. I understand that is not possible, but i was given an example in another language which is only half helpful as how to go about doing it the right way. In any case i tried to follow the example given here Query based on multiple where clauses in firebase .
This is the structure of my firebase
room
-KJe22sduQMz1DIs_DH6
allowedParticipants:
14
createdBy:
"Mr Tester"
members:
"nmeMYnnSatRch5qKPJKIe7jEOLy2"
participating:
true
status:
"seedling"
theme:
"Cats"
totalNumberOfMembers:
1
and this is the code that I am trying to get to work
ref.queryOrderedByChild("status").queryStartingAtValue("active").queryEndingAtValue("active").observeEventType(.Value) { (snapshot: FIRDataSnapshot) in
let themeOfEvent = snapshot.value
if themeOfEvent?.value == pickedTheme {
print("foo")
}
}
Could somebody please post a useful comment or answer to help me?
Thank you
I was able to get help
This works
func listOfPossibleCompetitionsFromFirebase(){
let createdRoomRef = firebase.child("room")
createdRoomRef.queryOrderedByChild("status").queryStartingAtValue("active").queryEndingAtValue("active").observeEventType(.Value) { (snapshot: FIRDataSnapshot) in
var themeCount = 0
self.listOfOpenComps.removeAll()
if let tmp = snapshot.value as? [String:AnyObject] {
let keys = tmp.keys
for key in keys {
if let roomDetails = (tmp[key] as? [String:AnyObject]) {
if let themeOfEvent = roomDetails["theme"] as? String where themeOfEvent == pickedTheme {
themeCount += 1
self.listOfOpenComps.append(key)
}
}
}
}
dispatch_async(dispatch_get_main_queue(), {
self.tableView.reloadData()
})
print("rooms count: \(themeCount)")
}
}