apple / sample-cloudkit-privatedb-sync

MIT License
172 stars 17 forks source link

The app compiles and runs but it does not seem to save any contacts #6

Closed ghecho closed 1 year ago

ghecho commented 1 year ago

I'm a bit stuck here. I've done the following:

  1. I downloaded the project.
  2. I verified that I was signed-in to my active developer account in Xcode.
  3. Xcode version is: 14.3.1 (14E300c).
  4. I configured the signing team, app bundle of my own and iCloud container (the container id is the same as the app id but with the iCloud prefix).
  5. I configured the iCloud container id in the Config.swift file.
  6. I ran the project on two devices, both showed "Did register for remote notifications" in the console.
  7. Both devices are running the latest public version of iOS and iPadOS (iOS 16.5.1 (20F75) and iPadOS 16.5.1 (20F75)) and both are signed-in with the same Apple ID Account.

When I run the app on the devices, it initially shows and empty contacts screen, when I try to add a new contact it just dismisses the screen and no contact is added and nothing happens on the other device.

Also, no message is shown on the console when trying to add a contact in the other device or the current one.

I'm attaching a screen recording of this running on an iPad.

https://github.com/apple/sample-cloudkit-privatedb-sync/assets/952873/e5193bbd-79d3-4fbf-8f1d-52ce21005bb1

Any ideas of what I might be missing?

Thanks in advance.

sjrmanning commented 1 year ago

Hi @ghecho — can you see records being created in CloudKit Console? Note that you may have to use the "Act as iCloud Account" feature that shows up in the bottom left menu when inspecting your CloudKit Database to inspect data for the same account your devices are logged into. Also does the same issue occur on your iOS device?

ghecho commented 1 year ago

Hi @sjrmanning .

I just logged-in to the CloudKit console and then I signed in via "Act as iCloud Account" with the account that's used on the devices and tried to see the records on the private database but no, I don't see any records in the CloudKit Console. I expected to see the record type of Contacts but I only see the default Users type:

Screenshot 2023-07-17 at 15 45 36
sjrmanning commented 1 year ago

Do you see any logs from CloudKit in the console? It seems like the save operation is failing, but there could be a few reasons why. In Xcode, under your target (e.g. "PrivateSync") in "Signing & Capabilities" do you see your container listed with a checkmark against it under the iCloud section?

It might be useful to add some logging to the database.save operation in ViewModel.swift's addContact function, something like this:

func addContact(name: String) async throws {
    let newRecordID = CKRecord.ID(zoneID: zone.zoneID)
    let newRecord = CKRecord(recordType: "Contact", recordID: newRecordID)
    newRecord["name"] = name

    do {
        let savedRecord = try await database.save(newRecord)
        let savedRecordName = savedRecord.recordID.recordName

        await MainActor.run {
            contacts[savedRecordName] = name
        }
        await saveLocalCache()
    } catch {
        debugPrint("Error saving contact: \(error)")
        throw error
    }
}

This will print out the error coming back from that database.save operation if it fails.

ghecho commented 1 year ago

Yes, the container was checked and NOT in red, it was black.

I added the logging and indeed the save operation was failing with this error:

"Error saving contact: <CKError 0x282dc4a50: \"Permission Failure\" (10/2007); server message = \"Invalid bundle ID for container\"; op = D75D9DD7D04D9C5A; uuid = 91BE5144-9469-4805-A71A-033AEB0AF32B; container ID = \"iCloud.my.org.bundle.here.PrivateSync\">"

Which seemed wierd. I re-checked that the App bundle ID was correct and that the iCloud Container id was the same but with the iCloud prefix and that it was the same in the Config.swift but I couldn't find any error.

I decided to create a new App ID my.org.bundle.here.privatesync1 with the corresponding container ID iCloud.my.org.bundle.here.privatesync1. I waited for Xcode to register the new ID and then clicked refresh on the iCloud container so it changed from red to black and then I ran the app, but now there's another error when trying to save:

Error saving contact: <CKError 0x281fa2e80: \"Bad Container\" (5/1014); \"Couldn\'t get container configuration from the server for container \"iCloud.my.org.bundle.here.privatesync1\"\">

I waited a few minutes without changing any code and then I ran the app again and now the error is:

Error saving contact: <CKError 0x28105df80: \"Zone Not Found\" (26/2036); server message = \"Zone does not exist\"; op = 42C3E47BEE3BDBB2; uuid = 06211F3E-A5D0-4E1B-A5CC-2351989466AB; container ID = \"iCloud.my.org.bundle.here.privatesync1\">

ghecho commented 1 year ago

So, I left more time pass in case there was something pending to propagate internally on CloudKit but no, same error Zone Not Found. I checked the code and I see that this should not happen since I'm seeing that there's a createZoneIfNeeded() function that should create the zone. Still, I was not seeing the zone Contacts on the CloudKit dashboard, so I manually created it on the private database.

I waited a couple minutes but I'm still seeing the same error.

ghecho commented 1 year ago

Ok, here's an update. I think I solved it.

  1. I deleted the app from one device and I told it also to delete the associated data.
  2. I rebuilt the app and put a breakpoint here:
Screenshot 2023-07-18 at 12 03 45

But it kept skipping the guard statement, I was super confused and not sure if the user defaults key was persisted even if the app was deleted.

  1. I commented that guard clause and re-run the app
  2. The app started working
  3. I uncommented the guard clause to revert to the original code
  4. The app now works as expected.

So here's my theory. I think that the guard clause has a bug in it. I think it should not be negated, right? From a quick look into the code, it's the same issue for the subscriptions.

I created a PR for this. Could you take a look in case I'm missing something in the PR?

Also, I'm still not sure why it didn't work when I manually created the zone on the private DB on the dashboard. Maybe the Zones are inside the user's private DB and independent of each others? Maybe I should've created using the "Act as iCloud Account" using the same account as the one on the devices?

sjrmanning commented 1 year ago

So it sounds like the original issue was that the bundle ID you used wasn't associated with the container you were using (Invalid bundle ID for container).

The guard clause is actually correct; another way to read it is isZoneCreated must be false, otherwise return (don't re-create zone). What likely happened is at some point you ran this on a device (maybe with another container, or bundle ID), the UserDefaults was false, so it tried to create a zone, and it succeeded and set that bool isZoneCreated to true, so on subsequent runs (with another container/bundle ID), it didn't even try to create a zone when it in fact needed to.

Regarding the zone creation on the dashboard, that's probably right — zones in the private database are still per-user, so unless your developer account and the iCloud account you're testing with are the same, you would need to use "Act as iCloud Account" to create the zone.

ghecho commented 1 year ago

It's weird, I double checked that it was not in red and that the part after iCloud matched the App Bundle ID 🤷🏻‍♂️. I have no idea what happened. I'm just glad that it's working now.

Ohh, I see my mistake with isZoneCreated. Yes, it's correct here on the repo. Sorry about that. I'll close my PR 😅.

BTW It now works perfectly and pretty much instantaneously. Thanks for the help.

sjrmanning commented 1 year ago

No problem, thanks for reaching out and I'm glad we were able to determine the issue! If you ever want to test with a fresh state, deleting and re-installing the app should clear that isZoneCreated flag from UserDefaults.