0

I am a fairly new developer and I am using Xcode and Swift 5.1.

I am getting the error above when I delete a Member object from the Realm on the device that deletes it. All other devices running at the same time handle the deletion fine. I see this type of error has been asked about many times, but I don't see my case. Here is the setup:

  1. I have Members in a firestore database.
  2. I have a firestore observer that observes changes in that database and writes the changes to Realm on the device.
  3. In viewDidLoad in the MainVC I load the Members from Realm. And I set up a notification listener to observe changes in the Members in Realm.
  4. In a SettingsVC, I also also load the Members from Realm and set up a notification listener there as well.
  5. In the SettingsVC, I delete a Member using commit editingStyle and a google cloud function, which removes the member from the firestore database.
  6. My firestore observer picks up the changes and removes the Member from the Realm file.
  7. The listener in the SettingsVC, where the removal was done, picks up the changes in Realm and removes the Member from the tableView.
  8. Then it crashes with the error above.
  9. Note, if I have 3 simulators open at the same time and running the same version of the app, only the simulator (or device) that actually deletes the Member crashes. The other two remove the Member from the tableview and do not crash.

Here is the code found in viewDidLoad of the MainVC and the SettingsVC to load the Members and set up the notification listener:

var members:Results<Member>?

// Get the members from Realm.
UserService.loadMembers { (members) in
    self.members = members
}

// Set up a listener for changes in the members.
memberNotificationToken = members?.observe { [weak self] (changes: RealmCollectionChange) in
    // Make sure tableView has not been dismissed
    guard let tableView = self?.tableView else { return }
    tableView.reloadData()
}
}

// Turn off the listener.
deinit {
memberNotificationToken?.invalidate()
}

Here is the code in UserService.loadMembers:

static func loadMembers(completion: @escaping (_ members:Results<Member>?) -> Void) {

// Get a reference to the Realm database where the members are stored.
let realm = try! Realm()
// Get the members.
let members = realm.objects(Member.self).sorted(byKeyPath: "displayName")
// Send them back.
completion (members)
}

Here is the code to delete the Member:

UserService.deleteUser(memberToDelete: memberToDelete, completion: { status in
            if status != nil {
                Alerts.showBasicAlert(title: Generic Title, message: Generic message) { (alertController) in
                    self.present(alertController, animated: true)
                }
                // Reload the table.
                tableView.reloadData()
            }
        })
    })

And here is the code in the firestore observer that does the actual deleting of the member from the Realm:

            // And remove deleted members.
            try! realm.write {
                for m in deletedMembers {
                    if let id = m.value(forKey: "userId"), !m.isInvalidated {
                        realm.delete(realm.objects(Member.self).filter("userId=%@", id))
                    }
                }
            }

Please note also that I have this exact code to manage Realm Comment objects in the MainVC and it works fine. The only difference is that with the Members, there is a second viewController that has a separate listener (i.e., the MainVC has one and the SettingsVC has one, whereas with the Comments, only the MainVC is involved for deleting the Comment and updating the table).

ggorlen
  • 44,755
  • 7
  • 76
  • 106
TM Lynch
  • 403
  • 3
  • 13
  • I will take a look at the rest of the question but right off the bat; why are you doing this? You've got two databases; Firebase and Realm and the way you're using them here seems identical - you can do all of that with Firebase or Realm so having both just adds a lot of complexity. – Jay Dec 11 '19 at 17:56
  • Also... I think we need clarification. *I have Members in a firestore database.* and then *In viewDidLoad in the MainVC I load the Members from Realm*. How does that work? Also *In the SettingsVC, I delete a Member using commit editingStyle and a google cloud function* - why a cloud function instead of the Firestore API you're already using? That adds yet another layer of complexity. – Jay Dec 11 '19 at 17:58
  • I load only changes to the Members into Realm (including initial set) based on an updateDate field so that the firestore download cost is minimized. Otherwise, I would have to load all the Members each time the app is started. – TM Lynch Dec 11 '19 at 18:06
  • The Members in the firestore database are kept updated in Realm with a firestore observer that is checking for changes in updateDate in the member collection. The completion block of this firestore observer writes the changes to Realm. The deleted members component of this completion block is the code above. – TM Lynch Dec 11 '19 at 18:09
  • *The Firestore download cost*? what cost and for how much data...? What does *Otherwise, I would have to load all the Members each time the app is started?* mean.. Why? Also, this is unclear: *Here is the code to delete the Member: UserService....* but then right after you have the Firestore Observer code that deletes objects from realm... Which one is it? Are the members being deleted in the `UserService.deleteUser` class - a singleton? Or are the members being deleted in the Firestore Observer? What does `UserService` class do? – Jay Dec 11 '19 at 18:13
  • May I also ask why you're using two almost identical databases (functionality wise) for this task? It would be so much easier to just use one or the other. – Jay Dec 11 '19 at 18:15
  • Do you know of a way to persist firestore data on the iPhone? There is a persistence feature, but it is not the same as Realm to my knowledge. Otherwise, if the app is closed and reopened, all the member data (and other data) needs to be downloaded to the app. If 1 million users has to download even 100 members each time they open the app, that is a significant cost with firestore. – TM Lynch Dec 11 '19 at 18:28
  • Responding to: `Also, this is unclear: Here is the code to delete the Member: UserService.... but then right after you have the Firestore Observer code that deletes objects from realm... Which one is it? Are the members being deleted in the UserService.deleteUser class - a singleton? Or are the members being deleted in the Firestore Observer? What does UserService class do?`: The members in firestore are deleted with the UserService, which uses a cloud function. Then the observer notices the change, and updates the Realm file. – TM Lynch Dec 11 '19 at 18:34
  • The deletion of the members from firestore, and the update to Realm all works fine. Even the table view updates fine before the app crashes. The problem seems to be the Results in two viewControllers. But I can't even pinpoint which one is crashing. I have tried many breakpoints, subscriber breakpoint tests, turning them off in each one, etc. – TM Lynch Dec 11 '19 at 18:37
  • *Do you know of a way to persist firestore data* Yes, persistence is built in and works between app restarts. The data is cached on the device. You can read more about it here [Access data offline](https://firebase.google.com/docs/firestore/manage-data/enable-offline). If you have 1 million users then my hat is off to you - that's rare. From a design perspective though, why would 1 million users all be downloading 100 members; you can only display so many on a device and even that is limited to a few dozen characters for readability. I realize this is off topic but just something to consider. – Jay Dec 11 '19 at 19:22
  • Well, the persistence in firestore is offline versus online. I don't think there is persistence between app starts. But I might be missing something. The technique I'm using is not my own creation - it was suggested by a group of developers that faced a huge firestore bill when they migrated their 400k users to firestore and found this cost adds up. So they used a JSON file instead of realm to persist data and only download updates. I am using Realm. I use this technique for users, comments, and other data that gets changed. – TM Lynch Dec 11 '19 at 19:30
  • @Jay I fixed the problem. It was a stupid mistake as so many of my questions are. What happened was: in my alert, which shows after successful deletion, I was attempting to show the name of the member that was deleted. But of course that member was already gone. I found it by using a symbolic breakpoint with [RLMException]. That took me to the line, and then I worked backwards. That was a first for me using this debugging technique. Thank you for taking the time to respond to my question. It's people like you that make SO work. I will try to delete this question. – TM Lynch Dec 11 '19 at 19:34
  • Yes. Firestore persists data between app starts via Disk Persistence. I am using that now with a project. See @alexmamo answer to [this question](https://stackoverflow.com/questions/47111181/firestore-offline-data-merging-writes-maximum-time-of-offline-persistence) as well as the [official documentation](https://firebase.google.com/docs/database/ios/offline-capabilities) – Jay Dec 11 '19 at 19:35
  • Thank you for this information. I will definitely look into it. I am using firestore instead of Realm for cloud storage because of the firebase auth features and some other reasons. If I can persist and update as needed without loading, that would be ideal to keep it all in one place. Thanks again Jay! – TM Lynch Dec 11 '19 at 19:42

0 Answers0