Ivan Ilic
07/12/2019, 1:14 PMlouiscad
07/12/2019, 7:33 PMIvan Ilic
07/15/2019, 9:21 AMsvyatoslav.scherbina
07/22/2019, 1:44 PMIvan Ilic
07/22/2019, 2:40 PMsvyatoslav.scherbina
07/22/2019, 2:44 PMIvan Ilic
07/22/2019, 2:57 PMIvan Ilic
07/24/2019, 11:21 AMobserveValueForKeyPath
function for catching new values.
Idea warns me that ‘observeValueForKeyPath’ overrides nothing.
Do you know how to do that?
Kotlin code
import kotlinx.cinterop.CValue
import platform.AVFoundation.*
import platform.CoreGraphics.CGRect
import platform.CoreGraphics.CGRectMake
import platform.Foundation.NSKeyValueObservingOptionNew
import platform.Foundation.NSURL
import platform.Foundation.addObserver
import platform.UIKit.UIView
import platform.darwin.NSObject
class PlayerOK {
private var observer = Observer()
private var asset: AVAsset
private var player: AVPlayer
private var playerItem: AVPlayerItem
private var playerLayer: AVPlayerLayer = AVPlayerLayer()
private val gcRect: CValue<CGRect> = CGRectMake(height = 100.0, width = 100.0, x = 20.0, y = 20.0)
val view: UIView = UIView(gcRect)
private val requiredAssetKeys = listOf("playable", "hasProtectedContent")
init {
val url =
NSURL(string = "<https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_adv_example_hevc/master.m3u8>")
asset = AVAsset.assetWithURL(URL = url)
// Create a new AVPlayerItem with the asset and an
// array of asset keys to be automatically loaded
playerItem = AVPlayerItem(asset = asset, automaticallyLoadedAssetKeys = requiredAssetKeys)
// Register as an observer of the player item's status property
playerItem.addObserver(observer, forKeyPath = "status", options = NSKeyValueObservingOptionNew, context = null)
// Associate the player item with the player
player = AVPlayer(playerItem = playerItem)
playerLayer.player = player
playerLayer.frame = view.bounds
view.layer.addSublayer(playerLayer)
player.play()
}
}
class Observer : NSObject() {
fun observeValueForKeyPath(
keyPath: String?,
ofObject: Any?,
change: Map<Any?, *>?,
context: kotlinx.cinterop.COpaquePointer?
) {
println("keyPath $keyPath")
println("ofObject $ofObject")
println("change $change")
println("context $context")
}
}
Using in swift
class ViewController: UIViewController {
var c: PlayerOK?
override func viewDidLoad() {
super.viewDidLoad()
c = PlayerOK()
self.view.addSubview(c!.view)
}
}
Xcode error:
iosApp[3203:1352905] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: '<sample.Observer: 0x2801446d0>: An -observeValueForKeyPath:ofObject:change:context: message was received but not handled.
Key path: status
Observed object: <AVPlayerItem: 0x28014ba00, asset = <AVURLAsset: 0x280368d00, URL = <https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_adv_example_hevc/master.m3u8>>>
Change: {
kind = 1;
new = 1;
}
Context: 0x0'
*** First throw call stack:
(0x209d7127c 0x208f4b9f8 0x209c7b4b0 0x20a7bc9b4 0x20a7bcb08 0x20a7bee9c 0x20a7bc3bc 0x20fca9f18 0x102cab6f0 0x102cacc74 0x102cba6fc 0x209d02c1c 0x209cfdb54 0x209cfd0b0 0x20befd79c 0x23652c978 0x1027c989c 0x2097c28e0)
libc++abi.dylib: terminating with uncaught exception of type NSException
Ivan Ilic
07/24/2019, 11:22 AMimport Foundation
import AVFoundation
import AVKit
class PlayerOK {
var observer:Observer = Observer()
var url: URL?
var asset: AVAsset?
var player: AVPlayer?
var playerItem: AVPlayerItem?
var playerLayer: AVPlayerLayer?
var view: UIView!
// Key-value observing context
private var playerItemContext = 3
let requiredAssetKeys = [
"playable",
"hasProtectedContent"
]
init(controller: UIViewController) {
let rect = CGRect(x: 0, y: 100, width: 100, height: 100)
view = UIView(frame: rect)
// Create the asset to play
url = URL(string: "<https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_adv_example_hevc/master.m3u8>")
asset = AVAsset(url: url!)
// Create a new AVPlayerItem with the asset and an
// array of asset keys to be automatically loaded
playerItem = AVPlayerItem(asset: asset!,
automaticallyLoadedAssetKeys: requiredAssetKeys)
// Register as an observer of the player item's status property
playerItem?.addObserver(observer,
forKeyPath: #keyPath(AVPlayerItem.status),
options: [.old, .new],
context: &playerItemContext)
// Associate the player item with the player
player = AVPlayer(playerItem: playerItem)
playerLayer = AVPlayerLayer(player: player)
playerLayer?.frame = view.bounds
view.layer.addSublayer(playerLayer!)
player?.play()
}
}
class Observer: NSObject {
override func observeValue(forKeyPath keyPath: String?,
of object: Any?,
change: [NSKeyValueChangeKey : Any]?,
context: UnsafeMutableRawPointer?) {
if keyPath == #keyPath(AVPlayerItem.status) {
let status: AVPlayerItem.Status
if let statusNumber = change?[.newKey] as? NSNumber {
status = AVPlayerItem.Status(rawValue: statusNumber.intValue)!
} else {
status = .unknown
}
// Switch over status value
switch status {
case .readyToPlay:
print("readyToPlay")
break
// Player item is ready to play.
case .failed:
print("failed")
break
// Player item failed. See error.
case .unknown:
print("unknown")
break
// Player item is not yet ready.
@unknown default:
print("Error")
}
}
}
}
ViewController:
import UIKit
class ViewController: UIViewController {
var c: PlayerOK?
override func viewDidLoad() {
super.viewDidLoad()
c = PlayerOK(controller: self)
self.view.addSubview(c!.view)
}
}
svyatoslav.scherbina
07/24/2019, 1:17 PMobserveValueForKeyPath
method could be a bit tricky.
It is originally declared as category method in Objective-C, thus imported as Kotlin extension which can’t be overridden.
To workaround this, you can configure interop with custom .def
file containing declaration of a protocol with this method, e.g. `observer.def`:
language = Objective-C
---
#import <Foundation/Foundation.h>
@protocol Observer
@required
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary<NSKeyValueChangeKey, id> *)change
context:(void *)context;
@end;
and then inherit this protocol in Kotlin.Ivan Ilic
07/24/2019, 2:18 PMIvan Ilic
07/25/2019, 9:30 AMsvyatoslav.scherbina
07/25/2019, 11:54 AMlouiscad
07/26/2019, 7:51 AMdealloc
method in NSObject
?svyatoslav.scherbina
07/26/2019, 11:00 AMdealloc
is not possible for different reason.louiscad
07/26/2019, 11:33 AMsvyatoslav.scherbina
08/01/2019, 1:50 PMayodele
04/19/2023, 9:39 AMmessage was received but not handled
.Ivan Ilic
04/19/2023, 9:57 AMayodele
04/19/2023, 9:58 AMDebayan
08/04/2025, 2:08 PMobserveValueForKeyPath
. I tried by creating an observer.def and defining target for the same in build.gradle.kts.
But it does not seem to work.
You can refer to this chat for more info on my gradle file: https://kotlinlang.slack.com/archives/C0346LWVBJ4/p1753443973039359svyatoslav.scherbina
08/05/2025, 8:28 AM.def
file
package = platform.FoundationCould you please try using another package (and import
NSKeyValueObservingProtocol
from that package) and see if anything changes?Debayan
08/05/2025, 9:35 AMkotlin {
androidTarget {
@OptIn(ExperimentalKotlinGradlePluginApi::class)
compilerOptions {
jvmTarget.set(JvmTarget.JVM_11)
}
}
listOf(
iosX64(),
iosArm64(),
iosSimulatorArm64()
).forEach { iosTarget ->
iosTarget.binaries.framework {
baseName = "ComposeApp"
isStatic = true
export(libs.kmpnotifier)
}
iosTarget.compilations.getByName("main") {
cinterops.creating {
definitionFile.set(file("src/nativeInterop/cinterop/observer.def"))
}
}
}
room {
schemaDirectory("$projectDir/schemas")
}
sourceSets {
androidMain.dependencies {
// dependencies
}
commonMain.dependencies {
// dependencies
}
nativeMain.dependencies {
implementation(libs.ktor.client.darwin)
}
// Android specific
dependencies {
// dependencies
}
}
}
This is what I tried last for observer.def using platform.Symbols:
package = platform.Symbols
language = Objective-C
---
#import <Foundation/Foundation.h>
@protocol Observer
@required
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary<NSKeyValueChangeKey, id> *)change
context:(void *)context;
@end;
Also I have the following in my gradle.properties:
kotlin.mpp.enableCInteropCommonization=true
svyatoslav.scherbina
08/05/2025, 9:35 AMDebayan
08/05/2025, 9:46 AMsvyatoslav.scherbina
08/05/2025, 10:09 AMIs the way I am creating cinterops in gradle using iosTarget correct?No. You have
cinterops.creating {
, while it should be val observer by cinterops.creating {
Debayan
08/05/2025, 1:52 PMKLIB resolver: Could not find "org.jetbrains.androidx.savedstate:savedstate" in [/Users/debz/branchTwo/Android-StoryTeller/composeApp, /Users/debz/.konan/kotlin-native-prebuilt-macos-aarch64-2.2.0/klib/common, /Users/debz/.konan/kotlin-native-prebuilt-macos-aarch64-2.2.0/klib/platform/ios_arm64]
and
Failed to generate cinterop for :composeApp:cinteropObserverIosArm64: Process 'command '/Users/debz/Library/Java/JavaVirtualMachines/temurin-22.0.2/Contents/Home/bin/java'' finished with non-zero exit value 1
As for the first issue, it is related to Koin versioning with Kotlin.
The second issue is of JDL, I have tried using JDK 17,21,22,24 but it did not resolve it.svyatoslav.scherbina
08/05/2025, 2:30 PMFailed to generate cinterop for composeAppcinteropObserverIosArm64: Process 'command '/Users/debz/Library/Java/JavaVirtualMachines/temurin-22.0.2/Contents/Home/bin/java'' finished with non-zero exit value 1Please show the related parts of the log before and after this error
Debayan
08/06/2025, 6:49 AMsvyatoslav.scherbina
08/06/2025, 8:41 AM