Android (v4.1.2) Kotlin 2.1.x compile error: Bundle? passed to Arguments.fromBundle
Describe the Bug On Android with Kotlin 2.1.x (Expo / RN 0.81), RNTP v4.1.2 fails to compile due to a nullability mismatch: Arguments.fromBundle expects a non-null Bundle, but originalItem is Bundle? in v4.1.2.
Compiler output (examples):
e: .../node_modules/react-native-track-player/android/src/main/java/com/doublesymmetry/trackplayer/module/MusicModule.kt:548:51
Argument type mismatch: actual type is 'Bundle?', but 'Bundle' was expected.
e: .../node_modules/react-native-track-player/android/src/main/java/com/doublesymmetry/trackplayer/module/MusicModule.kt:588:17
Argument type mismatch: actual type is 'Bundle?', but 'Bundle' was expected.
A tiny change fixes it for Kotlin 2.1.x without changing behavior: coerce the nullable bundle to a non-null Bundle() before passing into Arguments.fromBundle.
Steps To Reproduce
- Create an Expo app (SDK 54; React Native 0.8').
- npm i [email protected]
- npx expo prebuild --platform android (if not already prebuilt)
- npx expo run:android
- Build fails at :react-native-track-player:compileDebugKotlin with the errors above.
Code To Reproduce This is a library compile error (no app code required). For completeness, a minimal app that imports RNTP also reproduces it:
import TrackPlayer from 'react-native-track-player';
export default function App() { return null; }
Replicable on Example App? Yes—if the example app is bumped to Kotlin 2.1.x (and corresponding AGP), the same error appears during :react-native-track-player:compileDebugKotlin.
Environment Info:
- React Native: 0.81.4 (Expo SDK 54)
- Gradle: 8.14.3
- react-native-track-player: 4.1.2
- Device: Android Emulator (Pixel_9_Pro)
- Host OS: macOS (Apple Silicon)
How I can Help
I have opened a PR at #2535.
diff --git a/android/src/main/java/com/doublesymmetry/trackplayer/module/MusicModule.kt b/android/src/main/java/com/doublesymmetry/trackplayer/module/MusicModule.kt
@@
- callback.resolve(Arguments.fromBundle(musicService.tracks[indexInt].originalItem))
+ callback.resolve(
+ Arguments.fromBundle(musicService.tracks[indexInt].originalItem ?: Bundle())
+ )
@@
- callback.resolve(
- musicService.currentTrack?.let {
- Arguments.fromBundle(it.originalItem)
- }
- )
+ callback.resolve(
+ musicService.currentTrack?.let {
+ Arguments.fromBundle(it.originalItem ?: Bundle())
+ }
+ )
(Related: on bridgeless, v4’s annotation-based module can also trigger TurboModuleInteropUtils$ParsingException when @ReactMethod sync flags don’t match signatures. If helpful, I can prepare a second v4 fix aligning those annotations. I understand main has moved to codegen and likely doesn’t need it; this would just help v4 users.)
@jmalmo i've gotten through the first part, thanks! Now i'm getting the following error: TurboModuleInteropUtils$ParsingException
can you help me figure out how to fix it?
@jmalmo i've gotten through the first part, thanks! Now i'm getting the following error: TurboModuleInteropUtils$ParsingException
can you help me figure out how to fix it?
@mahamnasir1
In v4.1.2 many native methods are written like this in Kotlin:
@ReactMethod
fun getTrack(index: Int, callback: Promise) = scope.launch {
// ...
}
Because of the = scope.launch { ... } expression, the function returns a Job (not void/Unit). Under bridgeless, the interop layer expects async ReactMethods to return void (Unit) and take a Promise. Returning a Job trips the parser and you get that ParsingException.
Proper fix (v4.1.2 patch):
Make all @ReactMethod functions not return a Job. Wrap the coroutine launch so the Kotlin function itself returns Unit:
- Add a small helper to MusicModule.kt:
// Bridgeless interop tries to pass the Job from scope.launch to JS.
// Wrap to ensure the Kotlin function returns Unit.
private fun launchInScope(block: suspend () -> Unit) {
scope.launch { block() }
}
- Change every method from the form:
@ReactMethod
fun someMethod(..., promise: Promise) = scope.launch {
// ...
}
to:
@ReactMethod
fun someMethod(..., promise: Promise) = launchInScope {
// ...
}
(or give it a normal block body and call scope.launch { ... } inside—either way the function returns Unit).
That resolves the ParsingException on bridgeless for me with Kotlin 2.1.x. I can open a second v4 backport PR with this refactor if maintainers can point me to a 4.x branch, similar to the nullability patch I proposed.
@ReactMethod
fun Method(callback: Promise) {
scope.launch{
...... return@launch
.......
}
}
this format worked for me!!
Thanks @jmalmo
diff --git a/node_modules/react-native-track-player/android/src/main/java/com/doublesymmetry/trackplayer/module/MusicModule.kt b/node_modules/react-native-track-player/android/src/main/java/com/doublesymmetry/trackplayer/module/MusicModule.kt
index b2409a0..8180cb0 100644
--- a/node_modules/react-native-track-player/android/src/main/java/com/doublesymmetry/trackplayer/module/MusicModule.kt
+++ b/node_modules/react-native-track-player/android/src/main/java/com/doublesymmetry/trackplayer/module/MusicModule.kt
@@ -545,7 +545,7 @@ class MusicModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaM
if (verifyServiceBoundOrReject(callback)) return@launch
if (index >= 0 && index < musicService.tracks.size) {
- callback.resolve(Arguments.fromBundle(musicService.tracks[index].originalItem))
+ callback.resolve(musicService.tracks[index].originalItem?.let { Arguments.fromBundle(it) })
} else {
callback.resolve(null)
}
@@ -584,9 +584,7 @@ class MusicModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaM
if (verifyServiceBoundOrReject(callback)) return@launch
callback.resolve(
if (musicService.tracks.isEmpty()) null
- else Arguments.fromBundle(
- musicService.tracks[musicService.getCurrentTrackIndex()].originalItem
- )
+ else musicService.tracks[musicService.getCurrentTrackIndex()].originalItem?.let { Arguments.fromBundle(it) }
)
}
@reklis Thanks! 👍 Your patch does the same thing I was aiming for but preserves the correct API shape (resolves null when originalItem is null) instead of forcing an empty map. That’s better for v4.1.2. I also now saw it is possible to do a PR to the v4 branch, so I did that.
@jmalmo , I am facing another issue which was previously working fine on older react native version and also react native track player version. I was able to listen to 'playback-active-track-changed' or 'playback-active-changed' events but now I am unable to on Android. On iOS, it is working as expected.
Can you help figure this out?
@jmalmo , I am facing another issue which was previously working fine on older react native version and also react native track player version. I was able to listen to 'playback-active-track-changed' or 'playback-active-changed' events but now I am unable to on Android. On iOS, it is working as expected.
Can you help figure this out?
Hard to help without more context. I suggest you open a separate issue, and I'll see what I can do :)
@jmalmo , I am facing another issue which was previously working fine on older react native version and also react native track player version. I was able to listen to 'playback-active-track-changed' or 'playback-active-changed' events but now I am unable to on Android. On iOS, it is working as expected.
Can you help figure this out?
I have a somekind of problem with Events with Expo 54 and RN 81: the event triggers, but code is executing rarely. But it stills working fine with Expo 53 and RN 0.79 with New Arch disabled and this Kotlin fix. This is enought for now as it solves the 16KB memory page of Google Play meanwhile we wait for a stable v5.
Anyways, post a issue for let know the RNTP team knows the Events aren't working as expected.
getting exact same issue
same issue here! any fix for this?
same issue here! any fix for this?
Got a fix in this PR #2535 . The maintainers have not approved it yet, but you can patch your code with it.
Use patch-package lib to apply the fixes automatically until they don't have a new stable v5 version with the fixes :) Just try to get the correct keys and save the changes. This lib saved years of my life.