Local Realm dbs for multiple users - swift

I'm trying to implement a user based realm db. So each time a new users registers, a Realm file is created with his UID (from Firebase), or if exists, accesses that Realm.
To do that i created a singletone for the Realm calls and setting the Realm Configuration.
My issue now is that, every time i switch the users, the realm path changes (i'm printing after users logged in), but the Results reading are the same, as the users didn't change, or the realm did not switch. To be more clear, the queries results are giving same data.
If i open the actual Realms, the data is different.
I'm seeing this behavior in my tableView, just by loading data.
What am i doing wrong?
RealmServices:
import Foundation
import RealmSwift
import Firebase
class RealmServices {
private init() {}
static let shared = RealmServices()
let userID = Auth.auth().currentUser!.uid
var realm = try! Realm(configuration: Realm.Configuration.init(
fileURL: Realm.Configuration().fileURL!.deletingLastPathComponent().appendingPathComponent("\(Auth.auth().currentUser!.uid).realm")))
func create<T:Object> (_ object: T) {
do {
try realm.write {
realm.add(object)
}
} catch {
post(error)
}
}
func update <T: Object>(_ object: T, with dictionary: [String : Any?]) {
do {
try realm.write {
for (key,value) in dictionary {
object.setValue(value, forKey: key)
}
}
} catch {
post (error)
}
}
func delete<T:Object> (_ object: T) {
do {
try realm.write{
realm.delete(object)
}
} catch {
post (error)
}
}
func post (_ error: Error) {
NotificationCenter.default.post(name: NSNotification.Name("RealmError"), object: error)
}
func observeRealmErrors (in vc: UIViewController, completion: #escaping (Error?)-> Void) {
NotificationCenter.default.addObserver(forName: NSNotification.Name("RealmError"), object: nil, queue: nil) { (Notification) in
completion(Notification.object as? Error)
}
}
func stopObservingError (in vc: UIViewController){
NotificationCenter.default.removeObserver(vc, name: NSNotification.Name("RealmError"), object: nil)
}
}

Here's what I think is going on (I could be totally off but let me give it a shot)
Your RealmServices is a singleton and initialized only once. It's not a subclass where each time one is created, it's initialized
So any var's are set up and populated on first run - the realm property is not going to re-initialize each time it's accessed. For example, take this singleton
final class MySingleton {
private init() {}
var someUuid = UUID().uuidString
static let sharedInstance = MySingleton()
}
as you can see, someUuId is a 'randomly' generated uuid. If you access that property twice, it presents the same value, there's nothing to force an update.
print(MySingleton.sharedInstance.someUuid)
print(MySingleton.sharedInstance.someUuid)
results in
EBE131DE-B574-4CE1-8D74-E680D80A577B
EBE131DE-B574-4CE1-8D74-E680D80A577B
So, the solution is to set the users uid property in your question to a different value each time you want it changed. Here's a quick example
final class RealmService {
private init() {}
var uid: String!
var usersRealm: Realm! {
let path = Realm.Configuration().fileURL!.deletingLastPathComponent().appendingPathComponent("\(uid!).realm")
let realm = try! Realm(configuration: Realm.Configuration.init(fileURL: path))
return realm
}
static let sharedInstance = RealmService()
}
and here's how it's called; in this example we write item0 to user0's realm and item1 to user1's realm:
RealmService.sharedInstance.uid = "uid_0"
let user0Realm = RealmService.sharedInstance.usersRealm!
let item0 = ItemClass(name: "item 0")
try! user0Realm.write {
user0Realm.add(item0)
}
RealmService.sharedInstance.uid = "uid_1"
let user1Realm = RealmService.sharedInstance.usersRealm!
let item1 = ItemClass(name: "item 1")
try! user1Realm.write {
user1Realm.add(item1)
}
the result is a realm being created for each user with item0 in user0's realm and item1 being in user1's realm

Related

How can I get a variable declared in a class in another class?

I just started to use Swift/firebase, sorry for my noobiness. I am writing data into firebase database using childbyautoid. I want to access the last key/id in another class.
let postName = Database.database().reference().child("Event").childByAutoId()
let postNameObject = [
"EventName": NameTextField.text,
"timestamp": [".sv":"timestamp"],
"userID": Auth.auth().currentUser!.uid
] as [String:Any]
postName.setValue(postNameObject, withCompletionBlock: { error, ref in
if error == nil {
self.dismiss(animated: true, completion: nil)
} else {
}
})
let childautoID = postName.key
I want to be able to call childautoID in another class our find another way to update this node.
You could maybe make a singleton class for managing Firebase methods. I do something similar whenever building apps with Firebase. This lets you reference values and use methods related to Firebase globally, across multiple classes. Before I adopted this method, I found myself rewriting the same code for uploading objects to firebase. A singleton will let you create reusable code, including storage of a "last key" set in this global class.
class FirebaseManager {
//You've probably seen something similar to this "shared" in other Apple frameworks
//Maybe URLSession.shared or FileManager.default or UserDefaults.standard or SKPaymentQueue.default() or UIApplication.shared
static var shared = FirebaseManager()
func createEvent(name: String, timeStamp: [String:String], uid: String) {
let postNameObject = [
"EventName": name,
"timestamp": timeStamp,
"userID": uid
] as [String:Any]
postName.setValue(postNameObject, withCompletionBlock: { error, ref in
if error == nil {
self.dismiss(animated: true, completion: nil)
} else {
//Do something
}
})
let childautoID = postName.key
//Whatever else you need to do in the function below here...
}
}
Usage:
class SomeViewController: UIViewController {
#IBOutlet var nameTextField: UITextField!
//Some code...
#IBAction func createEventButtonPressed(_ sender: Any) {
FirebaseManager.shared.createEvent(name: nameTextField.text, timeStamp: [".sv":"timestamp"], uid: Auth.auth().currentUser!.uid)
}
//Some more code...
}
Similarly, you could add a value called lastKey of type String in our FirebaseManager class. Note the variable we add to the top of our FirebaseManager class:
class FirebaseManager {
var lastKey: String!
static var shared = FirebaseManager()
func createEvent(name: String, timeStamp: [String:String], uid: String) {
let postNameObject = [
"EventName": name,
"timestamp": timeStamp,
"userID": uid
] as [String:Any]
postName.setValue(postNameObject, withCompletionBlock: { error, ref in
if error == nil {
self.dismiss(animated: true, completion: nil)
} else {
//Do something
}
})
let childautoID = postName.key
//Whatever else you need to do in the function below here...
}
}
Similarly, we can set this value in one view controller:
class ViewControllerA: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
FirebaseManager.shared.lastKey = "aBcDeFg9876543210"
}
}
And grab this value in another view controller that loads following the previous view controller:
class ViewControllerB: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
print(FirebaseManager.shared.lastKey)
}
}
Prints: aBcDeFg9876543210
This is the beauty of the static keyword. You can learn more about creating singleton classes here: https://cocoacasts.com/what-is-a-singleton-and-how-to-create-one-in-swift

Observing realm changes are not firing

In my application I have a custom RealmDatabase class. It initializes the Realm database for me.
public class RealmDatabase: Database {
let realm: Realm
//
// I also provide a shared() singleton
//
public init(fileURL: URL) {
let config = Realm.Configuration(fileURL: fileURL)
Realm.Configuration.defaultConfiguration = config
realm = try! Realm()
}
public func observe<T>(_ block: #escaping ((T) -> Void), for object: T.Type) where T : Storable {
realm.objects(object).observe { (changes) in
print("Changes: ", changes)
}
}
}
Now, I also wrote a class called SyncEngine so that I can start syncing with CloudKit. The class looks like this:
public class SyncEngine: NSObject {
private let database: Database
public init(database: Database) {
self.database = database
super.init()
}
public func start() {
database.observe({ restaurant in
print("changes!")
}, for: Restaurant.self)
}
}
Now, in my AppDelegate I do the following:
let database = RealmDatabase.shared()
let syncEngine = SyncEngine(database: database)
syncEngine.start()
The problem, though, is that my observer is never fired and print("Changes: ", changes) is never printed to the console. I don't know what I'm doing wrong, though. Any ideas?
You're discarding the observation as you create it. To solve this, you need to retain the NotificationToken returned by observe.
var token: NotificationToken?
func start() {
token = database.observe { changes in ... }
}

Swift: Realm array empty after writing object

First of all, i'm new to Swift, so probably I have made some stupid mistake, but I have tried everything and I can't figure it out why this is happening.
So let me start. List property inside realm object is not being stored. That is first problem and second problem is that object I'm calling after adding object to realm loses it property value.
Here is the picture, so you guys can understand better what I'm saying
And now this is output:
PRODUCT BEFORE UPDATE: 34
Added new object
PRODUCT AFTER UPDATE:
I will show how my DBManager class looks:
import Foundation
import RealmSwift
class DBManager {
private var database:Realm
static let sharedInstance = DBManager()
private init() {
database = try! Realm()
}
func getDataFromDB() -> Results<Product> {
let results: Results<Product> = database.objects(Product.self)
return results
}
func getSingleDataFromDB(primaryKey: String) -> Product {
let product: Product = database.object(ofType: Product.self, forPrimaryKey: primaryKey)!
return product
}
func addData(object: Product) {
try! database.write {
database.add(object)
print("Added new object")
}
}
func updateData(object: Product) {
try! database.write {
database.add(object, update: true)
}
}
func deleteAllFromDatabase() {
try! database.write {
database.deleteAll()
}
}
func deleteFromDb(object: Product) {
try! database.write {
database.delete(object)
}
}
}
So If someone is seeing something that I'm not seeing, please let me know because I have lost many hours finding solution for this problem.
Thank you in advance

How to update a list of objects in Realm DB Swift

I'm trying to find the right way to update a list in a Realm object. For example:
class Person: Object {
#objc dynamic var id: String = ""
let children = List<Child>()
}
//Adding an item to the list
func add(child: Child, _ completion: (DBError?) -> Void) {
do {
let ctx = try Realm()
if let person = ctx.objects(Person.self).last {
try ctx.write({
person.children.append(child)
completion(nil)
})
} catch {
completion(DBError.unableToAccessDatabase)
}
}
This seems to work for adding an element. So how do I update an individual element in the array as well as replace the whole array to ensure it persists?

MVVM with realm: Passing Realm-results across threads?

Using Xcode-8.2.1, Swift-3.0.2, RealmSwift-2.2.0, iOS-Simulator-10:
I try applying the MVVM pattern (explained by Steve Scott here) using Realm.
Everything works until the moment (inside the VIEW-part - see below) where I try to access a viewmodel-property. It says: Realm accessed from incorrect thread
How could I still make the MVVM-pattern do its job of separating model, view-model and view but, on the same time, get thread-safety with realm ?
Is there a way to make Realm-results (i.e. Results<BalancesDataEntry>) being passed across threads ??
Here is my code:
(the issue happens at the very bottom, inside the View-part)
// REALM-OBJECT:
import Foundation
import RealmSwift
class BalancesDataEntry: Object {
dynamic var category: String = ""
dynamic var index: Int = 0
}
MODEL:
import Foundation
import RealmSwift
class MVVMCBalancesModel: BalancesModel
{
fileprivate var entries = [BalancesDataEntry]()
let realm = try! Realm()
init() {
self.createDataEntries()
}
fileprivate func createDataEntries() {
let myBalance = BalancesDataEntry()
myBalance.index = 0
myBalance.category = "Love"
try! self.realm.write {
self.realm.deleteAll()
self.realm.add(myBalance)
}
}
func getEntries(_ completionHandler: #escaping (_ entries: [BalancesDataEntry]) -> Void)
{
// Simulate Aysnchronous data access
DispatchQueue.global().async {
let realmThread = try! Realm()
let returnArray: [BalancesDataEntry] = Array(realmThread.objects(BalancesDataEntry.self))
completionHandler(returnArray)
}
}
}
VIEW-MODEL:
import Foundation
import RealmSwift
class MVVMCBalancesViewModel: BalancesViewModel
{
weak var viewDelegate: BalancesViewModelViewDelegate?
weak var coordinatorDelegate: BalancesViewModelCoordinatorDelegate?
fileprivate var entries: [BalancesDataEntry]? {
didSet {
viewDelegate?.entriesDidChange(viewModel: self)
}
}
var model: BalancesModel? {
didSet {
entries = nil;
model?.getEntries({ (myEntries) in
self.entries = myEntries
})
}
}
var title: String {
return "My Balances"
}
var numberOfEntries: Int {
if let entries = entries {
return entries.count
}
return 0
}
func entryAtIndex(_ index: Int) -> BalancesDataEntry?
{
if let entries = entries , entries.count > index {
return entries[index]
}
return nil
}
func useEntryAtIndex(_ index: Int)
{
if let entries = entries, let coordinatorDelegate = coordinatorDelegate , index < entries.count {
coordinatorDelegate.balancesViewModelDidSelectData(self, data: entries[index])
}
}
}
VIEW:
import UIKit
class MVVMCBalancesViewController: UIViewController {
#IBOutlet weak var label1Outlet: UILabel!
#IBOutlet weak var label2Outlet: UILabel!
var viewModel: BalancesViewModel? {
willSet {
viewModel?.viewDelegate = nil
}
didSet {
viewModel?.viewDelegate = self
refreshDisplay()
}
}
var isLoaded: Bool = false
func refreshDisplay()
{
if let viewModel = viewModel , isLoaded {
// !!!!!!! HERE IS THE ISSUE: Realm accessed from incorrect thread !!!!
self.label1Outlet.text = viewModel.entryAtIndex(0)?.category
self.label2Outlet.text = viewModel.entryAtIndex(1)?.category
} else {
}
}
override func viewDidLoad()
{
super.viewDidLoad()
isLoaded = true
refreshDisplay();
}
}
extension MVVMCBalancesViewController: BalancesViewModelViewDelegate
{
func entriesDidChange(viewModel: BalancesViewModel)
{
}
}
You can use ThreadSafeReference to pass Realm's thread-confined types (Object, Results, List, LinkingObjects) to a different thread. The documentation's section on Passing Instances Across Threads contains this example of passing a single instance of an Object subclass across threads:
let person = Person(name: "Jane")
try! realm.write {
realm.add(person)
}
let personRef = ThreadSafeReference(to: person)
DispatchQueue(label: "background").async {
let realm = try! Realm()
guard let person = realm.resolve(personRef) else {
return // person was deleted
}
try! realm.write {
person.name = "Jane Doe"
}
}
It can be used similarly for Results.
I have found a workaround (see below): Maybe you have better solutions - please let me know!
Here is my github-code realm_mvvm_c on github
After introducing a new protocol and making (pretty much everything) conform to it, things worked out.
Here is the protocol called DataEntry:
import Foundation
protocol DataEntry: class {
var idx: Int { get set }
var category: String { get set }
}
Now, make everything conform to it, such as
--> the realm object (i.e. class BalancesDataEntry: Object, DataEntry {...)
--> the getEntries return value (i.e. func getEntries(_ completionHandler: #escaping (_ entries: [DataEntry]) -> Void))
--> the View-Model's entries (i.e. fileprivate var entries: [DataEntry]? {..)
--> all the corresponding Model- and View-Model protocols also need the DataEntry datatype (see git-repo for complete picture)
After that, it was enough to change the completion-handler return-array of the MODEL's method getEntries(..) to a newly created object-instance (ie. DataEntryDub) that is keept conform to the DataEntry protocol:
func getEntries(_ completionHandler: #escaping (_ entries: [DataEntry]) -> Void)
{
// Simulate Aysnchronous data access
DispatchQueue.global().async {
let realmThread = try! Realm()
class DataEntryDub: DataEntry {
var idx: Int
var category: String
init(idx: Int, category: String) {
self.idx = idx
self.category = category
}
}
var returnArray = [DataEntry]()
for entry in realmThread.objects(BalancesDataEntry.self) {
returnArray.append(DataEntryDub(idx: entry.idx, category: entry.category))
}
completionHandler(returnArray)
}
}
Here is my github-code realm_mvvm_c on github