RealmSwift Observing a collection of child objects (I think) - swift

So I've got a collection of objects - Category
Each Category object has a field called total which contains another object, a Total
Each Total object has a field called amount which is a simple double
I want to observe any changes to the amount field of all the Total objects in my collection of Category... Basically, if I could add an observer onto each Category object's total. Does that makes sense? It's much harder to describe that I thought. haha
I've tried all kinds of different way but all either end up in memory leaks or horribly inefficient and breaking the typical Realm way of handling data.
Something to note - there are thousands of TartuGecko objects... Anything in the app that relates to an amount is stored as a TartuGecko. Therefore, observing them all is impractical.
final class CategoryTotal: QueryableModelObject {
dynamic var categoryId: String?
dynamic var total: TartuGecko?
}
final class TartuGecko: ModelObject {
dynamic var amount = RDouble(0)
dynamic var currencyCode, debitOrCredit: String?
dynamic var exchangeRate = RDouble(0)
}

There are some inconsistencies in how you are describing things (as noted by Jay) but assuming you just need the concepts, this is how you could structure your Realm models:
final class CategoryTotal: QueryableModelObject {
dynamic var categoryId: String?
let tartuGeckos = LinkingObjects(fromType: TartuGecko.self, property: "categoryTotal")
}
final class TartuGecko: ModelObject {
dynamic var amount = RDouble(0)
dynamic var currencyCode, debitOrCredit: String?
dynamic var exchangeRate = RDouble(0)
dynamic var categoryTotal: CategoryTotal?
}
Then you can access all the TartuGecko objects on each CategoryTotal object with categoryTotal.tartuGeckos like this:
let realm = try! Realm()
let categories = realm.objects(CategoryTotal.self)
for category in categories{
print(category.tartuGeckos) //<-- All your TartuGeckos that point to that category are available here
}
You will have to make sure that each TartuGecko object that gets created has a reference to a CategoryTotal.
If you just want a total amount for each CategoryTotal, then a more direct solution would be to use a computed property like this:
final class CategoryTotal: QueryModelObject{
dynamic var categoryId: String?
let tartuGeckos = LinkingObjects(fromType: TartuGecko.self, property: "categoryTotal")
//Computed property
var totalAmount: Double{
var total = 0.0
//There are fancier ways of calculating this, but I'm trying to be clear
for tartuGecko in tartuGecko{
total += tartuGecko.amount
}
return total
}
}
Then on each CategoryTotal object you always have the total amount available to you.
let realm = try! Realm()
let categories = realm.objects(CategoryTotal.self)
for category in categories{
print(category.totalAmount) //<-- Total amount
}

Related

Prepopulate Realm database with many to many relationships?

I'm brand new to this, so forgive me if I'm missing something obvious or not asking the right question. I plan to make an app that have a few sets of data that require many-to-many relationships. For example, if I have a model for Food items and a model for CookingMethods to cook that food item. So, each Food can have multiple CookingMethods, and each CookingMethod applies to multiple types of Food.
I think this is the right way to set up the realm data:
class Food: Object {
#objc dynamic var title: String = ""
#objc dynamic var id: Int = 0
var cookingMethods = List<CookingMethod>()
}
class CookingMethod: Object {
#objc dynamic var id: Int = 0
#objc dynamic var title: String = ""
let foods = LinkingObjects(fromType: Food.self, property: "cookingMethods")
}
Is it now possible to import a set of data (probably a csv file?) using either Realm Studio or programmatically that allows me to link this relationship? The Food would have a list of CookingMethods, and the CookingMethods would link back to multiple different Foods?
If I'm going about this all wrong please let me know, there is a spreadsheet of data that I'd like to add to my app's database as a one time thing.

Is there a way to change the Data Type in Realm Database?

As you can see the image, I totally mess up the Data Type (The red circle). Is there a way to change the Data Type into Integer?
EDIT
I want to change the data type from a String to an Int and I have existing data so I can't start with a fresh realm and just change the var type.
class Item: Object {
#objc dynamic var name: String?
#objc dynamic var itemid: String?
#objc dynamic var cateid: String?
}
I may have misunderstand the question but you appear to have existing data stored as as a String and you want to 'convert' all of those to an Int.
You cannot directly change the type to another type and have the stored data changed as well. If you do, it will be flagged with an error.
Error!
Migration is required due to the following errors:
- Property 'Item.itemid' has been changed from 'string' to 'int'.
You need to incorporate a migration block to 'convert' the string value to an Int. Assuming we add a new Int property to our object `item_id', something along these lines will migrate your strings to int's and in the case where the string is not a valid it, it will be assigned a value of 0
Realm.Configuration.defaultConfiguration = Realm.Configuration(
schemaVersion: 1,
migrationBlock: { migration, oldSchemaVersion in
if (oldSchemaVersion < 1) {
migration.enumerateObjects(ofType: Item.className()) { oldObject, newObject in
let stringValue = oldObject!["itemid"] as! String
newObject!["item_id"] = Int(stringValue) ?? 0
}
}
})
Also, as soon as Realm is accessed, the object models are written to the Realm file. So a simple matter of
let items = realm.object(Item.self)
Will store that model even if no data was ever written. If, after that line, the var type is changed from a String to an Int, it will throw the migration error.
Deleting the Realm and starting from scratch is one option if that's the case, and as mentioned above, a migration block.
If this is brand new model that has never been used, then as the comments and other answer suggest, just change the String to an Int.
Simply change the String to Int in your Object model. Please note that the Realm documentation says:
String, NSDate, and NSData properties can be declared as optional or non-optional using the standard Swift syntax.
So unlike the String in your previous model, you will not be able to declare your Int as optional. You have two options:
Declare a default value:
class Item: Object {
#objc dynamic var name: String?
#objc dynamic var itemid: Int = 0
#objc dynamic var cateid: Int = 0
}
Declare it as a RealmOptional:
class Item: Object {
#objc dynamic var name: String?
#objc dynamic var itemid = RealmOptional<Int>()
#objc dynamic var cateid = RealmOptional<Int>()
}
For more information on each solution please see this SO answer and the Realm documentation.

Storing weather forecast data in Swift

I want to make an application which requires a lot of weather forecast data, three cities, seven days, 24 hours and six values in every hour of forecast.
I'm using the Dark Sky API and pod called ForecastIO.
What should I use for storage? CoreData or Realm? I was told that Realm is a lot of easier to work with and it's also more efficient. I looked into code and for a beginner it is much easier and not dealing with any graphs is also plus, but if it needs to be done, I'll study it. And how should I structure it?
I tried this but Realm accepts only basic data types, so it won't work.
HourlyWeather.swift
import Foundation
import RealmSwift
class HourlyWeather: Object {
#objc dynamic var temperature: Double = 0
#objc dynamic var wind: Double = 0
#objc dynamic var precip: Double = 0
#objc dynamic var humidity: Double = 0
#objc dynamic var uvIndex: Int = 0
#objc dynamic var icon: String = ""
}
DailyWeather.swift
import Foundation
import RealmSwift
class DailyWeather: Object {
#objc dynamic var day = [HourlyWeather()] // I understand that this is a no-no for Realm
}
CityWeather.swift
import Foundation
import RealmSwift
class CityWeather: Object {
#objc dynamic var city = [DailyWeather()] // The same
}
I wanted this code to be accessible as
City.day[index].hour[index].temperature
for example
You need to use List instead of Array when you want to store a collection of Realm objects as a property of another object. For more information, read the Many-to-many relationships part of the official docs (but I'd suggest going through the whole documentation, since it gives a really good starting point for using Realm).
class DailyWeather: Object {
let day = List<HourlyWeather>()
}
class CityWeather: Object {
let city = List<DailyWeather>()
}
You need to declare a property of type List. See the Many to Many docs
class DailyWeather: Object {
let city = List<DailyWeather>()
}

Realm - Property cannot be marked dynamic because its type cannot be represented in Objective-C

I am trying to implement below scenario, but i am facing the issue
class CommentsModel: Object {
dynamic var commentId = ""
dynamic var ownerId: UserModel?
dynamic var treeLevel = 0
dynamic var message = ""
dynamic var modifiedTs = NSDate()
dynamic var createdTs = NSDate()
//facing issue here
dynamic var childComments = List<CommentsModel>()
}
I have a comments model which has non optional properties in which childComments is List of same Comments model class. In this when i declare dynamic var childComments = List<CommentsModel>()
it shows me Property cannot be marked dynamic because its type cannot
be represented in Objective-C.
Please help me how to achieve my requirement
List and RealmOptional properties cannot be declared as dynamic because generic properties cannot be represented in the Objective‑C runtime, which is used for dynamic dispatch of dynamic properties, and should always be declared with let.
Learn more in Docs.
So you should declare childComments this way:
let childComments = List<CommentsModel>()
Just to make it more understandable how you can add data to the list although its declared as let.
import Foundation
import RealmSwift
import Realm
class Goal: Object {
//List that holds all the Events of this goal
let listOfEvents = List<CalEvent>()
required public convenience init(eventList: List<CalEvent>) {
self.init()
for event in eventList {
//Append the date here
self.listOfEvents.append(i)
}
}
}

Wait for one property to initialize and use it as reference for pulling data from db into another property, best practice?

I have a class, it has two properties:
var fruitsPackId: Int
var fruitsPackContent: Array<Fruit>?
Once the class is being initialized, I want to append data into fruintsPackContent from a local db according the the initialized fruitsPackId. I am not sure what is the best practice on that type of case.
What I did for now, is creating fruitsPackContent as a computed property, that pulls out the data from the local db using the fruitsPackId as reference id.
However, I feel that this is just not the right way of doing it, any ideas?
My code:
class FruitsPack: NSObject {
var fruitsPackId: Int
init(fruitsPackId: Int) {
self.fruitsPackId = fruitsPackId
}
var fruitsPackContent: Array<Fruit>? {
// Pulling data from local db here...
// For this example I create a dummy array with one instance of Fruit
let fruit1 = Fruit(fruitsPackId: self.fruitsPackId, fruitName: "Banana")
var fruits = Array<Fruit>()
fruits.append(fruit1)
return fruits
}
}
class Fruit: FruitsPack {
var fruitName: String
init(fruitsPackId: Int, fruitName: String) {
self.fruitName = fruitName
super.init(fruitsPackId: fruitsPackId)
}
}
EDIT:
Using lazy variable type did the work for me:
Class initialization has nothing to do with that property
Memory is being utilized only once property is being called
The property is being filled up with data only once
An instance method is available to be used by others
New code:
class FruitsPack: NSObject {
var fruitsPackId: Int
lazy var fruitsPackContent: Array<Fruit>? = self.getFruitsPackContent(self.fruitsPackId)
init(fruitsPackId: Int) {
self.fruitsPackId = fruitsPackId
}
func getFruitsPackContent(fruitsPackId: Int) -> Array<Fruit>? {
// Pulling data from local db here...
// For this example I create a dummy array with one instance of Fruit
let fruit1 = Fruit(fruitsPackId: self.fruitsPackId, fruitName: "Banana")
var fruits = Array<Fruit>()
fruits.append(fruit1)
return fruits
}
}
class Fruit: FruitsPack {
var fruitName: String
init(fruitsPackId: Int, fruitName: String) {
self.fruitName = fruitName
super.init(fruitsPackId: fruitsPackId)
}
}
Retrieving data from a database is a relatively computationally expensive process, so I'm not personally a fan of building that into a computed property. What if you innocently had some code that did the following:
for index in 0 ..< fruitPack.fruitsPackContent.count {
print(fruitPack.fruitsPackContent[index])
}
If you had n items in the database, this code might be repeatedly retrieving the full list of all items from the database n + 1 times (once for count and again for each subscripted access). You could, theoretically, remedy this by making sure that the computed property cached the results (and you'd have to build some code that would identify when the database was updated and invalidate the cache).
It would be more prudent to make the retrieval from the database an explicit method so that the app developer knows when they're retrieving data from the database, and when they're accessing a cached array. You generally want to avoid some significant hidden performance impact resulting from innocently accessing some property.