Realm Model Conversion Failed - swift

I have a realm db model like:
import RealmSwift
class User: Object {
let isVerified = RealmOptional<Bool>()
#objc dynamic var pk = 0
#objc dynamic var profilePicUrl: String? = nil
}
And I am getting data from service and it returns same name and type like realm model.
I want to save this data to db. But when I try to convert model to realm model it gives error.
let data = [Users(value: serviceUser)] -> serviceUser comes from service.
Before save when I try to convert I get this error:
'RLMException', reason: 'Invalid value 'ServiceUserModel(isVerified: Optional(false), pk: Optional(123456), profilePicUrl: Optional("") of type '__UserDataValue' for 'bool' property 'Users.isVerified'.'
serviceUser Model:
public struct ServiceUserModel: Codable {
public var isVerified: Bool?
public var pk: Int?
public var profilePicUrl: String?
}
I do not want to use for loop because of performance problem. I want to save this data in one time.
Save Method:
func save(users: [Users]){
try! database.write {
database.add(users)
}
}
How can I convert it?

I think the problem is located here:
when I try to convert
When dealing with RealmOptional you have to set it's .value property.
let isVerified = RealmOptional<Bool>()
now the isVerified.value is nil
isVerified.value = true
now it's true
But you can't use the Bool? to set the value.
The same applies to pk.
You should implement the convenience init() and provide some default values for your optionals

Related

How I can get new unique id for each new object in Realm?

I try create objects in Realm with unique id. I use this code:
class Persons: Object {
#objc dynamic var id = 0
#objc dynamic var name = ""
override static func primaryKey() -> String? {
return "id"
}
}
and in my StorageManager I use this code:
import RealmSwift
let realm = try! Realm()
class StorageManager {
static func saveObject(_ person: Persons) {
try! realm.write {
realm.add(person)
}
}
static func deleteObject(_ person: Persons) {
try! realm.write {
realm.delete(person)
}
}
}
But when I add second new object, I get error:
Terminating app due to uncaught exception 'RLMException', reason:
'Attempting to create an object of type 'Persons' with an existing
primary key value '0'.'
How I can get new unique id for each new object?
Your best bet is to let your code define that for you
class PersonClass: Object {
#objc dynamic var id = UUID().uuidString
#objc dynamic var name = ""
override static func primaryKey() -> String? {
return "id"
}
}
UUID().uuidString returns a unique string of characters which are perfect for primary keys.
HOWEVER
That won't work for Realm Sync in the future. MongoDB Realm is the new product and objects need to have a specific primary key property called _id which should be populated with the Realm ObjectID Property. So this is better if you want future compatibility
class PersonClass: Object {
#objc dynamic var _id = ObjectId()
#objc dynamic var name = ""
override static func primaryKey() -> String? {
return "_id"
}
}
You can read more about it in the MongoDB Realm Getting Started Guide
Note that after RealmSwift 10.10.0, the following can be used to auto-generate the objectId
#Persisted(primaryKey: true) var _id: ObjectId
Keeping in mind that if #Persisted is used on an object then all of the Realm managed properties need to be defined with #Persisted
Note if using #Persisted then the override static func primaryKey() function is not needed
The better answer since Realm v10 came out is to use the dedicated new objectID class
A 12-byte (probably) unique object identifier.
ObjectIds are similar to a GUID or a UUID, and can be used to uniquely identify objects without a centralized ID generator. An ObjectID consists of:
A 4 byte timestamp measuring the creation time of the ObjectId in seconds since the Unix epoch.
A 5 byte random value
A 3 byte counter, initialized to a random value.
ObjectIds are intended to be fast to generate. Sorting by an ObjectId field will typically result in the objects being sorted in creation order.

How to map a Firestore DocumentID to a RealmObject attribute?

I'm trying to provide some data in the cloud with Firestore that can be downloaded and stored in a Realm database on an iOS device.
The structure of my object that I want to store is:
import Foundation
import RealmSwift
import FirebaseFirestore
import FirebaseFirestoreSwift
#objcMembers class Flashcard: Object, Codable{
#objc dynamic var id: String? = NSUUID().uuidString
#objc dynamic var character: String?
#objc dynamic var title: String?
#objc dynamic var translation: String?
#objc dynamic var created: Date = Date()
let deck = LinkingObjects<FlashcardDeck>(fromType: FlashcardDeck.self, property: "cards")
override class func primaryKey() -> String? {
return "id"
}
private enum CodingKeys: String, CodingKey {
case id
case character
case translation
case created
case title
}
If I try to map the documentID to my id attribute with
#DocumentID #objc dynamic var id: String? = NSUUID().uuidString
If get the following error:
'Primary key property 'id' does not exist on object 'Flashcard'
How can I solve this problem?
EDIT: To make it more understandable here is a screenshot of my Firestore database:
The collection "PredifinedDecks" will store many decks. For example the id = DF59B1B3-BD22-47CE-81A6-04E7A274B98F represents one deck. Each deck will store an array/List with cards in it.
Not sure I fully understand the question but let me address this at a high level.
It appears there is a PredefinedDecks (a collection) that contains documents. Each document has a field (an array) of cards and some other field data. If the goal is to read in all of the documents (the decks) and their child data and store them as Realm objects, here's one solution. Start with a Realm object to hold the data from Firestore
class DeckClass: Object {
#objc dynamic var deck_id = ""
#objc dynamic var created = ""
#objc dynamic var title = ""
let cardList = List<CardClass>()
convenience init(withDoc: QueryDocumentSnapshot) {
self.init()
self.deck_id = withDoc.documentID
self.title = withDoc.get("title") as? String ?? "no title"
self.created = withDoc.get("created") as? String ?? "no date"
let cardArray = withDoc.get("cards") as? [String]
for card in cardArray {
let card = CardClass(withCard: card) {
self.cardList.append(card)
}
}
}
}
With this, you simply pass the documentSnapshot from Firestore for each document and the class will populate its properties accordingly.
and the code to read Firestore
func readDecks() {
let decksCollection = self.db.collection("PredefinedDecks")
decksCollection.getDocuments(completion: { documentSnapshot, error in
if let err = error {
print(err.localizedDescription)
return
}
for doc in documentSnapshot!.documents {
let deck = DeckClass(withDoc: doc)
self.decksList.append(deck) //a Realm List class object? Something else?
}
})
}

Can't save string to Realm Swift

I'm trying to save a string to Realm. For some reason, a nil value is being saved to Realm. Here is the function where I save to Realm (imageKey is the String)
func saveImageKey(_ imageKey: Data){
do{
try realm.write{
realm.add(imageKey)
}
}
catch{
print("Error saving imageKey: \(error)")
}
}
I'm not sure if this affects Realm, but when initializing the imageKey variable, I used a didSet to reorganize the imageKey objects by date created as follows:
var imageKey: Results<Data>?{
didSet{ //every time imageKey is updated, this will be called
imageKey = imageKey!.sorted(byKeyPath: "date", ascending: false)
print(imageKey)
}
}
This is how I converted the imageKey string into a Data object:
let newData = Data()
newData.imageKey = "item" + String(textCount)
saveImageKey(newData)
This is the class definition for Data:
class Data: Object{
#objc dynamic var text: String = ""
#objc dynamic var imageKey: String = ""
#objc dynamic var date = NSDate().timeIntervalSince1970
}
I have already checked for common errors, such as not initializing Realm, not adding the dynamic to the variable declarations in the Data class.
What could the issue be? Please let me know if you need more code/information.

Updating Struct variables on a singleton instance

I am trying to update a locally stored feature flag values on my singleton instance based on the UI changes and having trouble implementing a solution. Any help here is much appreciated.
The following is the singleton class that gets instantiated during app launch and the Featureflag values are read from remote and is cached in the "cachedKillSwitchValues" attribute. I have also detailed the implementation of the "KillSwitchValuesCacheEntry" and the "KillSwitches" struct as well.
The feature flag values are displayed in the debug screen of the app so we can override the remote values by toggling them. When this happens I have to override the cachedKillSwitchValues to retain the changes.
On the UI I convert the KillSwitches() object into "var tableData: [(String, Bool)] = []" so it becomes easier to load the table View data . The challenge is I am confused on how to update the cachedKillSwitchValues since my updateLocalKillSwitchCache(key: String, value: Bool) requires a string and a bool argument while I see the only way to update the KillSwitches are by accessing the attribute directly. I took a shot at coding the update method but couldn't complete.
public final class AppConfigurationRepository {
public static let shared = AppConfigurationRepository()
private var cachedKillSwitchValues: KillSwitchValuesCacheEntry?
private init() {
cachedKillSwitchValues = KillSwitchValuesCacheEntry(entryItem: KillSwitches(), lastUpdateMetaDate: Date())
}
public func updateLocalKillSwitchCache(key: String, value: Bool) {
// The code below is wrong, tried implementing multiple ways but went no where.
var killSwithDict = cachedKillSwitchValues?.entryData.dictionary()
killSwithDict?.updateValue(value, forKey: key)
cachedKillSwitchValues?.entryData = killSwithDict as KillSwitches
}
}
struct KillSwitchValuesCacheEntry: Codable {
var entryData: KillSwitches
let remoteConfigLastUpdateDate: Date
init(entryItem: KillSwitches, lastUpdateMetaDate: Date) {
self.entryData = entryItem
self.remoteConfigLastUpdateDate = lastUpdateMetaDate
}
}
public struct KillSwitches: Codable {
public var featureA: Bool = true
public var featureB: Bool = true
public enum CodingKeys: String, CodingKey {
case featureA
case featureB
}
init() { }
}

How to save a struct to realm in swift?

It is easy to use Realm with classes by inheriting from Object. But how would I save a struct containing several fields to realm in Swift? E.g.
struct DataModel {
var id = 0
var test = "test"
}
I know the documentation is clear about supported types. But maybe there is nice workaround or - even better - someone from realm could write about future plans about structs.
I' suggest you to use protocols, to achive what you want.
1) Create your Struct
struct Character {
public let identifier: Int
public let name: String
public let realName: String
}
2) Create your Realm Object
final class CharacterObject: Object {
dynamic var identifier = 0
dynamic var name = ""
dynamic var realName = ""
override static func primaryKey() -> String? {
return "identifier"
}
}
3) Use protocols to transform our struct to Realm Object
public protocol Persistable {
associatedtype ManagedObject: RealmSwift.Object
init(managedObject: ManagedObject)
func managedObject() -> ManagedObject
}
4) Make your struct persistable
extension Character: Persistable {
public init(managedObject: CharacterObject) {
identifier = managedObject.identifier
name = managedObject.name
realName = managedObject.realName
}
public func managedObject() -> CharacterObject {
let character = CharacterObject()
character.identifier = identifier
character.name = name
character.realName = realName
return character
}
}
With these tools in place, we are ready to implement the insertion methods of our persistence layer.
5) Exemple to write datas
public final class WriteTransaction {
private let realm: Realm
internal init(realm: Realm) {
self.realm = realm
}
public func add<T: Persistable>(_ value: T, update: Bool) {
realm.add(value.managedObject(), update: update)
}
}
// Implement the Container
public final class Container {
private let realm: Realm
public convenience init() throws {
try self.init(realm: Realm())
}
internal init(realm: Realm) {
self.realm = realm
}
public func write(_ block: (WriteTransaction) throws -> Void)
throws {
let transaction = WriteTransaction(realm: realm)
try realm.write {
try block(transaction)
}
}
}
5) Use the magic!
let character = Character(
identifier: 1000,
name: "Spiderman",
realName: "Peter Parker"
)
let container = try! Container()
try! container.write { transaction in
transaction.add(character)
}
Amazing source : Using Realm with Value Types & My Article
To save a struct in Realm, means copying the data into a Realm Object. The reason why Realm Objects are classes and not structs is because they are not inert values, but auto-updating objects that represent the persisted data in Realm. This has practical benefits, such as the fact that a Realm Object's data is lazy loaded.
You can take advantage of Realm's approach by responding to the change notifications from a Realm instance. For example if your UITableView data source is based off an array property on a Realm Object, as long as you have an instance of that object, you are guaranteed that after the notification it represents the correct values. Used properly this can simplify your code versus having multiple copies of values as structs.
Swift 4 shortest answer
Save structs as Data in Realm
struct MyStruct : Codable { // Variables here }
class MyRealObject : Object {
#objc private dynamic var structData:Data? = nil
var myStruct : MyStruct? {
get {
if let data = structData {
return try? JSONDecoder().decode(MyStruct.self, from: data)
}
return nil
}
set {
structData = try? JSONEncoder().encode(newValue)
}
}
}
Use the magic
let realm = try! Realm()
try! realm.write {
let myReal = MyRealObject()
myReal.myStruct = MyStruct(....)
realm.add(myReal)
}
You can do what suggests Ludovic, or you can automate that process and get rid of that boilerplate code for each of your structs by using Unrealm.