Error when user tries to send next message immediately in Realm - ios

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!

Related

MongoDB Realm cannot update user embedded object in Swift

I'm trying to add to an embedded "Conversation" object within my user collection (for a chat app) in Mongo Realm. I would like to create a "default" conversation when the user account is created, such that every user should be a member of at least one conversation that they can then add others to.
The app currently updates the user collection via a trigger and function in Realm at the back end using the email / Password authentication process.
My classes are defined in Swift as follows:
#objcMembers class User: Object, ObjectKeyIdentifiable {
dynamic var _id = UUID().uuidString
dynamic var partition = "" // "user=_id"
dynamic var userName = ""
dynamic var userPreferences: UserPreferences?
dynamic var lastSeenAt: Date?
var teams = List<Team>()
dynamic var presence = "Off-Line"
var isProfileSet: Bool { !(userPreferences?.isEmpty ?? true) }
var presenceState: Presence {
get { return Presence(rawValue: presence) ?? .hidden }
set { presence = newValue.asString }
}
override static func primaryKey() -> String? {
return "_id"
}
#objcMembers class Conversation: EmbeddedObject, ObjectKeyIdentifiable {
dynamic var id = UUID().uuidString
dynamic var displayName = ""
dynamic var unreadCount = 0
var members = List<Member>()
}
So my current thinking is that I should code it in Swift as follows which I believe should update the logged in user, but sadly can't get this quite right:
// Open the default realm
let realm = try! Realm()
try! realm.write {
let conversation = Conversation()
conversation.displayName = "My Conversation"
conversation.unreadCount = 0
var user = app.currentUser
let userID = user.id
let thisUser = User(_id: userID)
realm.add(user)
}
Can anyone please spot where I'm going wrong in my code?
Hours spent on Google and I'm missing something really obvious! I have a fair bit of .NET experience and SQL but struggling to convert to the new world!
I'm a noob when it comes to NoSQL databases and SwiftUI and trying to find my way looking at a lot of Google examples. My example us based on the tutorial by Andrew Morgan https://developer.mongodb.com/how-to/building-a-mobile-chat-app-using-realm-new-way/
I am a bit unclear on the exact use case here but I think what's being asked is how to initialize an object with default values - in this case, a default conversation, which is an embedded object.
If that's not the question, let me know so I can update.
Starting with User object
class UserClass: Object {
#objc dynamic var _id = ObjectId.generate()
#objc dynamic var name = ""
let conversationList = List<ConversationClass>()
convenience init(name: String) {
self.init()
self.name = name
let convo = ConversationClass()
self.conversationList.append(convo)
}
override static func primaryKey() -> String? {
return "_id"
}
}
and the EmbeddedObject ConversationClass
class ConversationClass: EmbeddedObject {
dynamic var displayName = "My Conversation"
dynamic var unreadCount = 0
var members = List<MemberClass>()
}
The objective is that when a new user is created, they have a default conversation class added to the conversationList. That's done in the convenience init
So the entire process is like this:
let realm = Realm()
let aUser = UserClass(name: "Leroy")
try! realm.write {
realm.add(aUser)
}
Will initialize a new user, set their name to Leroy. It will also initialize a ConversationClass Embedded object and add it to the conversationList.
The ConversationClass object has default values set for displayName and unread count per the question.

Xcode 9.4.1: Value of type 'String' has no member 'setValue'

I'm learning Swift from a Udemy tutorial that shows how to make a chat app using a Firebase database. For my own learning and as a quick reference guide, I typed the code in a single .swift file to get a quick overview of the entire app and practice debugging. However, I have one more compiler error saying that the constant 'messagesDB' doesn't have a member 'setValue'. I'm assuming that messagesDB being an instance of class 'Database' would have access to the setValue() instance method. What do you think I'm missing in order to silence this error? Does it have something to do with the way the functions are declared?
Both class Auth and Database are arbitrary classes to mimic Firebase, so that the rest of the code could be displayed without a bunch of errors, thus giving me a single file to see how things work.
class Auth {
var currentUser: String = ""
func auth() -> Self { return self }
}
class Database {
func setValue() -> Self { return self }
func database() -> Self { return self }
func reference() -> Self { return self }
func child(_ someString: String) -> String {
print(someString)
}
}
class ChatViewController: UIViewController {
#IBOutlet var messageTextfield: UITextField!
#IBOutlet var sendButton: UIButton!
#IBAction func sendPressed(_ sender: AnyObject) {
messageTextfield.endEditing(true)
messageTextfield.isEnabled = false
sendButton.isEnabled = false
let messagesDB = Database().database().reference().child("Messages")
let messageDictionary = ["Sender": Auth().auth().currentUser, "MessageBody": messageTextfield.text!] as [String : Any]
messagesDB.setValue(messageDictionary) { //ERROR: Value of type 'String' has no member 'setValue'
(error, reference) in
if error != nil {
print(error!)
} else {
print("Message saved successfully!")
self.messageTextfield.isEnabled = true
self.sendButton.isEnabled = true
self.messageTextfield.text = ""
}
}
}
}
It's easy, your messageDB constant it's being set equals to the function "child" that returns a string, that's why it's telling you that "string" doesn't has no member setValue, so you should just set your messageDB to Database().database().reference()
the child method returns a String and not a Database instance, hence the error message.
create the database instance first and then use it where needed:
let messagesDB = Database()
.
.
messagesDB.setValue(...

Realm Swift - save a reference to another object

I thought this would be pretty straightforward after reading here and here but I'm a bit stuck.
I have a 'favouriteWorkout' object that looks like this :
class FavouriteObject: Object {
#objc dynamic var favouriteWorkoutName = ""
#objc dynamic var workoutReference = WorkoutSessionObject()
override class func primaryKey() -> String? {
return "favouriteWorkoutName"
}
}
What I'm trying to do here is reference a WorkoutSessionObject in Realm that links from a WorkoutName when a workout is saved as a favourite.
My WorkoutSessionObject has a primary key of workoutID which is a UUID string. It looks like this :
class WorkoutSessionObject: Object {
#objc dynamic var workoutID = UUID().uuidString
#objc dynamic var workoutType = ""
let exercises = List<WorkoutExercise>()
#objc dynamic var totalExerciseCount = 0
#objc dynamic var rounds = 0
#objc dynamic var favourite : Bool = false
override class func primaryKey() -> String? {
return "workoutID"
}
}
I've then tried to save using this :
let favouriteWorkout = FavouriteObject()
favouriteWorkout.favouriteWorkoutName = favouriteName
favouriteWorkout.workoutReference = (realm.object(ofType: WorkoutSessionObject.self, forPrimaryKey: self.workoutID))!
do {
try realm.write {
realm.add(favouriteWorkout)
}
} catch {
print ("Error adding favourite")
}
but i get a crash when I run of :
'RLMException', reason: 'The FavouriteObject.workoutReference property must be marked as being optional.
However, when I then try to make it optional (by adding ?) it says
"Cannot use optional chaining on non-optional value of type 'WorkoutSessionObject"!
Summary
I want to save a reference of the workoutID of a WorkoutSessionObject in my FavouriteObject which is an actual link to the WorkoutSessionObject (so the properties can be accessed from favourites)
Update
using the answers below I've now sorted the problem of the workout reference. This is now showing in Realm as the proper format () under "workoutReference". However, I'm now getting "nil" in "workoutReference" when trying to save. I know the workoutID is coming through correctly as I am printing it in the console.
You need to change the declaration of workoutReference. First of all, you need to make it Optional by writing ? after the type. Secondly, you shouldn't assign a default value to it, it needs to be Optional for a reason. The linked docs clearly state that
to-one relationships must be optional
, and workoutReference is clearly a to-one relationship.
class FavouriteObject: Object {
#objc dynamic var favouriteWorkoutName = ""
#objc dynamic var workoutReference:WorkoutSessionObject?
override class func primaryKey() -> String? {
return "favouriteWorkoutName"
}
}
In property-cheatsheet you can see that a non-optional Object-property is not allowed, so you have to change it like the following:
class FavouriteObject: Object {
#objc dynamic var favouriteWorkoutName = ""
// here you have to make the property optional
#objc dynamic var workoutReference: WorkoutSessionObject?
override class func primaryKey() -> String? {
return "favouriteWorkoutName"
}
}

Add initial objects to Realm

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)
}
}

Realm object as member is nil after saving

I'm facing an issue where a Realm object has another Realm object as member which is always nil after adding to the database.
class MedPack: Object {
dynamic var uuid = NSUUID().UUIDString
dynamic var medicine: Medicine?
convenience init(medicine: Medicine) {
self.init()
self.medicine = medicine
}
override static func primaryKey() -> String? {
return "uuid"
}
}
The reference to the object Medicine is always nil after adding.
class Medicine: Object {
var uuid = NSUUID().UUIDString
var name: String?
override static func primaryKey() -> String? {
return "uuid"
}
}
Creation of object
let medPack = MedPack(medicine: med)
Adding to database
static let sharedInstance = DBHelper()
var realmDb: Realm!
private init() {
realmDb = try! Realm()
}
func store(object: Object) {
try! self.realmDb.write {
self.realmDb.add(object)
}
}
After comparing this code to one of the Realm sample projects, it would appear that simply setting an Object as a child of another does not implicitly write it to the database as well.
Instead, you may need to refactor your code slightly, but make sure you explicitly add your Medicine object to Realm in a write transaction, before you set its relation to MedPack and then write MedPack to the database.

Resources