Fix Android 'Already closed' crash on receipt PDF render#94894
Conversation
…crash Repoint react-native-pdf's pdfiumandroid dependency to a JitPack fork whose PdfDocument.openPage/openTextPage route through handleAlreadyClosed instead of an unconditional check(!isClosed). The IGNORE behavior patch 002 already configures now reaches the exact call that crashed on the renderer thread when a receipt render raced with PDF view teardown.
…ready-closed-render-crash
|
|
A native Android build surfaced a duplicate-class failure (:checkDuplicateClasses): AndroidPdfViewer pulls io.legere:pdfiumandroid transitively, which carries the same io.legere.pdfiumandroid.* classes as the fork under a different coordinate. Exclude the transitive copy and depend on the fork directly so a single coordinate provides those classes. Verified on a standalone NewDot Android build (BUILD SUCCESSFUL, checkDuplicateClasses passes; dependencyInsight shows the fork resolved and io.legere:pdfiumandroid absent).
|
@mountiny @eVoloshchak — quick question. It needs a small custom build of the PDF library, and I'd like to host it wherever you're comfortable. Why we need it: the crash comes from one method in the Where should it live? It's temporarily on my personal fork ( |
|
You can make patch to the library cant you |
|
@mountiny good idea, and that's actually what we already do where we can — The problem is the bug is one method inside |
|
Ok I think given all the options our fork with the fix is the best approach Can you point me to the right repo and version that we need to fork? |
|
@mountiny fork https://github.com/wildan-m/PdfiumAndroidKt (a fork of the official |
|
Tracked the Expensify org fork request in an internal Ring Zero issue: https://github.com/Expensify/Expensify/issues/655446 @wildan-m @mountiny — once Ring Zero creates |
|
@mountiny can't acess the link. is it created? |
|
Fork is here https://github.com/Expensify/PdfiumAndroidKt |
|
thread to discuss here https://expensify.slack.com/archives/C01GTK53T8Q/p1783261052206059 @eVoloshchak |
…ready-closed-render-crash
Move the PdfiumAndroidKt fork dependency off the personal com.github.wildan-m coordinate onto the Expensify org: com.github.Expensify:PdfiumAndroidKt:e6c06c2905d1adf8b76f28c7440a2006033ca4c8 Pinned by commit rather than the expensify-already-closed-fix-v1 tag because JitPack cached a transient failed build of that tag (an UnknownHostException on services.gradle.org while fetching the Gradle wrapper). The commit coordinate is a fresh JitPack version key that builds green (BUILD SUCCESSFUL, pom+aar 200) and pins the exact same immutable commit the tag points to. Can switch to the tag once an Expensify JitPack admin rebuilds it.
…ready-closed-render-crash
|
@eVoloshchak Please copy/paste the Reviewer Checklist from here into a new comment on this PR and complete it. If you have the K2 extension, you can simply click: [this button] |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7afeef740e
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| + implementation('com.github.zacharee:AndroidPdfViewer:4.0.1') { | ||
| + exclude group: 'io.legere', module: 'pdfiumandroid' | ||
| + } | ||
| + implementation 'com.github.Expensify:PdfiumAndroidKt:e6c06c2905d1adf8b76f28c7440a2006033ca4c8' |
There was a problem hiding this comment.
Use the fork's published module coordinate
For Android builds, this dependency is resolving the JitPack root aggregate for a multi-module repo, not the pdfiumandroid AAR that replaces io.legere:pdfiumandroid. The fork's jitpack.yml only publishes the :pdfiumandroid module, and JitPack's module coordinate for that is com.github.Expensify.PdfiumAndroidKt:pdfiumandroid:<sha>; with the current root coordinate Gradle either cannot resolve the artifact or may pull the aggregate instead of the class-compatible library after the original transitive dependency has been excluded.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Thanks — I checked this against JitPack and the current root coordinate is correct; the suggested submodule coordinate doesn't exist.
com.github.Expensify:PdfiumAndroidKt:e6c06c2905d1adf8b76f28c7440a2006033ca4c8 resolves the real library AAR (.aar → 200, ~9.9 MB): a classes.jar containing the 123 io.legere.pdfiumandroid.* classes, plus jni/{arm64-v8a,armeabi-v7a,x86,x86_64}/libpdfium.so and libpdfiumandroid.so. So after excluding the transitive io.legere:pdfiumandroid, this coordinate supplies the same classes (with the fix) — it's not an empty aggregate, and nothing goes missing.
The suggested com.github.Expensify.PdfiumAndroidKt:pdfiumandroid:e6c06c2905d1adf8b76f28c7440a2006033ca4c8 returns 404 for both .pom and .aar. The fork's jitpack.yml builds only :pdfiumandroid (./gradlew :pdfiumandroid:publishToMavenLocal), so JitPack maps the root coordinate to that module's artifact — which is also why the previously build-verified com.github.wildan-m:PdfiumAndroidKt fork used the same root form.
Reviewer Checklist
Screenshots/VideosAndroid: HybridAppscreen-20260713-182957-1783960147652.mp4Android: mWeb ChromeScreen.Recording.2026-07-13.at.18.53.28.moviOS: HybridAppScreen.Recording.2026-07-13.at.18.45.07.moviOS: mWeb SafariScreen.Recording.2026-07-13.at.18.50.42.movMacOS: Chrome / Safarihttps://github.com/user-attachments/assets/69474603-854a-4f68-8e50-3a06669f01e8 |
|
How is this looking @eVoloshchak @wildan-m ? |
|
@mountiny @eVoloshchak PR is ready, let me know if you have any feedback |
|
Testing this at the moment |
|
🚧 mountiny has triggered a test Expensify/App build. You can view the workflow run here. |
|
🧪🧪 Use the links below to test this adhoc build on Android, iOS, and Web. Happy testing! 🧪🧪
|
|
🚀 Deployed to staging by https://github.com/mountiny in version: 9.4.34-0 🚀
|

Explanation of Change
On Android, the app crashes (
java.lang.IllegalStateException: Already closed, SentryAPP-1VG) when a PDF is closed while it's still rendering — for example navigating away from a receipt mid-render. A previous fix (patch002) already told thepdfiumandroidlibrary to ignore this case instead of crashing, but two methods on the render path —PdfDocument.openPage()andopenTextPage()— don't respect that setting and still throw when the document is closed. That's the one path that keeps crashing.There's no published version of the library that fixes this and still works with our PDF viewer — the stable line is stuck on the buggy version, and the newer line changes the API and is only in alpha. So this patch points
react-native-pdfat a small fork of the current version where those two methods honor the ignore setting like the rest of the library — a Kotlin-only change, with the native code untouched and no change to how PDFs render. It also drops the older copy of the library that the PDF viewer pulls in alongside the fork, since otherwise the build fails with two copies of the same classes. The fork follows the same pattern as the other forksreact-native-pdfalready uses (zacharee,IvanIhnatsiuk) and can be dropped once an official fixed release is usable.Fixed Issues
$ #93839
PROPOSAL: #93839 (comment)
Tests
openPage(0)on a closedPdfDocumentreturns instead of throwing.IllegalStateException: Already closed at PdfDocument.openPage— the exactAPP-1VGsignature.📹 Recording of both runs (fail → pass):
Kapture.2026-07-12.at.19.37.19.mp4
Run it — re-runnable (clones if absent, otherwise reuses and resets the existing clone). Needs
ANDROID_HOMEset and an AVD; it boots a headless emulator if none is connected (then shuts it down), accepts SDK licenses, and repoints the fork's pinned NDK to whatever you have installed (the native part is a thin JNI wrapper over the prebuiltlibpdfium.so, so any recent NDK works). It first reverts to the pre-fix code and runs (fails withAlready closed), then restores the fix and runs again (passes):IllegalStateException: Already closed at PdfDocument.openPage.BUILD SUCCESSFUL).The definitive production signal is the
APP-1VGcrash rate in Sentry after release.Offline tests
This change does not affect offline behavior. It only changes how the native PDF library handles a render that races view teardown; an already-downloaded PDF renders identically offline.
QA Steps
PR Author Checklist
### Fixed Issuessection aboveTestssectionOffline stepssectionQA stepssectiontoggleReportand notonIconClick)Avatar, I verified the components usingAvatarare working as expected)StyleUtils.getBackgroundAndBorderStyle(theme.componentBG))npm run compress-svg)Avataris modified, I verified thatAvataris working as expected in all cases)Designlabel and/or tagged@Expensify/designso the design team can review the changes.mainbranch was merged into this PR after a review, I tested again and verified the outcome was still expected according to theTeststeps.Screenshots/Videos
Android: Native
Kapture.2026-07-13.at.08.53.20.mp4
Android: mWeb Chrome
Kapture.2026-07-13.at.08.57.59.mp4
iOS: Native
Kapture.2026-07-13.at.07.26.21.mp4
iOS: mWeb Safari
Kapture.2026-07-13.at.08.28.51.mp4
MacOS: Chrome / Safari
Kapture.2026-07-13.at.04.28.36-compressed.mp4