Optional Int in Realm

Optional Int in Realm

From the Realm docs:

String, NSDate, and NSData properties can be declared as optional or non-optional using the standard Swift syntax.

Optional numeric types are declared using RealmOptional:

class Person: Object {
// Optional string property, defaulting to nil
dynamic var name: String? = nil

// Optional int property, defaulting to nil
// RealmOptional properties should always be declared with `let`,
// as assigning to them directly will not work as desired
let age = RealmOptional<Int>()
}

let realm = try! Realm()
try! realm.write() {
var person = realm.create(Person.self, value: ["Jane", 27])
// Reading from or modifying a `RealmOptional` is done via the `value` property
person.age.value = 28
}

RealmOptional supports Int, Float, Double, Bool, and all of the sized versions of Int (Int8, Int16, Int32, Int64).

UPDATE:

The Optional Ints that were mentioned in the Tweet by Realm were just regarding a bugfix for the RealmOptional way of implementing an Optional numeric value with the sized versions of Int

According to the guys from Realm you still have to use RealmOptional if you want to have Optional numeric values in a Realm object. You cannot simply use it like other Optional types.

So dynamic var reps: Int? will not work.

RealmSwift and Codable when using optional types

Vin Gazoli gave me the missing key.

First, RealmOptional needs to be declared as a let, so in the init you need to set the value by using myObject.myVariable.value = newValue. Then, everywhere you use it you must use it as obj.variable.value as well. RealmOptional does not conform to codable though, so you must write an extension. You can find it below as well as a link to where I received it.

ex of object:

class Attendee: Object,Codable {

@objc dynamic var id = 0
@objc dynamic var attendeeId = 0
@objc dynamic var lanId = ""
@objc dynamic var firstName = ""
@objc dynamic var lastName = ""
@objc dynamic var email = ""
@objc dynamic var employeeId = ""
@objc dynamic var badgeId = ""
@objc dynamic var department = ""
@objc dynamic var picture:String? = nil
let scanIn = RealmOptional<Double>()
let scanOut = RealmOptional<Double>()

override static func primaryKey () -> String? {
return "id"
}

private enum CodingKeys: String, CodingKey {
case id, attendeeId, lanId, firstName, lastName, email, employeeId, badgeId, department, picture, scanIn, scanOut
}

required convenience init(from decoder: Decoder) throws {
self.init()
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(Int.self, forKey:.id)
attendeeId = try container.decodeIfPresent(Int.self, forKey:.attendeeId) ?? 0
lanId = try container.decode(String.self, forKey:.lanId)
firstName = try container.decode(String.self, forKey:.firstName)
lastName = try container.decode(String.self, forKey:.lastName)
email = try container.decode(String.self, forKey:.email)
employeeId = try container.decode(String.self, forKey:.employeeId)
badgeId = try container.decode(String.self, forKey:.badgeId)
department = try container.decode(String.self, forKey:.department)
picture = try container.decodeIfPresent(String.self, forKey:.picture)
self.scanIn.value = try container.decodeIfPresent(Double.self, forKey:.scanIn) ?? 0
self.scanOut.value = try container.decodeIfPresent(Double.self, forKey:.scanOut) ?? 0
}

The below code is REQUIRED to make the above object function. Retrieved from this link in conjunction with h1m5's fix in the comments of that page. The below is for Double. The link has other primitives.

func assertTypeIsEncodable<T>(_ type: T.Type, in wrappingType: Any.Type) {
guard T.self is Encodable.Type else {
if T.self == Encodable.self || T.self == Codable.self {
preconditionFailure("\(wrappingType) does not conform to Encodable because Encodable does not conform to itself. You must use a concrete type to encode or decode.")
} else {
preconditionFailure("\(wrappingType) does not conform to Encodable because \(T.self) does not conform to Encodable.")
}
}
}

func assertTypeIsDecodable<T>(_ type: T.Type, in wrappingType: Any.Type) {
guard T.self is Decodable.Type else {
if T.self == Decodable.self || T.self == Codable.self {
preconditionFailure("\(wrappingType) does not conform to Decodable because Decodable does not conform to itself. You must use a concrete type to encode or decode.")
} else {
preconditionFailure("\(wrappingType) does not conform to Decodable because \(T.self) does not conform to Decodable.")
}
}
}

extension RealmOptional : Encodable where Value : Encodable {
public func encode(to encoder: Encoder) throws {
assertTypeIsEncodable(Value.self, in: type(of: self))

var container = encoder.singleValueContainer()
if let v = self.value {
try (v as Encodable).encode(to: encoder) // swiftlint:disable:this force_cast
} else {
try container.encodeNil()
}
}
}

extension RealmOptional : Decodable where Value : Decodable {
public convenience init(from decoder: Decoder) throws {
// Initialize self here so we can get type(of: self).
self.init()
assertTypeIsDecodable(Value.self, in: type(of: self))

let container = try decoder.singleValueContainer()
if !container.decodeNil() {
let metaType = (Value.self as Decodable.Type) // swiftlint:disable:this force_cast
let element = try metaType.init(from: decoder)
self.value = (element as! Value) // swiftlint:disable:this force_cast
}
}
}

Why does Realm use RealmOptional Int rather than Int? for optional properties?

Realm model classes automatically implement getters and setters for your persisted properties that access the underlying database data. In order to provide these getters and setters, your properties must be declared with the dynamic modifier. This modifier asks Swift to dynamically dispatch accesses to the properties via getters and setters rather than directly accessing the member at compile time. The dynamic modifier comes with a significant limitation: it is only supported for types that can be represented in Objective-C. This is because Swift's dynamic dispatch is built on top of the Objective-C runtime. It is this limitation that prevents Int? from being directly supported by Realm.

You may wonder how String?, NSData?, and NSDate? are supported given this limitation. The answer is that they have natural equivalents in Objective-C, namely nullable NSString *, nullable NSData *, and nullable NSDate *. No such equivalents exist for Swift's numeric types.

Bool and Int properties not added to Realm

To make optional int's and bool's in Realm, they must be defined as RealmOptional.

So update your model like this

@objcMembers class CategoryDoc: Object: Codeable {
dynamic var name: String?

let isActive = RealmOptional<Int>()
let v = RealmOptional<Bool>()

Unwrapping an Optional value in swift and realm

As from comments if appears that your view is not yet loaded and some of your views are still nil. Your app crashes because in line limitLabel.text = limit[0].limitSum the limitLabel is nil. It would crash regardless of Realm even by calling limitLabel.text = "Hello world!"

You can always guard data that you need to avoid changes in your code. Simply add

guard let limitLabel = limitLabel else { return nil } 
guard let ForThePeriod = ForThePeriod else { return nil }

and so on.

I tried to clean up your code a bit. It is hard to understand what exactly are you trying to achieve but something like the following may seem a bit more appropriate:

func leftLabels() {
// Elements needed for method to execute.
guard let limitLabel = limitLabel else { return }
guard let forThePeriodLabel = forThePeriodLabel else { return }
guard let availableForSpendingLabel = availableForSpendingLabel else { return }

// Items that will be reused throughout the method later on
let limits: [Limit]
let firstLimit: Limit
let dates: (start: Date?, end: Date?)
let filterLimit: Int

limits = self.realm.objects(Limit.self)
guard limits.isEmpty == false else { return }
firstLimit = limits[0]

// limitLabel
limitLabel.text = firstLimit.limitSum

// Date components
dates = {
let calendar = Calendar.current
let formatter = DateFormatter()
formatter.dateFormat = "yyyy/MM/dd HH:mm"

let firstDay = firstLimit.limitDate as Date
let lastDay = firstLimit.limitLastDate as Date

let firstComponent = calendar.dateComponents([.year, .month, .day], from: firstDay)
let lastComponent = calendar.dateComponents([.year, .month, .day], from: lastDay)

let startDate = formatter.date(from: "\(firstComponent.year!)/\(firstComponent.month!)/\(firstComponent.day!) 00:00")
let endDate = formatter.date(from: "\(lastComponent.year!)/\(lastComponent.month!)/\(lastComponent.day!) 23:59")

return (startDate, endDate)
}()

// forThePeriodLabel
filterLimit = realm.objects(SpendingDB.self).filter("self.date >= %@ && self.date <= %@", startDate ?? "", endDate ?? "").sum(ofProperty: "cost")
forThePeriodLabel.text = String(filterLimit)

// availableForSpendingLabel
availableForSpendingLabel.text = {
guard let a = Int(firstLimit.limitSum) else { return "" }
let b = filterLimit
let c = a - b
return String(c)
}()
}

Note some practices which help you better to structure and solve your code.

  • Guard dangerous data at first
  • Create a list of reusable items for your method (there should be as fewer as possible, in most cases none). Note how these can be later assigned to. And if you try using it before assigning to it, you will be warned by your compiler.
  • Wrap as much code into closed sections such as availableForSpendingLabel.text = { ... code here ... }()
  • Use tuples such as let dates: (start: Date?, end: Date?)
  • Don't be afraid of using long names such as availableForSpendingLabel

I would even further try and break this down into multiple methods. But I am not sure what this method does and assume that you have posted only part of it...

========== EDIT: Adding alternate approach ==========

From comments this is a financial application so probably at least dealing with Decimal numbers would make sense. Also introducing approach with adding a new structure which resolves data internally. A formatter is also used to format the number. And some other improvements:

struct Limit {
let amount: Decimal
let startDate: Date
let endDate: Date
}

struct Spending {
let cost: Decimal
let date: Date
}

struct LimitReport {
let limitAmount: Decimal
let spendingSum: Decimal
let balance: Decimal

init(limit: Limit) {
let limitAmount: Decimal = limit.amount
let spendingSum: Decimal = {
let calendar = Calendar.autoupdatingCurrent // Is this OK or should it be some UTC or something?
func beginningOfDate(_ date: Date) -> Date {
let components = calendar.dateComponents([.day, .month, .year], from: date)
return calendar.date(from: components)!
}
let startDate = beginningOfDate(limit.startDate)
let endDate = calendar.date(byAdding: .day, value: 1, to: startDate)

let spendings: [Spending] = realm.objects(Spending.self).filter { $0.date >= startDate && $0.date < endDate }
return spendings.reduce(0, { $0 + $1.cost })
}()
let balance = limitAmount - spendingSum

self.limitAmount = limitAmount
self.spendingSum = spendingSum
self.balance = balance
}

}

func leftLabels() {
// Elements needed for method to execute.
guard let limitLabel = limitLabel else { return }
guard let forThePeriodLabel = forThePeriodLabel else { return }
guard let availableForSpendingLabel = availableForSpendingLabel else { return }

guard let limit = self.realm.objects(Limit.self).first else { return }

let formatter = NumberFormatter()
formatter.numberStyle = .currency
formatter.currencySymbol = "$"

let report = LimitReport(limit: limit)

limitLabel.text = formatter.string(from: report.limitAmount)
forThePeriodLabel.text = formatter.string(from: report.spendingSum)
availableForSpendingLabel.text = formatter.string(from: report.balance)
}

How to make the RealmSwift RealmOptional compatible with Swift Codable?

Here is something you can do to work around the problem. Create a new class which supports decoding and has RealmOptional as its property.

class OptionalInt64: Object, Decodable {
private var numeric = RealmOptional<Int64>()

required public convenience init(from decoder: Decoder) throws {
self.init()

let singleValueContainer = try decoder.singleValueContainer()
if singleValueContainer.decodeNil() == false {
let value = try singleValueContainer.decode(Int64.self)
numeric = RealmOptional(value)
}
}

var value: Int64? {
return numeric.value
}

var zeroOrValue: Int64 {
return numeric.value ?? 0
}
}

Then, instead of using RealmOptional in your school class, use this new OptionalInt64 class,

class School: Object, Codable {

@objc dynamic var id: Int64 = 0

@objc dynamic var name: String?
@objc dynamic var numberOfStudents: OptionalInt64?
var classes = List<Class>()

enum CodingKeys: String, CodingKey {
case id
case name
case numberOfStudents
case classes
}
}

Note that now instead of using RealmOptional, you are using RealmNumeric? which is of type Optional. Since, it is optional, automatic decoding uses decodeIfPresent method to decode the optional value. And if it is not present in json the value will simply become nil.

Exception throw for null data with optional Int type in Realm

The screenshot from the debugger shows that elevationFt is an empty string, which is not a number or null, so it is not a valid value for an optional int property.

Realm Swift Composite Keys for Optional Properties

You need to set RealmOptional's value member. RealmOptional properties cannot be var because Realm can't detect assignment to property types that cannot be represented by the Objective-C runtime, which is why RealmOptional, List and LinkingObjects properties must all be let.

class Item: Object {
dynamic var id = 0
let importantNumber = RealmOptional<Int>()
let importantNumber2 = RealmOptional<Int>()

func setCompoundID(id: Int) {
self.id = id
compoundKey = compoundKeyValue()
}

func setCompoundImportantNumber(importantNumber: Int) {
self.importantNumber.value = importantNumber
compoundKey = compoundKeyValue()
}

func setCompoundImportantNumber2(importantNumber2: Int) {
self.importantNumber2.value = importantNumber2
compoundKey = compoundKeyValue()
}

dynamic lazy var compoundKey: String = self.compoundKeyValue()

override static func primaryKey() -> String? {
return "compoundKey"
}

func compoundKeyValue() -> String {
return "\(id)\(importantNumber)\(importantNumber2)"
}
}

Optional Double in Realm

The problem is I can't find anything about making properties optional in Realm.

Well that's a shame, because you can actually do that in a migration.

public class MyMigration implements Realm.Migration {
@Override
public void migrate(DynamicRealm realm, long oldVersion, long newVersion) {
RealmSchema schema = realm.getSchema();
if(oldVersion == someNumber) {
RealmObjectSchema gasPipe = schema.get("GasPipe");
if(gasPipe.isRequired("gasPressure")) {
gasPipe.setRequired(false); // same as setNullable(true)
}
oldVersion++;
}
}

// equals, hashcode
}

And you need to bump schemaVersion on the configuration

RealmConfiguration config = new RealmConfiguration.Builder()
.migration(new MyMigration())
.schemaVersion(someNumber+1)
.build();


Related Topics



Leave a reply



Submit