react-native-track-player icon indicating copy to clipboard operation
react-native-track-player copied to clipboard

Android (v4.1.2) Kotlin 2.1.x compile error: Bundle? passed to Arguments.fromBundle

Open jmalmo opened this issue 7 months ago • 12 comments

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

  1. Create an Expo app (SDK 54; React Native 0.8').
  2. npm i [email protected]
  3. npx expo prebuild --platform android (if not already prebuilt)
  4. npx expo run:android
  5. 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 avatar Sep 25 '25 21:09 jmalmo

@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 avatar Sep 29 '25 10:09 mahamnasir1

@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:

  1. 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() }
}
  1. 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.

jmalmo avatar Sep 29 '25 20:09 jmalmo

@ReactMethod
    fun Method(callback: Promise)  {
        scope.launch{
        ...... return@launch
         .......
       }
}

this format worked for me!!

Thanks @jmalmo

mahamnasir1 avatar Sep 30 '25 08:09 mahamnasir1

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 avatar Oct 01 '25 20:10 reklis

@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 avatar Oct 01 '25 21:10 jmalmo

@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?

mahamnasir1 avatar Oct 02 '25 23:10 mahamnasir1

@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 avatar Oct 03 '25 08:10 jmalmo

@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.

KaiValar avatar Oct 03 '25 09:10 KaiValar

getting exact same issue

anmol1432 avatar Oct 24 '25 19:10 anmol1432

same issue here! any fix for this?

franvillasil avatar Oct 29 '25 11:10 franvillasil

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.

jmalmo avatar Oct 29 '25 16:10 jmalmo

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.

KaiValar avatar Dec 04 '25 15:12 KaiValar