Defining a Realm Database Structure (Swift) - ios

I am in the process of creating an application that will display a list of stocks that a user saves in a tableView. It will also allow the user to add or remove items from their favorites. I need help defining the database structure and setting up for the adding and constant updating of the user's favorite stocks.
I currently have a StockData struct that works with my tableView and a button for adding to the user's list:
struct StockData {
let currentPrice: Double
// meta data
let name: String
let ticker: String
let interval: String
let lastRefreshed: String
let change: Double
}
// In an actual ViewController
#IBAction func addButtonClicked(_ sender: Any) {
print("Add clicked")
// Handle adding the item to the user's list
}
Now as far as my current realm model is concerned, I have:
class User: Object {
#objc dynamic var name = ""
#objc dynamic var id = ""
var stockFavs = List<StockItem>()
}
class StockItem: Object {
#objc dynamic var currentPrice: Double = 0.0
// meta data
#objc dynamic var name = ""
#objc dynamic var ticker = ""
#objc dynamic var interval = ""
#objc dynamic var lastRefreshed = ""
#objc dynamic var change: Double = 0.0
}
// Setting up the user and creating test values
let newUser = User()
newUser.name = "John Smith"
newUser.id = "coolId123"
var stockArr = List<StockItem>()
for i in 0..<12 {
let item = StockItem()
item.name = "Microsoft Corporation"
item.change = -3.55
item.currentPrice = 123.45
item.interval = "1day"
item.lastRefreshed = "Now"
item.ticker = "MSFT"
stockArr.append(item)
}
newUser.stockFavs = stockArr
try! realm.write {
realm.add(newUser)
}
So far, I have been able to create a user object for the current user of the device and add test values, but I am unsure how to implement constant realm updating (the method would have self.tableView.reloadData(), but apart from that, I'm clueless) in conjunction with the ability to add StockItem's to the user's array.
Any help would be much appreciated!

You use a function for every time you want to add to the database.
let button = UIButton(frame: CGRect(x: 0, y: 0, width: 100, height: 100)
button.addTarget(self, action: #selector(add), for: .touchUpInside)
func add() {
let currentData = [MyFakeData1, MyFakeData2, etc.]
try! realm.write {
realm.add(currentData)
}
// You need to get the updates in the database
// You could have an array in your model that you update and then append all
// the new items to it and only do database lookups when you restart the
// device
update()
}
func update() {
let realm = try! Realm()
var newArray = realm.objects(StockItem.self)
myViewController.modelArray = newArray
table.reloadData()
}

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.

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 <List> populated but just with one object

First time i use Realm, i use it in a Swift Project. I use the last version of Realm 0.96.3.
I got two Realm Models: Jog and Marker
class RealmCurrentRecJog: Object {
dynamic var id: Int = 0
dynamic var duration: Double = 0.0
let markers = List<RealmCurrentRecMarker>()
// Specify properties to ignore (Realm won't persist these)
override static func primaryKey() -> String? {
return "id"
}
}
class RealmCurrentRecMarker: Object {
dynamic var order: Int = 0
dynamic var latitude: Double = 0.0
dynamic var longitude: Double = 0.0
dynamic var speed: Double = 0.0
dynamic var accuracy: Double = 0.0
dynamic var altitude: Double = 0.0
dynamic var time: Double = 0.0
}
As you can see in the Jog model i got a <List> of Markers and id is the primary key. In the Marker model nothing crazy.
So i fill my Realm Models with this function:
private func fillDbWithMarker() {
let saveJog = RealmCurrentRecJog()
let saveMarker = RealmCurrentRecMarker()
let jogToSave = self.jogRecorder.getDraftJog()
saveJog.id = Int(jogToSave.id)
saveJog.duration = jogToSave.duration
saveMarker.order = Int((jogToSave.markers.last?.order)!)
saveMarker.longitude = (jogToSave.markers.last?.longitude)!
saveMarker.latitude = (jogToSave.markers.last?.latitude)!
saveMarker.accuracy = (jogToSave.markers.last?.accuracy)!
saveMarker.altitude = (jogToSave.markers.last?.altitude)!
saveMarker.speed = (jogToSave.markers.last?.speed)!
saveMarker.time = (jogToSave.markers.last?.time)!
do {
let realm = try Realm()
do {
try! realm.write {
saveJog.markers.append(saveMarker)
realm.add(saveJog, update: true)
}
}
} catch let error as NSError {
print(error)
}
}
And at the end when i look at my Realm Browser the result is that i just got the last marker in my list even if my Marker table is full.
I don't understand where is my mistake .
Edit: Here is the code to make it work
do {
let realm = try Realm()
do {
try! realm.write {
saveJog = realm.objects(RealmCurrentRecJog).last!
saveJog.markers.append(saveMarker)
realm.add(saveJog, update: true)
}
}
} catch let error as NSError {
print(error)
}
fillDbWithMarker creates a Jog whose markers list contains a single marker. When it's saved with Realm.add(_:update:), it updates the existing Jog with the same primary key so that its properties match the passed-in object's properties. This results in the existing Jog object being updated to contain only the single new marker.
If you tweak fillDbWithMarker to retrieve the existing Jog with the given ID and append the marker to its markers list you'll get the behavior you're after.

Sort Realm objects with the count of List property

I have two Realm data models classes like this:
class TaskList: Object {
dynamic var name = ""
dynamic var createdAt = NSDate()
let tasks = List<Task>()
}
And:
class Task: Object {
dynamic var name = ""
dynamic var createdAt = NSDate()
dynamic var notes = ""
dynamic var isCompleted = false
}
Now I need to query TaskList and sort them with number of tasks in each of them. I tried to use something like this but it crashes the app because its not supported:
realm.objects(TaskList).sorted("tasks.count")
Another workaround is:
Introduce taskCount property in TaskList, and make always sync taskCount and tasks.count.
class TaskList: Object {
dynamic var name = ""
dynamic var createdAt = NSDate()
let tasks = List<Task>()
dynamic var taskCount = 0
}
Then, you can use
realm.objects(TaskList).sorted("taskCount")
Since Realm does not support sorting on key paths, currently.
If you'd like to sync taskCount and tasks.count automatically, you can do like the following:
(Don't use tasks.append() directly, use addTask() method instead.)
class TaskList: Object {
dynamic var name = ""
dynamic var createdAt = NSDate()
private let tasks = List<Task>()
dynamic var taskCount = 0
func addTask(task: Task) {
func add() {
tasks.append(task)
taskCount = tasks.count
}
if let realm = realm where !realm.inWriteTransaction {
try! realm.write{
add()
}
} else {
add()
}
}
}
Like this:
realm.objects(TaskList).sort { $0.tasks.count < $1.tasks.count }
EDIT: have no idea about Realm, this only works when objects returns a CollectionType and List has a count property.

What is the best way to connect Realm and SwiftBond

I love Realm and I love Bond. Both of them makes app creation a joy. So I was wondering what is the best way to connect Realm and Bond? In Realm we can store basic types such as Int, String, e.g. But in Bond we work with Dynamics and Bonds. The only way that I found to connect Realm and Bond is following:
class TestObject: RLMObject {
dynamic var rlmTitle: String = ""
dynamic var rlmSubtitle: String = ""
var title: Dynamic<String>
var subtitle: Dynamic<String>
private let titleBond: Bond<String>!
private let subtitleBond: Bond<String>!
init(title: String, subtitle: String) {
self.title = Dynamic<String>(title)
self.subtitle = Dynamic<String>(subtitle)
super.init()
self.titleBond = Bond<String>() { [unowned self] title in self.rlmTitle = title }
self.subtitleBond = Bond<String>() { [unowned self] subtitle in self.rlmSubtitle = subtitle }
self.title ->> titleBond
self.subtitle ->> subtitleBond
}
}
But it surely lacks simplicity and elegance and produces a lot of boiler code. Is there any way to do this better?
With Realm supporting KVO and Bond 4, you can extend Realm objects to provide Observable variants. There is some boilerplate to it, but it's clean and without hacks.
class Dog: Object {
dynamic var name = ""
dynamic var birthdate = NSDate(timeIntervalSince1970: 1)
}
extension Dog {
class ObservableDog {
let name: Observable<String>
let birthdate: Observable<NSDate>
init(dog: Dog) {
name = Observable(object: dog, keyPath: "name")
birthdate = Observable(object: dog, keyPath: "birthdate")
}
}
func observableVariant() -> Dog.ObservableDog {
return ObservableDog(dog: self)
}
}
Than you'll be able to do:
let myDog = Dog().observableVariant()
myDog.name.observe { newName in
print(newName)
}
myDog.name.bindTo(nameLabel.bnd_text)
realm.write {
myDog.name.value = "Jim"
}
You could likely simplify the pattern you're using somewhat if you used
default property values:
class TestObject: RLMObject {
dynamic var rlmTitle = ""
dynamic var rlmSubtitle = ""
var title: Dynamic<String>
var subtitle: Dynamic<String>
private let titleBond = Bond<String>() { [unowned self] title in self.rlmTitle = title }
private let subtitleBond = Bond<String>() { [unowned self] subtitle in self.rlmSubtitle = subtitle }
init(title: String, subtitle: String) {
self.title = Dynamic<String>(title)
self.subtitle = Dynamic<String>(subtitle)
self.title ->> titleBond
self.subtitle ->> subtitleBond
super.init()
}
}
You could remove another two lines of code if Bond's ->> operator returned the
left value so you could do self.title = Dynamic<String>(title) ->> titleBond.
But ultimately, until Swift has native language support for KVO or an equivalent observation mechanism, you're sadly going to have to write some amount of boilerplate.
I've been thinking about this for three days and came up with nearly perfect solution, which does not employ any boilerplate code. First of all I have created a super class for a realm model's wrapper:
class BondRealmBaseClass {
private var realmModel: RLMObject!
private let realm = RLMRealm.defaultRealm()
private var bonds = NSMutableArray()
init(){
realmModel = createRealmModel()
realm.beginWriteTransaction()
realm.addObject(realmModel)
realm.commitWriteTransaction()
createBonds()
}
init(realmModel: RLMObject){
self.realmModel = realmModel
createBonds()
}
func createBondFrom<T>(from: Dynamic<T>, toModelKeyPath keyPath: String){
from.value = realmModel.valueForKeyPath(keyPath) as T
let bond = Bond<T>() { [unowned self] value in
self.realm.beginWriteTransaction()
self.realmModel.setValue(value as NSObject, forKey: keyPath)
self.realm.commitWriteTransaction()
}
from ->| bond
bonds.addObject(bond)
}
//MARK: - Should be overriden by super classes
func createBonds(){ fatalError("should be implemented in supreclass") }
func createRealmModel() -> RLMObject{ fatalError("should be implemented in supreclass") }
}
After that for each realm model I create two classes, first is the actual realm model, which stores all properties:
class RealmTodoModel: RLMObject {
dynamic var title = ""
dynamic var date = NSDate()
}
and a second one is the wrapper around realm model:
class TodoModel : BondRealmBaseClass{
let title = Dynamic("")
let date = Dynamic(NSDate())
override func createBonds(){
createBondFrom(title, toModelKeyPath: "title")
createBondFrom(date, toModelKeyPath: "date")
}
override func createRealmModel() -> RLMObject { return RealmTodoModel() }
}
And this two classes is actually all is needed to link Realm and Bond: creating new TodoModel will actually add to Realm new RealmTodoModel and all changes made with TodoModel's title and date will be automatically saved to corresponding Realm model!
EDIT
I added some functionality and posted this as a framework on GitHub. Here is the link.

Resources