The proper way to setup Realm Platform


#1

I’ve downloaded the iOS ToDo sample and RayWenderlich Realm Platform code samples. But all are using try!first to write and then retrieve.

I am having difficulties retrieving first but stumble upon blank table view because the sync takes longer than the viewcontroller loading. So now, I’m trying to immediately sync upon login, but the delegate must wait until realm sync is done.

Can someone help reviewing my code, I keep getting error Operation Cancelled

Here’s the view model

class LoginViewModel {

    weak var delegate: LoginViewControllerDelegate?

    internal func login(with username: String, password: String) {

        RealmProvider.setup(username: username, password: password) { (success, error) in
            if success {
                self.delegate?.didLoginSuccessfully()
            } else {
                self.delegate?.didLoginWithError(errorMessage: error?.localizedDescription ?? "unknown error")
            }
        }
    }

}

And here’s the struct singleton (most of this code are from RayWenderlich)

import Foundation
import RealmSwift

struct RealmProvider {
    
    let configuration: Realm.Configuration

    static var realm: Realm?
    
    private init(config: Realm.Configuration) {
        configuration = config
    }
    
    func realm(callback: @escaping (Realm?, Error?) -> Void) {
        Realm.asyncOpen(configuration: configuration, callback: callback)
    }
    
    // MARK: - News realm
    private static let newsConfig: Realm.Configuration = {
        var config = SyncUser.current?.configuration(realmURL: Constants.NEWS_REALM_URL,
                                                             fullSynchronization: false,
                                                             enableSSLValidation: true,
                                                             urlPrefix: nil)       
        
        return config!
    }()
    
    public static var news: RealmProvider = {
        return RealmProvider(config: newsConfig)
    }()

    public static func setup(username: String, password: String, completion: @escaping (_ success: Bool, _ error: Error?) -> Void) {

        let credentials = SyncCredentials.usernamePassword(username: username, password: password)

        SyncUser.logIn(with: credentials, server: Constants.AUTH_URL, timeout: 5.0) { (user, error) in

            if let error = error {
                completion(false, error)
                return
            }

            if let user = user {
                #if DEBUG
                print("Realm login ok. User = \(user.identity ?? "unknown user!")")
                #endif

//                let syncConfig = SyncConfiguration(user: user, realmURL: Constants.NEWS_REALM_URL)
//                RealmProvider.realm = try! Realm(configuration: Realm.Configuration(syncConfiguration: syncConfig))
//                completion(true, nil)

                RealmProvider.news.realm(callback: { (realm, error) in
                    if let error = error {
                        completion(false, error)
                    }
                    if let realm = realm {
                        RealmProvider.realm = realm
                        completion(true, nil)
                    }

                })


            }
        }
    }
}

#2

I don’t know how your code works.
As I see, the RealmProvider.setup should make the login, and download the database.
The login code is ok, but the public static var news: RealmProvider = { return RealmProvider(config: newsConfig) }() is messed up.

You commented out the Realm creation - you still need that The commented code is not async, it will not wait for the db download, so you should use Realm.asyncOpen(configuration: configuration.
In the callback, you will have a successfully downloaded realm, so can call the completion block.


#3

Tried changing the setup() to this but still getting Operation canceled error.localizedDescription … any other clue?

public static func setup(username: String, password: String, completion: @escaping (_ success: Bool, _ error: Error?) -> Void) {
        
        let credentials = SyncCredentials.usernamePassword(username: username, password: password)
        
        SyncUser.logIn(with: credentials, server: Constants.AUTH_URL, timeout: 5.0) { (user, error) in
            
            if let error = error {
                completion(false, error)
                return
            }
            
            if let user = user {
                #if DEBUG
                print("Realm login ok. User = \(user.identity ?? "unknown user!")")
                #endif
                
                Realm.asyncOpen(configuration: RealmProvider.newsConfig, callback: { (realm, error) in
                    if let error = error {
                        #if DEBUG
                        print("Realm asyncopen error: \(error.localizedDescription)")
                        #endif
                        completion(false, error)
                    }
                    if let realm = realm {
                        self.realm = realm
                        completion(true, nil)
                    }
                })
            }
        }
    }

#4

Try to change the timeout. But I don’t think that is the issue but its worth a try :smiley:
Also, try with fullSynchronization = true.

Otherwise, I don’t see any problems, it should work. What is the exact error?


#5

lol … how to get the exact error? … same result for fullSynchronization = true …

I only see this asyncOpen from RayWenderlich book. Must it be an async way?

I’m trying this now and realm was set BUT … completion wasn’t delivered to the viewController

public static func setup(username: String, password: String, completion: @escaping (_ success: Bool, _ error: Error?) -> Void) {
        
        let credentials = SyncCredentials.usernamePassword(username: username, password: password)
        
        SyncUser.logIn(with: credentials, server: Constants.AUTH_URL, timeout: 5.0) { (user, error) in
            
            if let error = error {
                completion(false, error)
                return
            }
            
            if let user = user {
                #if DEBUG
                print("Realm login ok. User = \(user.identity ?? "unknown user!")")
                #endif
                
                do {
                    let realm = try Realm(configuration: RealmProvider.newsConfig)
                    self.realm = realm
                    completion(true, nil)
                } catch let error as NSError {
                    print("Realm error: \(error.localizedDescription)")
                    completion(false, error)
                }
                
//                Realm.asyncOpen(configuration: RealmProvider.newsConfig, callback: { (realm, error) in
//                    if let error = error {
//                        #if DEBUG
//                        print("Realm asyncopen error: \(error.localizedDescription)")
//                        #endif
//                        completion(false, error)
//                    }
//                    if let realm = realm {
//                        self.realm = realm
//                        completion(true, nil)
//                    }
//                })
            }
        }
    }

#6

naah… i think, forget the non-async open because … the app supposedly retrieve the realm file from the cloud first …


#7

Someone asked on Stackoverflow but no solution I could use …


#8

It doesnt need to be async, if you okay with blocking the thread, then you can use the simple Realm.open too.

The error should be printed after the print("Realm asyncopen error:
Or where do you see the issue? The error is nil and the realm nil too in the asyncOpen?


#9

@vinamelody Can you share the logs on the Server-side and the client-side? Server-side is shown on Studio and client side is set here:
https://docs.realm.io/platform/using-synced-realms/troubleshoot/errors#setting-the-client-logging-level

Knowing where the error is triggered would be helpful - for instance, are you able auth successfully but not open the realm?


#10

I put SyncManager.shared.logLevel = .detail inside AppDelegate but somehow not getting any other detail than Operation canceled … (should be cancelled here?)


#11

Okay, changed the setup() to below and the completion seems to wait. Why am I not sure? Because … after the LoginViewController push to next screen, I still need to initialise realm again.

public static func setup(username: String, password: String, completion: @escaping (_ success: Bool, _ error: Error?) -> Void) {
        
        let credentials = SyncCredentials.usernamePassword(username: username, password: password)
        
        SyncUser.logIn(with: credentials, server: Constants.AUTH_URL, timeout: 5.0) { (user, error) in
            
            if let error = error {
                completion(false, error)
                return
            }
            
            if let user = user {
                #if DEBUG
                print("Realm login ok. User = \(user.identity ?? "unknown user!")")
                #endif

                Realm.asyncOpen(configuration: RealmProvider.newsConfig, callback: { (realm, error) in
                    if let error = error {
                        #if DEBUG
                        print("Realm asyncopen error: \(error.localizedDescription)")
                        #endif
                        completion(false, error)
                    }
                    if let realm = realm {
                        self.realm = realm
                        completion(true, nil)
                    }
                })
            }
        }
    }

And the next view controller must somehow try getting the realm again:

class SpeakersViewController: UITableViewController {
    
    private let viewModel = SpeakerViewModel()
    private let speakerCellId: String = "speakerCell"
    
    private let realm: Realm
    private let speakers: Results<Speaker>
    private var speakersToken: NotificationToken?

    override init(nibName nibNameOrNil: String? = nil, bundle nibBundleOrNil: Bundle? = nil) {
        // this works
//        let syncConfig = SyncConfiguration(user: SyncUser.current!, realmURL: Constants.NEWS_REALM_URL)
//        self.realm = try! Realm(configuration: Realm.Configuration(syncConfiguration: syncConfig))
//        self.speakers = realm.objects(Speaker.self)

        // but this is better?
        do {
            self.realm = try Realm(configuration: RealmProvider.news.configuration)
            self.speakers = realm.objects(Speaker.self)
            print("Init Speakers: \(self.speakers)")
        } catch let error {
            fatalError("Realm error: \(error.localizedDescription)")
        }
        super.init(nibName: nil, bundle: nil)
    }

.....

#12

To add more context on why it must be an asyncOpen is due to this documentation page https://docs.realm.io/platform/using-synced-realms/opening-a-synced-realm#synchronously-opening-a-realm


#13

Are you sure that this:

 var config = SyncUser.current?.configuration(realmURL: Constants.NEWS_REALM_URL,
                                                             fullSynchronization: false,
                                                             enableSSLValidation: true,
                                                             urlPrefix: nil)

produces a valid configuration? Thats the only change between the working code.


#14

hmm looks like I changed the fullSynchronization to true