I am creating a project. I want if the userID already exist, it doesn't add the user. But somehow my code isn't working properly.
This is my Realm Model Object (User.swift):
import Foundation
import RealmSwift
class User: Object {
#objc dynamic var userID = Int()
#objc dynamic var username = ""
#objc dynamic var full_name = ""
#objc dynamic var myBool = Bool()
override static func primaryKey() -> String? {
return "userID"
}
}
And this is the button to add users:
#IBAction func add(_ sender: Any) {
let myUser = User()
let JSON_userID = Int(arc4random_uniform(5)) // This is temporary. I am going to get code from JSON, but using random for testing purpose.
if (myUser.userID != JSON_userID) {
myUser.userID = JSON_userID
myUser.username = "myUsername"
myUser.full_name = "My Name"
let realm = try! Realm()
try! realm.write {
realm.add(myUser)
}
}
else {
print("Already exist")
}
}
Sometimes it runs the code, but most of the times it crashes with error:
libc++abi.dylib: terminating with uncaught exception of type NSException.
As you defined a primary key in your User object, Realm can handle this automatically if you set the update parameter to true inside the write closure.
let realm = try! Realm()
try! realm.write {
realm.add(myUser, update: true)
}
If the update parameter is not set or false, Realm will throw an exception when you try to add an object with an existing primary key.
This makes the if / else condition useless. It can be removed.
If you need to know if the user already exists, you can request the Realm with the primary key value:
realm.object(ofType: User.self, forPrimaryKey: JSON_userID)
The result will be nil if the user does not exist.
Related
I am using RealmSwift in a project. I have a model as below
#objc final class Diary : Object, Codable {
#objc dynamic public var id: Int = 1
#objc dynamic public var notes: String = ""
}
public func persistDiary(){
let realm = StorageServiceManager.shared.getRealm()
do{
try realm.write {
realm.add(self)
}
}catch{
debugPrint(error)
}
}
I wrote few Diary objects to the REALM db. I was able to fetch them also using below code
let realm = StorageServiceManager.shared.getRealm()
let notes = realm.objects(Diary.self)
After fetching those objects, I just tried updating a property of an object and the app got crashed. The code for that is as below,
var currentNotes = notes[0]
currentNotes.id = 2//This line leads to the crash
currentNotes.notes = "testing"
Console message: libc++abi.dylib: terminating with uncaught exception of type NSException
Any help will be great, Thanks.
You need to update your object inside a write transaction. Your code should look something like:
let realm = try! Realm()
let notes = realm.objects(Diary.self)
if let currentNotes = notes[0] {
try! realm.write {
currentNotes.id = 2//This line leads to the crash
currentNotes.notes = "testing"
}
}
To make a copy of your object, you can do it like this:
let currentNoteCopy = Diary(value: notes[0])
currentNoteCopy.id = 2
Working Code
Inside my tableView's didSelectRowAt IndexPath method, I have a call that updates the UserSave model, which is in the default realm. The function is structured like so:
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let UserRealm = UserSave()
let realm = try! Realm()
try! realm.write {
UserRealm.articleLink = newsLink[indexPath.row]
UserRealm.articleBody = newsData[indexPath.row]
UserRealm.articleTitle = newsTitle[indexPath.row]
UserRealm.articleAuthor = newsSrc[indexPath.row]
}
performSegue(withIdentifier: "webKitSegue", sender: self)
}
When this is run, the realm updates with the new values, as it should.
Problem Code
I have a second model, UserPrefs, which is also a part of the default realm. It is called inside function exrefresh(writeToRealm: String). The function looks like this:
func exrefresh(passed: String) {
let UserRealm = UserPrefs()
let realm = try! Realm()
try! realm.write {
UserRealm.fetchUrl = passed
}
self.refreshControl!.beginRefreshing()
self.refreshControl!.sendActions(for: .valueChanged)
}
When this function runs, however, the realm maintains its default values, and does not update with the new one.
Models
// UserSave
import RealmSwift
class UserSave: Object {
#objc dynamic var articleTitle = "Default Title"
#objc dynamic var articleAuthor = "Default Author"
#objc dynamic var articleLink = "https://example.com"
#objc dynamic var articleBody = "Default Body"
}
// UserPrefs
import RealmSwift
class UserPrefs: Object {
#objc dynamic var applicationDark = false
#objc dynamic var fetchUrl = "https://example.com/"
}
The Issue
I can update the UserSave model just fine, though I am unable to change the values in UserPrefs, even though both are in the default realm. I use the same code (with the names substituted) to update both models, and only one works properly. I have the .realm file pulled up in the Realm Browser, and am able to watch as UserSave changes. I have followed the guide from realm.io, and their code only works on one model.
Thanks for any help in advance.
Your question mentions default values. Assuming the objects already exist, you would need to get that object to then be able to update it. Otherwise Realm would not know what object you're referring to.
Here's the code to write an object.
func exrefresh(passed: String) {
let UserRealm = UserPrefs()
UserRealm.fetchUrl = passed
let realm = try! Realm()
try! realm.write {
realm.add(UserRealm)
}
}
If you are going to only ever have one object of that type, then here's the code to update it.
let realm = try! Realm()
let results = realm.objects(UserPrefs.self)
let theOnlyOne = results.first!
try! realm.write {
theOnlyOne.fetchUrl = passed
}
This assumes you have proper error checking to know they object exists before updating.
Let's say I have Queue class that has a unique title and can hold a list of objects from my other class Item.
class Queue: Object {
#objc dynamic var title = ""
let items = List<Item>()
override static func primaryKey() -> String? {
return "title"
}
}
I want to have n (probably 3-5) instances of Queue from the time the app gets installed available in the database, so I can access them at any time to add some Items to the list of items. Is there a way to create those queues and save them to the database just once when the app gets first launched and where exactly in the code should I do it?
You can check somewhere at the start of your app how many Queues you have right now:
let realm = try! Realm()
if realm.objects(Queue.self).isEmpty {
// no items, so you should create n items
}
Add new object for Realm
class Task : Object {
#objc dynamic var id : Int = 0
#objc dynamic var name = ""
#objc dynamic var phone = ""
#objc dynamic var address = ""
}
#IBAction func buttonSave(_ sender: Any) {
let realm = try! Realm()
let user = Task()
user.id = 0
user.name = (txtName.text! as NSString) as String
user.phone = (txtPhone.text! as NSString) as String
user.address = (txtAddress.text! as NSString) as String
try! realm.write {
realm.add(user)
print("user:",user.name)
}
}
Here are my two realm models used to create objects in realm database.
class Users: Object {
dynamic var phoneNumber:String = ""
dynamic var messageSenderName:String = ""
let userMessages = List<Messages>()
override static func primaryKey() -> String? {
return "phoneNumber"
}
// convenience init() here
}
class Messages: Object{
dynamic var id = UUID().uuidString
dynamic var phoneNumber:String = ""
dynamic var messageSenderName:String = ""
dynamic var messageBody:String = ""
dynamic var message_id:String = ""
dynamic var messageTime:String = ""
override static func primaryKey() -> String? {
return "id"
}
// convenience init here
}
This my RosterViewController where I show the 'phoneNumber' and latest 'messageBody' in tableViewCell. I am using XMPP Framework to receive messages and SwiftyXMLParser to parse the xml messages.
class RosterViewController: UIViewController, UITableViewDelegate, UITableViewDataSource{
var realm : Realm!
dynamic var users = Users()
var userResult : Results<Users>!
dynamic var messagedata = Messages()
var messageResult : Results<Messages>!
override func viewDidLoad() {
super.viewDidLoad()
realm = try! Realm()
notificationToken = realm.observe{ notification, realm in
self.contactsTableView.reloadData()
}
}
func xmppStream(_ sender: XMPPStream, didReceive message: XMPPMessage) {
.......
// parse xml and set object values here
userResult = realm.objects(Users.self)
messageResult = realm.objects(Messages.self)
try! realm.write {
users.userMessages.append(messagedata)
realm.add(users, update: true)
realm.add(messagedata, update: true)
}
}
}
}
I want to append the messages dynamically to the users List of the respective message sender. I am not able to do so. I am receiving messages in real time so they should get appended to the list and thus reflect the same on tableView.
First message is shown properly, but when same user sends another message immediately then I get an error saying:
RLMException', reason: 'Attempting to modify object outside of a write transaction - call beginWriteTransaction on an RLMRealm instance first
I have also tried a lot, but could not get any fixes or workaround. Please help me solve this issue, just began with realm. Thank you!
I'm quite familiar with Sqlite, but decided to try using realm for my next project.
I'm having trouble with reading data from the db and deleting objects
as well.
I'm using the default realm path:
let realm = RLMRealm.defaultRealm()
When a button is pressed an RLMObject should either be added or deleted (if already there). This is the IBAction for the button:
#IBAction func addToFavor(sender: UIBarButtonItem) {
// Create RealmTV (RLMObject)
let tvShow = RealmTV(id: id, title: TitleLabel.text!, posterPath: posterUrl)
if favoriteButton.image!.isEqual(UIImage(named: "Favor unfilled")) {
realm.beginWriteTransaction()
// Create or update tv-show in database
RealmTV.createOrUpdateInDefaultRealmWithValue(tvShow)
try! realm.commitWriteTransaction()
// Change button state
favoriteButton.image = UIImage(named: "Favor filled")
}
else
{
realm.beginWriteTransaction()
// Delete tv-show object from database
realm.deleteObject(tvShow) /* RLMException here */
try! realm.commitWriteTransaction()
// Change button state
favoriteButton.image = UIImage(named: "Favor unfilled")
}
}
When I try to delete the object after it has been added to db. I get an RLMExecption saying:
'Can only delete an object from the Realm it belongs to.'
I understand what the above reason mean, but not how to solve it?
And also how do I retrieve only this object from db after it has been added?
EDIT
This is my RealmTv class:
import UIKit
import Realm
class RealmTV: RLMObject {
dynamic var id = ""
dynamic var title = ""
dynamic var posterPath = ""
override class func primaryKey() -> String? {
return "id"
}
override init() {
super.init()
}
init(id: String, title: String, posterPath: String) {
super.init()
self.id = id
self.title = title
self.posterPath = posterPath
}
}
What the error message is trying to convey is that the object you pass to -[RLMRealm deleteObject:] must belong to the Realm that you're trying to delete the object from. In your case you're passing a new object that does not belong to any Realm (such an object is referred to as a standalone or unpersisted object in Realm's documentation). Instead you must pass either an object that you have retrieved from the Realm (using -[RLMRealm objectForPrimaryKey:], +[RLMObject allObjectsInRealm:], etc.), or added to the Realm (using -[RLMRealm addObject:]).
Reworking your code to meet these requirements would look something like:
if favoriteButton.image!.isEqual(UIImage(named: "Favor unfilled")) {
realm.beginWriteTransaction()
// Create or update tv-show in database
let tvShow = RealmTV(id: id, title: TitleLabel.text!, posterPath: posterUrl)
RealmTV.createOrUpdateInDefaultRealmWithValue(tvShow)
try! realm.commitWriteTransaction()
// Change button state
favoriteButton.image = UIImage(named: "Favor filled")
}
else {
realm.beginWriteTransaction()
// Delete tv-show object from database
let tvShow = RealmTV.objectForPrimaryKey(id)
realm.deleteObject(tvShow)
try! realm.commitWriteTransaction()
// Change button state
favoriteButton.image = UIImage(named: "Favor unfilled")
}