Skip to content

Conversation

@arthef
Copy link

@arthef arthef commented Jun 21, 2025

Using NSUbiquitousKeyValueStore settings can be automatically shared between devices. The implantation follows convention of other implementations and is mainly based on the NSUserDefaultsSettings code.

Except standard initialization, no other special code is needed. However, to make key-value storage work, the app needs to be correctly configured in Xcode:

In the app project, TARGETS, Signing & Capabilities a new capability needs to be added (+ Capability):

  1. iCloud, Service: Key-value storage

Also, it is highly recommended to add and implement external change listener(s) to make the app aware of the settings change on other device.

Copy link
Owner

@russhwolf russhwolf left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the contribution! I've never worked with NSUbiquitousKeyValueStore so I want to make sure everything works as expected before merging this.

The most important item to that end is to make sure this class passes the test suite for Settings implementations, in order to verify that it behaves as expected. This means adding a test class that extends BaseSettingsTest. Because it requires entitlements, this takes a bit of setup. You can use the keychain-tests module as a template.

I have other priorities at the moment so I don't expect to get this merged before the next library release (1.4.0).

callback.invoke()
}
val observer = NSNotificationCenter.defaultCenter.addObserverForName(
name = NSUbiquitousKeyValueStoreDidChangeExternallyNotification,
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not certain this will work as intended. The documentation for NSUbiquitousKeyValueStoreDidChangeExternallyNotification suggests that notifications are only served from external changes, meaning that if we update a value in-app it won't propagate to listeners. This would break the expectations of ObservableSettings

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are correct here. The notification does not trigger to the app which made the change, which is, as I understand, your intended function.
The notification from the NSUbiquitousKeyValueStore changes is only triggered for apps running on other devices.
However, I could not see any other place or way or API to implement external changes notification for the app, therefore I used this one.
From what I read in the documentation, the NSUbiquitousKeyValueStore does not send notification to the local app at all.

If you have any idea or suggestion on how to implement this in a different way, please let me know. I can rewrite the code.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll have to think about this some. I'm not sure whether it makes sense to use ObservableSettings instead of Settings here. Do we eventually see updated values on get calls even without subscribing to external notifications?

Comment on lines 234 to 239
internal expect fun NSUbiquitousKeyValueStore.intForKey(defaultName: String): Int
internal expect fun NSUbiquitousKeyValueStore.setInt(value: Int, forKey: String)
internal expect fun NSUbiquitousKeyValueStore.longForKey(defaultName: String): Long
internal expect fun NSUbiquitousKeyValueStore.setLong(value: Long, forKey: String)
internal expect fun NSUbiquitousKeyValueStore.setFloat(value: Float, forKey: String)
internal expect fun NSUbiquitousKeyValueStore.floatForKey(defaultName: String): Float
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's no reason to use expect/actual for these if the implementations are the same for 32-bit and 64-bit systems.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I said, not fully understanding the library implementation I tried to keep it as close to the NSUserDefaults as possible. I can rewrite this part if you want. Please confirm.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, please rewrite it without expect/actual

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done


private fun addListener(key: String, callback: () -> Unit): SettingsListener {
val block = { _: NSNotification? ->
callback.invoke()
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This probably should be caching the previous value, similar to other implementations, in order to avoid triggering the listener when nothing has changed.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I ran several tests and have it working in my app. It looks like the NSUbiquitousKeyValueStore takes care of avoiding notifications if nothing changed. So, it basically handles it itself efficiently. Therefore, from my experience, caching here is not needed for the NSUbiquitousKeyValueStore implementation. However, I can put it back for consistency with other implementations.

Please confirm that this is what you want.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The issue is, if you have two different keys "a" and "b" but you're only observing "a", the notification will trigger for changes to both "a" and "b". The cache ensures that the update callbacks only trigger when the key you're observing changes.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added cache back and confirmed it works as expected during my tests.

arthef and others added 4 commits June 21, 2025 11:05
…ings/NSUbiquitousKeyValueStoreSettings.kt


Yes, I agree, this makes sense.

Co-authored-by: Russell Wolf <russhwolf@users.noreply.github.com>
…ings/NSUbiquitousKeyValueStoreSettings.kt


Ok, TBH, when I was working on the code, I did not fully understand the existing implementation and therefore, tried to keep the new class as close to the NSUserDefaults implementation as possible.

Co-authored-by: Russell Wolf <russhwolf@users.noreply.github.com>
@arthef
Copy link
Author

arthef commented Jul 22, 2025

Hopefully with my recent commits all concerns are resolved.
Please take a look and let me know if there is anything else to be done to accept the PR.

@russhwolf
Copy link
Owner

I'd like to get tests running against this as well. I can take a pass at it at some point but probably not soon.

@coreypett-cube
Copy link

Any ETA on this? Soon to fork and apply @arthef's work.

@russhwolf
Copy link
Owner

No ETA, sorry. I don't want to merge this without having tests running against it. Open to contributions there.

If you want it sooner you should be able to just copy NSUbiquitousKeyValueStoreSettings.kt to your project without needing to fork anything.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants