Realm setup takes longer than query


#1

Hi, I want to use Realm Cloud for our next ios conference app. Writing to realm seems to be ok but I’m stuck in showing all speakers.

Here’s the first screen. I am calling a viewModel. But the viewModel’s getAllSpeakers() returns nil because the init() is slower. This is because on the debug console, I’m getting Speaker: No realm first, and then Realm set

Thanks a lot for your help!

class SpeakersViewController: UITableViewController {
    
    private let viewModel = SpeakerViewModel()
    private let speakerCellId: String = "speakerCell"
    private var speakers: Results<Speaker>?
    private var speakersToken: NotificationToken?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
//        viewModel.initSampleSpeakers()
        
        speakers = viewModel.getAllSpeakers()
        tableView.register(UITableViewCell.self, forCellReuseIdentifier: speakerCellId)
        
    }
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(true)
        
        speakersToken = speakers?.observe({ [weak tableView] (changes) in
            guard let tableView = tableView else { return }
            
            switch changes {
            case .initial:
                tableView.reloadData()
            case .update(_, let deletions, let insertion, let updates):
                tableView.applyChanges(deletions: deletions, insertions: insertion, updates: updates)
            case .error(let error):
                #if DEBUG
                print("SpeakersViewController error: \(error.localizedDescription)")
                #endif
            }
            
        })
    }
    
    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        speakersToken?.invalidate()
    }
    
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return speakers?.count ?? 0
    }
    
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        guard let speaker = speakers?[indexPath.row] else {
            return UITableViewCell()
        }
        
        let cell = tableView.dequeueReusableCell(withIdentifier: speakerCellId, for: indexPath)
        cell.imageView?.image = UIImage(imageLiteralResourceName: speaker.imageFilename)
        cell.textLabel?.text = speaker.name
        return cell
    }
}

The viewModel:

class SpeakerViewModel {
    
    private var realm: Realm? {
        didSet {
            print("Realm set")
        }
    }
    
    init() {
        RealmProvider.news.realm { [weak self] realm, error in
            guard error == nil, let realm = realm else {
                fatalError("Database error: \(error?.localizedDescription ?? "unknown error")")
            }
            self?.realm = realm
        }
    }
    
    internal func getAllSpeakers() -> Results<Speaker>? {
        guard let realm = self.realm else {
            print("Speaker: No realm")
            return nil
        }

        let speakers = realm.objects(Speaker.self)
        let sortedByName = speakers.sorted(byKeyPath: Speaker.Property.name.rawValue)
        return sortedByName
    }

// I've also tried this
    internal func getAllSpeakers() -> Results<Speaker>? {
        let realm = try! Realm()
        guard realm.isEmpty else {
        print("Speaker: No realm")
        return nil
        }
        return Speaker.all(in: realm)
    }
    
    internal func initSampleSpeakers() {
        
        guard let realm = self.realm else {
            print("No realm")
            return
        }
        
        try! realm.write {
            realm.deleteAll()
        }
        
        let subh = Speaker(name: "Subhransu", imageFilename: "subh")
        subh.company = "SPGroup"
        subh.twitter = "@subhransu"
        subh.shortBio = "Some cool guy am I?"
        subh.add(to: realm)
        
        let tim = Speaker(name: "Tim Oliver", imageFilename: "tim")
        tim.company = "Mercari"
        tim.twitter = "@timoliver"
        tim.shortBio = "I'm that N5 guy"
        tim.add(to: realm)
        
        let ykm = Speaker(name: "Yeo Kheng Meng", imageFilename: "ykm")
        ykm.company = "SPGroup"
        ykm.twitter = "@yeokm1"
        ykm.shortBio = "I am a software engineer and a pilot!"
        ykm.add(to: realm)
    }
    
}

And I have a RealmProvider struct

struct RealmProvider {
    
    let configuration: Realm.Configuration
    
    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: true,
                                                             enableSSLValidation: true,
                                                             urlPrefix: nil)       
        
        return config!
    }()
    
    public static var news: RealmProvider = {
        return RealmProvider(config: newsConfig)
    }()
}


#2

@vinamelody You should use asyncOpen and block the thread until the download is complete - then within that callback you can make calls to render the view.


#3

Ah, okay looks like this is caused by how I tried doing asyncOpen. The solution is to not trying to download the realm in the viewModel but to wait until the asyncOpen finishes. See this for more info https://forums.realm.io/t/the-proper-way-to-setup-realm-platform/2167/11