Here is the solution for 2 apps in the same group sharing UserDefaults. I share SQLite database between two processes and I need to know when one process writes somethink to db. Classical flows are not triggered so I wrote a flow helper, which emit values in Kotlin when NSUserDefaults changes.
Implement NSObject
as a part of the Swift codebase (Swift code inspiration). Swift calls a Kotlin method when NSUserDefaults changes. Firstly define interfaces.
interface NSUserDefaultsKotlinHelper {
fun userDefaultsChanged()
}
interface SwiftInjector {
fun injectIntoSwift(nsUserDefaultsKotlinHelper: NSUserDefaultsKotlinHelper?)
}
Let that interface inject listener into Swift code :
class InterprocessObserver: NSObject, SwiftInjector {
let key: String = "interprocess_communication"
private var nsUserDefaultsKotlinHelper : NSUserDefaultsKotlinHelper?
private let userDefaults = UserDefaults.init(suiteName: "group.your.group.id")
override init() {
super.init()
userDefaults?.addObserver(self, forKeyPath: key, options: [.old, .new], context: nil)
}
func injectIntoSwift(nsUserDefaultsKotlinHelper: NSUserDefaultsKotlinHelper?) {
self.nsUserDefaultsKotlinHelper = nsUserDefaultsKotlinHelper
}
func dataChangedFromAnotherProcess(data : [AnyHashable : Any]) {
userDefaults?.set(data, forKey: key)
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) {
guard let _ = change, object != nil, keyPath == key else { return }
nsUserDefaultsKotlinHelper?.userDefaultsChanged()
}
deinit {
userDefaults?.removeObserver(self, forKeyPath: key, context: nil)
}
}
Inject listener in Kotlin - I will inject when a flow starts to collect:
class InterProcessCommunication(val interPlatformInjector: InterplatformInjector) : InterplatformInjector by interplatformInjector {
val testFlow: Flow<Emitter> = flow {
val channel = Channel<Emitter>(CONFLATED)
channel.trySend(Emitter.STAY_CALM)
val listener = object : IInterprocessCommunication {
override fun interProcessChanged() {
channel.trySend(Emitter.EMIT)
}
}
interPlatformInjector.injectListener(listener)
try {
for (item in channel) {
emit(item)
}
} finally {
interPlatformInjector.injectListener(null)
}
}
}
Objects creation with Koin would be:
//Swift
func initObservers() {
let interplatformInjector = InterprocessObserver()
initKoin(interplatformInjector : interplatformInjector)
}
//Kotlin
fun initKoin(interplatformInjector : InterplatformInjector){
startKoin {
module {
single {InterProcessCommunication(interplatformInjector)}
}
}
}
//Swift Second process (for example NotificationService)
func dataChanged(interprocessObserver : InterprocessObserver) {
interprocessObserver.dataChangedFromAnotherProcess(data) //data could be anythink - for example a string
}
The method dataChenged()
will trigger a Kotlin flow. Is this what you are looking for?
NSUserDefaultsDidChangeNotification
will not fire if the change is done from a different process, therefor I was trying key-value observer. – Metts