Is it reasonable to create ReamManager singleton?


#1

Is it reasonable to create ReamManager singleton which will do any operations with Realm, (for example write to RealmDB) and use it anywhere in the project? Because I tried to do so and I got errors:

Terminating app due to uncaught exception ‘RLMException’, reason: 'Cannot modify managed RLMArray outside of a write transaction.

Or I must import RealmSwift anywhere in project?


#2

Yes. All of my apps, we made some kind of manager that handles the Realm functions (opening, syncing, migration etc.)
Realm is typesafe - its made in a way that you get an error if you try to access it in a different thread. In that case, you will get a RLMException.

The “Cannot modify managed RLMArray outside of a write transaction.” means exactly like what it said - you try to change a Realm object property while you are not in a realm.write transaction.

I have an architect/pattern, that fix both of these errors.
I can write about it more if you want, but its basically a factory-manager pattern with a small change: I map all RealmObjects to structs, so i can pass it freely everywhere :slight_smile:
It’s not perfect but I think its great.


#3

Hello @freeubi . Please write about it more. Thank you.


#4

Hello @freeubi. Can you show examples? Thank you.


#5

Sorry but I can’t right now - at least not without sharing a lot of my apps data and I dont want to do that.
I’m planning to do an open source version of it (just the default things) but I need to allocate time to do that.


#6

First, a few thought:

  • I made it in a way that I should fairy easy change the Realm db to any other. Because of that this is not the simplest architecht.
  • I like to use the Result framework [https://github.com/antitypical/Result], so its heavily build on using that, however its not necessery. I wrap a data and an error object with it, and make that as a result.
  • All of the realm operations will go on a dedicated Realm thread.
  • The communication between Factorys <-> Managers <-> UI made with callbacks, because of the thread switch. I am not happy with that.

I have a main RealmManager class with static functions. These functions are connected closely to realm, like user registration, login, logout, creating the realm, migrating, connect to the server etc [its not 100% true, because i have connection handlers too but it doesnt matter it this case].

Most of the apps has a user, so lets take that as an example.
I will have a RealmUser model to store the data and a User struct to use it on the UI.
The User can be mapped to RealmUser and RealmUser can be mapped to User too.

I will have a RealmUserFactory: this contains all the CRUD actions, queries etc. Also, in this class i’m switching to the dedicated realm thread too. You can access it, just only in the managers.

I will have a UserManager: this is the class that you can use everywhere. It calls the UserFactory methods, gets the result in a callback then map the data to the stuct and passes back to the caller.
When you call a manager like this, you need to decide if you need to switch to main thread or not - i dont want to go every call result into the main thread, but if i want i will put it here. This result a better code on the UI-s.

So the calls like this:

UI button: getUserByEmail() -> UserManager.getUserByEmail() -> RealmUserFactory.getUserByEmail() -> RealmManager.getCurrentRealm() return Realm() -> RealmUserFactory Query(), returning RealmUser -> UserManager return Map(from: RealmUser, to: User) -> UI writes userdata on screen


#7

Can I solve it via GCD?


#8

Yeah, i’m using it like the realm documents

// Query and update from any thread
DispatchQueue(label: “realm”).async {
autoreleasepool {
let realm = try! Realm()
let theDog = realm.objects(Dog.self).filter(“age == 1”).first
try! realm.write {
theDog!.age = 3
}
}
}

The get user factory function looks like this:


   public func getUserBy(realmIdentity: String,
                          callback: @escaping (Result<RealmUser?, NSError>) -> Void) {
        DispatchQueue(label: "realm").async {
            autoreleasepool {
                if let realm = try? Realm() {
                    let predicate = NSPredicate(format: "realmIdentity == %@", realmIdentity)
                    let userList = realm.objects(RealmUser.self).filter(predicate)
                    callback(.success(userList.first))
                } else {
                    callback(
                        .failure(
                            NSError(domain: "",
                                    code: 0,
                                    userInfo: [NSLocalizedDescriptionKey: "RealmUserFactory.Realm doesnt exist"])))
                }
            }
        }
    }