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.
Related
in realm i given id = 0 and it is as primary key and it will be auto increment, but problem is while updating it is saving in the index path 0 as declare as id : Int = 0.
Where ever i update also it is only updating in 0th index only.
i want to update as per selected object.
What to do?
Program :-
class Discount: Object {
#objc dynamic var id : Int = 0
#objc dynamic var offerName : String = ""
#objc dynamic var percentage: Float = 0.00
#objc dynamic var segmentIndex : Int = 0
#objc dynamic var dateWise: Date?
override class func primaryKey() -> String? {
return "id"
}
//Incrementa ID
func IncrementaID() -> Int{
let realm = try! Realm()
if let retNext = realm.objects(Discount.self).sorted(byKeyPath: "id").last?.id {
return retNext + 1
}else{
return 1
}
}
}
Generally speaking, auto-incrementing primary keys are challenging to deal with and can cause headaches long term.
What's generally most important is ensuring primary keys are unique and using UUID strings is ideally suited for that.
class Discount: Object {
#objc dynamic var discount_id = UUID().uuidString
override static func primaryKey() -> String? {
return "discount_id"
}
}
There may be concern about ordering and often times that managed by either adding a class var to determine ordering; like a timestamp for example or if you want to preserve ordering, objects can be added to a List, which keeps the order, like an array.
To answer your specific question, the code in your question is not complete (it was partially pulled from another question). The reason is that for each object that's created, it must be written to realm first, then the next object's primary key is based on the prior object.
Here's an example.
#objcMembers class User: Object {
dynamic var uid: Int = 0
dynamic var username: String?
func getNextUid() -> Int {
let realm = try! Realm()
if let lastObject = realm.objects(User.self).sorted(byKeyPath: "uid").first {
let lastUid = lastObject.uid
let nextUid = lastUid + 1
return nextUid
}
return 1
}
override static func primaryKey() -> String? {
return "uid"
}
}
now the sequence to use this is as follows
let u0 = User()
u0.uid = u0.getNextUid()
u0.username = "User 0"
let realm = try! Realm()
try! realm.write {
realm.add(u0)
}
let u1 = User()
u1.uid = u1.getNextUid()
u1.username = "User 1"
try! realm.write {
realm.add(u1)
}
as you can see, each object needs to be written to realm in order to the next object to be queried to get the prior objects primary key.
It's a whole lot of potentially unnecessary work and code.
My advice: Stick with the UUID().uuidString for primary keys.
Problem
I need to save a List in Realm, which is made up of a the properties of an Array of Struct Objects (which has been passed through a Segue and is popualating a tableview). This is in the form of an 'exercise name' and 'number of reps' on each row.
What have I tried?
I have matched the Realm Object with the Struct in terms of fields and format and attempted to save the array as a list e.g. "=List< array >" but this doesn't work ("use of undeclared type"). I've also tried various methods of trying to save the properties of each table row but again, couldn't get that to work (e.g. = cell.workoutname)
Research I found this How to save a struct to realm in swift? however, this isn't for saving arrays of objects I don't think. This did however (first answer), give me the idea of potentially saving the values contained within each row to Realm instead of the actual Struct array. I also found this Saving Array to Realm in Swift? but I think this is for when the array is already made up of Realm Objects, not Struct instances like in my case.
Code and details
Structs
I have a Struct as per below. Another struct, (Workout Generator) has a function which generates x number of instances of these objects. These are then passed via a Segue to a new VC TableView (each row displays a workout name and number of reps):
struct WorkoutExercise : Hashable, Equatable{
let name : String
let reps : Int
var hashValue: Int {
return name.hashValue
}
static func == (lhs: WorkoutExercise, rhs: WorkoutExercise) -> Bool {
return lhs.name == rhs.name
}
}
I then have the following Realm Objects. One is for saving a 'WorkoutSession'. This will contain a Realm List of WorkoutExercise Realm objects.
class WorkoutSessionObject: Object {
#objc dynamic var workoutID = UUID().uuidString
#objc dynamic var workoutName = ""
let exercises = List<WorkoutExerciseObject>()
var totalExerciseCount: Int {
return exercises.count
}
}
class WorkoutExerciseObject: Object {
#objc dynamic var name = ""
#objc dynamic var reps = 0
}
I have tried the following code when trying to save the Workout details to Realm :
func saveToRealm() {
let workoutData = WorkoutSessionObject()
workoutData.workoutName = "test"
workoutData.workoutID = UUID().uuidString
workoutData.exercises = List<selectedWorkoutExerciseArray>
}
What I think I need to do from reading the other answers
Option 1 - instead of trying to save the actual array, save the 'name' and 'reps' from each table row instead?
Option 2 - somehow convert the 'selectedWorkoutExerciseArray' into a list of realm objects?
of course there might be other options! Any help/ideas appreciated!
Why populate 2 separate lists if it needs to be persistent anyway? Just use the list in Realm to populate your table view. Here's a simple example of populating the list using append (just like any array):
class SomeClass: Object {
#objc dynamic var id: String = ""
var someList = List<SomeOtherClass>()
convenience init(id: String) {
self.init()
self.id = id
}
}
#objcMembers class SomeOtherClass: Object {
dynamic var someValue: String = ""
convenience init(value: String) {
self.init()
someValue = value
}
}
func addToList(someOtherClass: SomeOtherClass) {
let realm = try! Realm()
if let someClass = realm.objects(SomeClass.self).last {
do {
try realm.write({
someClass.someList.append(someOtherClass)
})
} catch {
print("something went wrong")
}
}
}
I have a very similar functionality, that allow the user to select from a table view. What I do is create a List from the selection like:
var arrayForSelectedObjects = [CustomObject]()
...
let aList = List<CustomObject>()
aList.append(objectsIn: arrayForSelectedObjects)
//I then assign the created list to the main object and save it.
let realmObject = MainObject()
realmObject.list = aList
My CustomObject is also stored on the realm db.
My MainObject is defined like so:
class MainObject : Object {
#objc dynamic var title: String?
var list = List<CustomObject>()
}
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 just start to learn Realm data persistence, I start it from a iOS test project.
The realm object is declared like this:
class AchievementRecord: Object {
dynamic var dateID:String = "1111-00-00"
dynamic var date:String = "0000-00-00"
dynamic var apple:Int = Int(0)
override static func primaryKey() -> String? {
return "dateID"
}
}
I initialise the object in View Controller's viewDidLoad() method as this:
class AchievementRecord: Object {
dynamic var dateID:String = "1111-00-00"
dynamic var date:String = "0000-00-00"
dynamic var apple:Int = Int(0)
override static func primaryKey() -> String? {
return "dateID"
}
}
then I declare another function to obtain the save data as:
let appleOn_05 = defaultRealm.objects(AchievementRecord.self).filter("dateID = '05-06-2017'")
print(appleOn_05)
In the console, Xcode says:
Because I need to retrieve the apple's number, which is 22 in the console. How can I retrieve the apple's number to demo it on the screen, how can I do it? Thanks in advance.
Results works like native Swift collections in many ways. If you are fetching a single object, you can just access it with Results.first let appleOn_05 = defaultRealm.objects(AchievementRecord.self).filter("dateID = '05-06-2017'").first
Subclasses of Object work like any other native class instance in Swift, so you can access their properties using the dot syntax.
let apple = appleOn_05.apple
Combining the two:
if let appleOn_05 = defaultRealm.objects(AchievementRecord.self).filter("dateID = '05-06-2017'").first {
let apple = appleOn_05.apple
}
I have an issue about Inheritance with my Objects in Realm.
Could you please have a look a it. I have :
an Object Activity
an Object Sport which I want to be a subclass of Activity
an Object Seminar which I want to be a subclass of Activity
To make this happen I write, according to the documentation, the following code :
// Base Model
class Activity: Object {
dynamic var id = ""
dynamic var date = NSDate()
override static func primaryKey() -> String? {
return "id"
}
}
// Models composed with Activity
class Nutrition: Object {
dynamic var activity: Activity? = nil
dynamic var quantity = 0
}
class Sport: Object {
dynamic var activity: Activity? = nil
dynamic var quantity = 0
dynamic var duration = 0
}
Now I have an Model Category which I want it to hold the activities, doesn’t matter if it’s an Nutrition or Sport.
Here is my code :
class Categorie: Object {
let activities = List<Activitie>()
dynamic var categoryType: String = ""
override static func primaryKey() -> String? {
return "categoryType"
}
}
Now I try to add a Nutrition object to my List<Activitie> by doing this :
let nutrition = Nutrition(value: [ "activity": [ "date": NSDate(), "id": "0" ], "quantity": 12 ])
try! realm.write {
realm.add(nutrition, update: true)
}
It doesn’t work because List<Activitie> expect an Activity Object and not a Nutrition Object. Where am I wrong ?
Thanks a lot for the help.
You encountered one of the big problems of Realm : there is no complete polymorphism.
This github post gives a big highlight on what is possible or not, and a few possible solutions that you can use.
Quick quote from jpsim from the link above:
Inheritance in Realm at the moment gets you:
Class methods, instance methods and properties on parent classes are
inherited in their child classes.
Methods and functions that take
parent classes as arguments can operate on subclasses.
It does not get you:
Casting between polymorphic classes (subclass->subclass,
subclass->parent, parent->subclass, etc.).
Querying on multiple classes simultaneously.
Multi-class container (RLMArray/List and RLMResults/Results).
According to the article about type erased wrappers in swift and the #5 option I have ended up with something more flexible, here is my solution.
( please note that the solution #5 need to be updated for Swift 3, my solution is updated for Swift 3 )
My main Object Activity
class Activity: Object {
dynamic var id = ""
override static func primaryKey() -> String? {
return "id"
}
}
and my inheritance : Nutrition and Sport
class Nutrition: Activity { }
class Sport: Activity { }
The solution according to the solution #5 option : Using a type-erased wrapper for polymorphic relationships.
If you want to store an instance of any subclass of Activity, define a type-erased wrapper that stores the type's name and the primary key.
class AnyActivity: Object {
dynamic var typeName: String = ""
dynamic var primaryKey: String = ""
// A list of all subclasses that this wrapper can store
static let supportedClasses: [Activity.Type] = [
Nutrition.self,
Sport.self
]
// Construct the type-erased activity from any supported subclass
convenience init(_ activity: Activity) {
self.init()
typeName = String(describing: type(of: activity))
guard let primaryKeyName = type(of: activity).primaryKey() else {
fatalError("`\(typeName)` does not define a primary key")
}
guard let primaryKeyValue = activity.value(forKey: primaryKeyName) as? String else {
fatalError("`\(typeName)`'s primary key `\(primaryKeyName)` is not a `String`")
}
primaryKey = primaryKeyValue
}
// Dictionary to lookup subclass type from its name
static let methodLookup: [String : Activitie.Type] = {
var dict: [String : Activity.Type] = [:]
for method in supportedClasses {
dict[String(describing: method)] = method
}
return dict
}()
// Use to access the *actual* Activitie value, using `as` to upcast
var value: Activitie {
guard let type = AnyActivity.methodLookup[typeName] else {
fatalError("Unknown activity `\(typeName)`")
}
guard let value = try! Realm().object(ofType: type, forPrimaryKey: primaryKey) else {
fatalError("`\(typeName)` with primary key `\(primaryKey)` does not exist")
}
return value
}
}
Now, we can create a type that stores an AnyActivity!
class Category: Object {
var categoryType: String = ""
let activities = List<AnyActivity>()
override static func primaryKey() -> String? {
return "categoryType"
}
}
and to store the data :
let nutrition = Nutrition(value : [ "id" : "a_primary_value"] )
let category = Category(value: ["categoryType" : "0"])
category.activities.append(AnyActivity(tree))
To read the data we want to check the activity method, use the value property on AnyActivity
for activity in activities {
if let nutrition = activity.value as? Nutrition {
// cool it's a nutrition
} else if let sport = activity.value as? Sport {
// cool it's a Sport
} else {
fatalError("Unknown payment method")
}
}
Owen is correct, in regarding OO principles, and I noticed that also, that you are not truly doing inheritance.
When an object uses another as an attribute or property, it is Association, not inheritance. I too am reviewing whether Realm supports Table/Object level inheritance like Java does with Hibernate ... but not expecting it.
This framework while still young but powerful, is good enough for me to avoid using SQLite ... very fast, easy to use and much easier with data model migrations !
In your code Nutrition and Sport don't inherit Activity, they inherit Object and composite an Activity instance. To inherit Activity, you should do
class Nutrition: Activity {
dynamic var quantity = 0
}
I think maybe you worried if you did above, your code did not inherit Object any more. But it is not true. Nutrition is still inherited from Object as Object is inherited by Activity.
Their relations are Nutrition: Activity: Object.
As Yoam Farges pointed it out, Realm doesn't support :
Casting between polymorphic classes (subclass->subclass,
subclass->parent, parent->subclass, etc.).
Which is what I was trying to do.
You guys are right when saying that my Inheritance is not an Inheritance, but as you can see in the Realm documentation it's how you achieve it.
Thanks to the informations I got in this github post I could achieved what I wanted and could keep a easy readability.
Use an option type for polymorphic relationships :
class PolyActivity: Object {
dynamic var nutrition: Nutrition? = nil
dynamic var sport: Sport? = nil
dynamic var id = ""
override static func primaryKey() -> String? {
return "id"
}
}
Create my main Object Activity
class Activity: Object {
dynamic var date = NSDate()
}
and have my Nutrition and Sport object inherited properly to Activity
class Nutrition: Activity {
dynamic var quantity = 0
}
class Sport: Activity {
dynamic var quantity = 0
dynamic var duration = 0
}
My Category object can now hold a List
class Categorie: Object {
let activities = List<PolyActivity>()
var categoryType: String = ""
override static func primaryKey() -> String? {
return "categoryType"
}
}
And this is how I create my Nutrition object :
let polyActivity = PolyActivity(value : [ "id": primaryKey ] )
poly.nutrition = Nutrition(value: [ "date": NSDate(), "quantity": 0, "duration": 0 ])
let category = Category(value: ["categoryType" : "0"])
category.activities.append(polyActivity)
And to retrieve just use Optional Binding :
if let nutrition = category.activities[0].nutrition { }
If you guys have a better, clearer, easier solution please go head !