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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions