Skip to content

Android withDocumentPicker may crash on real devices due to URL.appendingPathComponent NPE #31

@guoshao-dev

Description

@guoshao-dev

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions