Body
We are seeing a real-device Android crash when importing files with SkipKit's withDocumentPicker.
Symptoms
After selecting a file from the system picker, the app immediately crashes on the main thread before our own import/validation logic runs.
This is reproducible for us with PDF files from Downloads on a real Android device. We did not reliably reproduce it on emulator.
Stack trace
java.lang.RuntimeException: Failure delivering result ResultInfo{who=null, request=1062580886, result=-1, data=Intent { dat=content://com.android.providers.downloads.documents/document/msf:146 flg=0x43 }} to activity {app.coutto.jade/jade.module.MainActivity}: java.lang.NullPointerException。整体的log:04-16 23:46:58.618 W/HwActivityTaskManagerServiceEx( 1387): appSwitch from: com.android.documentsui to: app.coutto.jade
04-16 23:46:58.674 E/AndroidRuntime(10066): FATAL EXCEPTION: main
04-16 23:46:58.674 E/AndroidRuntime(10066): Process: app.coutto.jade, PID: 10066
04-16 23:46:58.674 E/AndroidRuntime(10066): java.lang.RuntimeException: Failure delivering result ResultInfo{who=null, request=1062580886, result=-1, data=Intent { dat=content://com.android.providers.downloads.documents/document/msf:146 flg=0x43 }} to activity {app.coutto.jade/jade.module.MainActivity}: java.lang.NullPointerException
04-16 23:46:58.674 E/AndroidRuntime(10066): at android.app.ActivityThread.deliverResults(ActivityThread.java:6027)
04-16 23:46:58.674 E/AndroidRuntime(10066): at android.app.ActivityThread.handleSendResult(ActivityThread.java:6068)
04-16 23:46:58.674 E/AndroidRuntime(10066): at android.app.servertransaction.ActivityResultItem.execute(ActivityResultItem.java:51)
04-16 23:46:58.674 E/AndroidRuntime(10066): at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:149)
04-16 23:46:58.674 E/AndroidRuntime(10066): at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:103)
04-16 23:46:58.674 E/AndroidRuntime(10066): at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2706)
04-16 23:46:58.674 E/AndroidRuntime(10066): at android.os.Handler.dispatchMessage(Handler.java:109)
04-16 23:46:58.674 E/AndroidRuntime(10066): at android.os.Looper.loop(Looper.java:228)
04-16 23:46:58.674 E/AndroidRuntime(10066): at android.app.ActivityThread.main(ActivityThread.java:9105)
04-16 23:46:58.674 E/AndroidRuntime(10066): at java.lang.reflect.Method.invoke(Native Method)
04-16 23:46:58.674 E/AndroidRuntime(10066): at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:614)
04-16 23:46:58.674 E/AndroidRuntime(10066): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1129)
04-16 23:46:58.674 E/AndroidRuntime(10066): Caused by: java.lang.NullPointerException
04-16 23:46:58.674 E/AndroidRuntime(10066): at skip.foundation.URL._appendingPathComponent(URL.kt:322)
04-16 23:46:58.674 E/AndroidRuntime(10066): at skip.foundation.URL.appendingPathComponent(URL.kt:325)
04-16 23:46:58.674 E/AndroidRuntime(10066): at jade.ui.View_JadeDocumentPickerKt.withJadeDocumentPicker$lambda$0$0$0(View+JadeDocumentPicker.kt:63)
04-16 23:46:58.674 E/AndroidRuntime(10066): at jade.ui.View_JadeDocumentPickerKt.$r8$lambda$0XlgswPBrP8Xoc9uug2MGsF6uXQ(Unknown Source:0)
04-16 23:46:58.674 E/AndroidRuntime(10066): at jade.ui.View_JadeDocumentPickerKt$$ExternalSyntheticLambda0.invoke(D8$$SyntheticClass:0)
04-16 23:46:58.674 E/AndroidRuntime(10066): at androidx.activity.compose.ActivityResultRegistryKt.rememberLauncherForActivityResult$lambda$4$0$0(ActivityResultRegistry.kt:104)
04-16 23:46:58.674 E/AndroidRuntime(10066): at androidx.activity.compose.ActivityResultRegistryKt.$r8$lambda$3CvmGOvkwZLY6ksF4ULs20ma2UE(Unknown Source:0)
04-16 23:46:58.674 E/AndroidRuntime(10066): at androidx.activity.compose.ActivityResultRegistryKt$$ExternalSyntheticLambda2.onActivityResult(D8$$SyntheticClass:0)
04-16 23:46:58.674 E/AndroidRuntime(10066): at androidx.activity.result.ActivityResultRegistry.doDispatch(ActivityResultRegistry.kt:350)
04-16 23:46:58.674 E/AndroidRuntime(10066): at androidx.activity.result.ActivityResultRegistry.dispatchResult(ActivityResultRegistry.kt:311)
04-16 23:46:58.674 E/AndroidRuntime(10066): at androidx.activity.ComponentActivity.onActivityResult(ComponentActivity.kt:788)
04-16 23:46:58.674 E/AndroidRuntime(10066): at androidx.fragment.app.FragmentActivity.onActivityResult(FragmentActivity.java:152)
04-16 23:46:58.674 E/AndroidRuntime(10066): at android.app.Activity.dispatchActivityResult(Activity.java:8735)
04-16 23:46:58.674 E/AndroidRuntime(10066): at android.app.ActivityThread.deliverResults(ActivityThread.java:6020)
04-16 23:46:58.674 E/AndroidRuntime(10066): ... 11 more
04-16 23:46:58.679 W/ActivityTaskManager( 1387): finishTopCrashedActivityLocked Force finishing activity app.coutto.jade/jade.module.MainActivity
04-16 23:46:58.680 W/HwActivityTaskManagerServiceEx( 1387): setResumedActivityUncheckLocked start call, from: ActivityRecord{cd771ce u0 app.coutto.jade/jade.module.MainActivity t392 f}}, to: ActivityRecord{f5a29a7 u0 com.huawei.android.launcher/.unihome.UniHomeLauncher t2}
04-16 23:46:58.680 W/HwActivityTaskManagerServiceEx( 1387): appSwitch from: app.coutto.jade to: com.huawei.android.launcher
04-16 23:46:58.716 W/InputDispatcher( 1387): Attempted to unregister already unregistered input channel '8df915f app.coutto.jade/jade.module.MainActivity (server)'
04-16 23:46:59.181 W/ActivityTaskManager( 1387): Activity top resumed state loss timeout for ActivityRecord{cd771ce u0 app.coutto.jade/jade.module.MainActivity t-1 f}}
04-16 23:47:04.700 E/SWAP_AppModel( 1387): getAppNowScore, app not exist: app.coutto.jade
Likely cause
The Android implementation of withDocumentPicker copies the selected document into cache and builds the destination path with URL.appendingPathComponent(...).
That eventually reaches:
return components_0.url(relativeTo = baseURL)!!
in SkipFoundation.URL._appendingPathComponent, which can return null on Android and then crash with NPE.
So this looks like an Android path construction issue inside SkipKit / SkipFoundation, not an app-level file parsing issue.
Minimal fix suggestion
In the Android cache-copy path, avoid URL.appendingPathComponent(...) entirely and use JVM file APIs instead:
let safeFilename = selectedFilename.wrappedValue ?? "imported_file"
let outputFile = java.io.File(storageDir, safeFilename)
let destinationFileURL = URL(platformValue: outputFile.toURI())
This avoids the problematic SkipFoundation.URL path construction on Android.
Additional hardening that would help
- avoid
selectedFilename.wrappedValue!
- avoid
getColumnIndexOrThrow(...)
- fall back to
resolver.getType(uri)
- fall back to
uri.lastPathSegment when DISPLAY_NAME is missing
- handle
openInputStream(uri) returning null
Body
We are seeing a real-device Android crash when importing files with
SkipKit'swithDocumentPicker.Symptoms
After selecting a file from the system picker, the app immediately crashes on the main thread before our own import/validation logic runs.
This is reproducible for us with PDF files from
Downloadson a real Android device. We did not reliably reproduce it on emulator.Stack trace
Likely cause
The Android implementation of
withDocumentPickercopies the selected document into cache and builds the destination path withURL.appendingPathComponent(...).That eventually reaches:
in
SkipFoundation.URL._appendingPathComponent, which can returnnullon Android and then crash with NPE.So this looks like an Android path construction issue inside
SkipKit/SkipFoundation, not an app-level file parsing issue.Minimal fix suggestion
In the Android cache-copy path, avoid
URL.appendingPathComponent(...)entirely and use JVM file APIs instead:This avoids the problematic
SkipFoundation.URLpath construction on Android.Additional hardening that would help
selectedFilename.wrappedValue!getColumnIndexOrThrow(...)resolver.getType(uri)uri.lastPathSegmentwhenDISPLAY_NAMEis missingopenInputStream(uri)returningnull