From ff113233bc6e56347500a9501fd4c10ec1d09280 Mon Sep 17 00:00:00 2001 From: Wildan Muhlis Date: Thu, 18 Jun 2026 00:02:33 +0700 Subject: [PATCH 1/4] Make PDF renderer honor AlreadyClosedBehavior to stop Already closed 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. --- patches/react-native-pdf/details.md | 17 +++++++++++++++++ ...nt-pdfiumandroid-to-already-closed-fix.patch | 15 +++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 patches/react-native-pdf/react-native-pdf+7.0.2+004+point-pdfiumandroid-to-already-closed-fix.patch diff --git a/patches/react-native-pdf/details.md b/patches/react-native-pdf/details.md index 794d4c5a22f7..5e6dc655457c 100644 --- a/patches/react-native-pdf/details.md +++ b/patches/react-native-pdf/details.md @@ -44,3 +44,20 @@ - Upstream PR/issue: https://github.com/wonday/react-native-pdf/issues/1009, https://github.com/wonday/react-native-pdf/pull/1011 - E/App issue: https://github.com/Expensify/App/issues/81225 - PR introducing patch: https://github.com/Expensify/App/pull/87416 + + +### [react-native-pdf+7.0.2+004+point-pdfiumandroid-to-already-closed-fix.patch](react-native-pdf+7.0.2+004+point-pdfiumandroid-to-already-closed-fix.patch) + +- Reason: + + ``` + This patch completes the "Already closed" crash fix that patch 002 started. + + Patch 002 sets AlreadyClosedBehavior.IGNORE so a render that races with PDF view teardown is ignored instead of throwing. That config is only honored by pdfiumandroid methods that route their closed-state check through handleAlreadyClosed(). In io.legere:pdfiumandroid 1.0.35, PdfDocument.openPage()/openTextPage() are the only document methods that do NOT use that helper — they guard with an unconditional check(!isClosed) { "Already closed" } that always throws, regardless of the configured behavior. PdfiumCore.renderPageBitmap() calls openPage(), and the barteksc RenderingHandler only catches PageRenderingException, so the IllegalStateException is uncaught on the renderer thread and crashes the app. + + Since io.legere:pdfiumandroid is a published artifact (not vendored into node_modules), it cannot be patched with patch-package directly. Instead this patch repoints react-native-pdf's Android build.gradle dependency to a JitPack fork that makes openPage()/openTextPage() honor handleAlreadyClosed() like every other PdfDocument method. When IGNORE is set they return a page bound to the closed document, whose operations all no-op via the existing doc.isClosed guards, so the closed-document render is silently dropped instead of crashing. The fork only changes Kotlin (the prebuilt native libs are unchanged) and should be raised upstream so the pin can eventually be dropped. + ``` + +- Fork carrying the fix: https://github.com/wildan-m/PdfiumAndroidKt/commit/29195ff5eca4689457a44b3b69ef8f1e86c7a7e6 (tag `1.0.35-already-closed-fix.3`) +- Upstream library: https://github.com/johngray1965/PdfiumAndroidKt +- E/App issue: https://github.com/Expensify/App/issues/93839 diff --git a/patches/react-native-pdf/react-native-pdf+7.0.2+004+point-pdfiumandroid-to-already-closed-fix.patch b/patches/react-native-pdf/react-native-pdf+7.0.2+004+point-pdfiumandroid-to-already-closed-fix.patch new file mode 100644 index 000000000000..bca641516e52 --- /dev/null +++ b/patches/react-native-pdf/react-native-pdf+7.0.2+004+point-pdfiumandroid-to-already-closed-fix.patch @@ -0,0 +1,15 @@ +diff --git a/node_modules/react-native-pdf/android/build.gradle b/node_modules/react-native-pdf/android/build.gradle +index 7a70e6e0bf6..bf045897522 100644 +--- a/node_modules/react-native-pdf/android/build.gradle ++++ b/node_modules/react-native-pdf/android/build.gradle +@@ -143,6 +143,9 @@ dependencies { + // The repo from zacharee is based on PdfiumAndroidKt, a much newer fork of PdfiumAndroid, with better maintenance and updated native libraries. + implementation 'com.github.zacharee:AndroidPdfViewer:4.0.1' + // Depend on PdfiumAndroidKt directly so this can be updated independently of AndroidPdfViewer as updates are provided. +- implementation 'io.legere:pdfiumandroid:1.0.35' ++ // Pinned to a JitPack fork of io.legere:pdfiumandroid 1.0.35 that makes PdfDocument.openPage/openTextPage honor ++ // AlreadyClosedBehavior.IGNORE (the same config patch 002 already sets) instead of throwing IllegalStateException on ++ // the renderer thread when a render races with document teardown. See patch 004 / details.md (E/App #93839). ++ implementation 'com.github.wildan-m:PdfiumAndroidKt:1.0.35-already-closed-fix.3' + implementation 'com.google.code.gson:gson:2.13.2' + } From 502ec092928fc94ea3d55d96d238ed31cdef3532 Mon Sep 17 00:00:00 2001 From: Wildan Muhlis Date: Tue, 30 Jun 2026 03:33:31 +0700 Subject: [PATCH 2/4] Exclude transitive io.legere:pdfiumandroid so the fork dedupes 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). --- patches/react-native-pdf/details.md | 2 +- ...-pdfiumandroid-to-already-closed-fix.patch | 21 ++++++++++++------- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/patches/react-native-pdf/details.md b/patches/react-native-pdf/details.md index 5e6dc655457c..c87b0c639037 100644 --- a/patches/react-native-pdf/details.md +++ b/patches/react-native-pdf/details.md @@ -55,7 +55,7 @@ Patch 002 sets AlreadyClosedBehavior.IGNORE so a render that races with PDF view teardown is ignored instead of throwing. That config is only honored by pdfiumandroid methods that route their closed-state check through handleAlreadyClosed(). In io.legere:pdfiumandroid 1.0.35, PdfDocument.openPage()/openTextPage() are the only document methods that do NOT use that helper — they guard with an unconditional check(!isClosed) { "Already closed" } that always throws, regardless of the configured behavior. PdfiumCore.renderPageBitmap() calls openPage(), and the barteksc RenderingHandler only catches PageRenderingException, so the IllegalStateException is uncaught on the renderer thread and crashes the app. - Since io.legere:pdfiumandroid is a published artifact (not vendored into node_modules), it cannot be patched with patch-package directly. Instead this patch repoints react-native-pdf's Android build.gradle dependency to a JitPack fork that makes openPage()/openTextPage() honor handleAlreadyClosed() like every other PdfDocument method. When IGNORE is set they return a page bound to the closed document, whose operations all no-op via the existing doc.isClosed guards, so the closed-document render is silently dropped instead of crashing. The fork only changes Kotlin (the prebuilt native libs are unchanged) and should be raised upstream so the pin can eventually be dropped. + Since io.legere:pdfiumandroid is a published artifact (not vendored into node_modules), it cannot be patched with patch-package directly. Instead this patch makes react-native-pdf's Android build.gradle depend on a JitPack fork that makes openPage()/openTextPage() honor handleAlreadyClosed() like every other PdfDocument method, and excludes the io.legere:pdfiumandroid that com.github.zacharee:AndroidPdfViewer pulls in transitively. The exclude is required: the fork is published under a different Maven coordinate (com.github.wildan-m:PdfiumAndroidKt) but ships the same io.legere.pdfiumandroid.* classes, so without it both the original (via AndroidPdfViewer) and the fork land on the classpath and the build fails :checkDuplicateClasses. AndroidPdfViewer links against the fork's identical classes unchanged. With IGNORE set, openPage()/openTextPage() return a page bound to the closed document whose operations all no-op via the existing doc.isClosed guards, so the closed-document render is silently dropped instead of crashing. The fork only changes Kotlin (the prebuilt native libs are unchanged) and should be raised upstream so the pin can eventually be dropped. ``` - Fork carrying the fix: https://github.com/wildan-m/PdfiumAndroidKt/commit/29195ff5eca4689457a44b3b69ef8f1e86c7a7e6 (tag `1.0.35-already-closed-fix.3`) diff --git a/patches/react-native-pdf/react-native-pdf+7.0.2+004+point-pdfiumandroid-to-already-closed-fix.patch b/patches/react-native-pdf/react-native-pdf+7.0.2+004+point-pdfiumandroid-to-already-closed-fix.patch index bca641516e52..1f2812deae62 100644 --- a/patches/react-native-pdf/react-native-pdf+7.0.2+004+point-pdfiumandroid-to-already-closed-fix.patch +++ b/patches/react-native-pdf/react-native-pdf+7.0.2+004+point-pdfiumandroid-to-already-closed-fix.patch @@ -1,15 +1,22 @@ diff --git a/node_modules/react-native-pdf/android/build.gradle b/node_modules/react-native-pdf/android/build.gradle -index 7a70e6e0bf6..bf045897522 100644 +index 7a70e6e..0e94f9c 100644 --- a/node_modules/react-native-pdf/android/build.gradle +++ b/node_modules/react-native-pdf/android/build.gradle -@@ -143,6 +143,9 @@ dependencies { +@@ -141,8 +141,14 @@ dependencies { + } + // NOTE: The original repo at com.github.barteksc is abandoned by the maintainer; there will be no more updates coming from that repo. // The repo from zacharee is based on PdfiumAndroidKt, a much newer fork of PdfiumAndroid, with better maintenance and updated native libraries. - implementation 'com.github.zacharee:AndroidPdfViewer:4.0.1' - // Depend on PdfiumAndroidKt directly so this can be updated independently of AndroidPdfViewer as updates are provided. +- implementation 'com.github.zacharee:AndroidPdfViewer:4.0.1' +- // Depend on PdfiumAndroidKt directly so this can be updated independently of AndroidPdfViewer as updates are provided. - implementation 'io.legere:pdfiumandroid:1.0.35' -+ // Pinned to a JitPack fork of io.legere:pdfiumandroid 1.0.35 that makes PdfDocument.openPage/openTextPage honor -+ // AlreadyClosedBehavior.IGNORE (the same config patch 002 already sets) instead of throwing IllegalStateException on -+ // the renderer thread when a render races with document teardown. See patch 004 / details.md (E/App #93839). ++ // E/App #93839: AndroidPdfViewer pulls io.legere:pdfiumandroid 1.0.35 transitively, whose ++ // PdfDocument.openPage/openTextPage throw "Already closed" instead of honoring AlreadyClosedBehavior.IGNORE ++ // (set by patch 002), crashing the renderer thread when a render races PDF view teardown. Depend on a ++ // JitPack fork that fixes those two methods and exclude the original, so the two coordinates don't ship the ++ // same io.legere.pdfiumandroid.* classes twice. See details.md / patch 004. ++ implementation('com.github.zacharee:AndroidPdfViewer:4.0.1') { ++ exclude group: 'io.legere', module: 'pdfiumandroid' ++ } + implementation 'com.github.wildan-m:PdfiumAndroidKt:1.0.35-already-closed-fix.3' implementation 'com.google.code.gson:gson:2.13.2' } From b4e5469588bf06b278d1b40d56f0bd6ec48feff3 Mon Sep 17 00:00:00 2001 From: Wildan Muhlis Date: Sat, 11 Jul 2026 04:42:57 +0700 Subject: [PATCH 3/4] Repoint patch 004 to Expensify-org PdfiumAndroidKt 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. --- patches/react-native-pdf/details.md | 4 ++-- ...+7.0.2+004+point-pdfiumandroid-to-already-closed-fix.patch | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/patches/react-native-pdf/details.md b/patches/react-native-pdf/details.md index c87b0c639037..53279ae9d0b3 100644 --- a/patches/react-native-pdf/details.md +++ b/patches/react-native-pdf/details.md @@ -55,9 +55,9 @@ Patch 002 sets AlreadyClosedBehavior.IGNORE so a render that races with PDF view teardown is ignored instead of throwing. That config is only honored by pdfiumandroid methods that route their closed-state check through handleAlreadyClosed(). In io.legere:pdfiumandroid 1.0.35, PdfDocument.openPage()/openTextPage() are the only document methods that do NOT use that helper — they guard with an unconditional check(!isClosed) { "Already closed" } that always throws, regardless of the configured behavior. PdfiumCore.renderPageBitmap() calls openPage(), and the barteksc RenderingHandler only catches PageRenderingException, so the IllegalStateException is uncaught on the renderer thread and crashes the app. - Since io.legere:pdfiumandroid is a published artifact (not vendored into node_modules), it cannot be patched with patch-package directly. Instead this patch makes react-native-pdf's Android build.gradle depend on a JitPack fork that makes openPage()/openTextPage() honor handleAlreadyClosed() like every other PdfDocument method, and excludes the io.legere:pdfiumandroid that com.github.zacharee:AndroidPdfViewer pulls in transitively. The exclude is required: the fork is published under a different Maven coordinate (com.github.wildan-m:PdfiumAndroidKt) but ships the same io.legere.pdfiumandroid.* classes, so without it both the original (via AndroidPdfViewer) and the fork land on the classpath and the build fails :checkDuplicateClasses. AndroidPdfViewer links against the fork's identical classes unchanged. With IGNORE set, openPage()/openTextPage() return a page bound to the closed document whose operations all no-op via the existing doc.isClosed guards, so the closed-document render is silently dropped instead of crashing. The fork only changes Kotlin (the prebuilt native libs are unchanged) and should be raised upstream so the pin can eventually be dropped. + Since io.legere:pdfiumandroid is a published artifact (not vendored into node_modules), it cannot be patched with patch-package directly. Instead this patch makes react-native-pdf's Android build.gradle depend on a JitPack fork that makes openPage()/openTextPage() honor handleAlreadyClosed() like every other PdfDocument method, and excludes the io.legere:pdfiumandroid that com.github.zacharee:AndroidPdfViewer pulls in transitively. The exclude is required: the fork is published under a different Maven coordinate (com.github.Expensify:PdfiumAndroidKt) but ships the same io.legere.pdfiumandroid.* classes, so without it both the original (via AndroidPdfViewer) and the fork land on the classpath and the build fails :checkDuplicateClasses. AndroidPdfViewer links against the fork's identical classes unchanged. With IGNORE set, openPage()/openTextPage() return a page bound to the closed document whose operations all no-op via the existing doc.isClosed guards, so the closed-document render is silently dropped instead of crashing. The fork only changes Kotlin (the prebuilt native libs are unchanged) and should be raised upstream so the pin can eventually be dropped. ``` -- Fork carrying the fix: https://github.com/wildan-m/PdfiumAndroidKt/commit/29195ff5eca4689457a44b3b69ef8f1e86c7a7e6 (tag `1.0.35-already-closed-fix.3`) +- Fork carrying the fix: https://github.com/Expensify/PdfiumAndroidKt/commit/e6c06c2905d1adf8b76f28c7440a2006033ca4c8 (tag `expensify-already-closed-fix-v1`) - Upstream library: https://github.com/johngray1965/PdfiumAndroidKt - E/App issue: https://github.com/Expensify/App/issues/93839 diff --git a/patches/react-native-pdf/react-native-pdf+7.0.2+004+point-pdfiumandroid-to-already-closed-fix.patch b/patches/react-native-pdf/react-native-pdf+7.0.2+004+point-pdfiumandroid-to-already-closed-fix.patch index 1f2812deae62..8f9d6ef43849 100644 --- a/patches/react-native-pdf/react-native-pdf+7.0.2+004+point-pdfiumandroid-to-already-closed-fix.patch +++ b/patches/react-native-pdf/react-native-pdf+7.0.2+004+point-pdfiumandroid-to-already-closed-fix.patch @@ -17,6 +17,6 @@ index 7a70e6e..0e94f9c 100644 + implementation('com.github.zacharee:AndroidPdfViewer:4.0.1') { + exclude group: 'io.legere', module: 'pdfiumandroid' + } -+ implementation 'com.github.wildan-m:PdfiumAndroidKt:1.0.35-already-closed-fix.3' ++ implementation 'com.github.Expensify:PdfiumAndroidKt:e6c06c2905d1adf8b76f28c7440a2006033ca4c8' implementation 'com.google.code.gson:gson:2.13.2' } From 2aeae32f1a45f432fa3d0709e3acf90ef9b61186 Mon Sep 17 00:00:00 2001 From: Wildan Muhlis Date: Sat, 11 Jul 2026 13:16:23 +0700 Subject: [PATCH 4/4] docs(patch 004): add PR-introducing-patch link to match house style --- patches/react-native-pdf/details.md | 1 + 1 file changed, 1 insertion(+) diff --git a/patches/react-native-pdf/details.md b/patches/react-native-pdf/details.md index 53279ae9d0b3..ea280c9994b3 100644 --- a/patches/react-native-pdf/details.md +++ b/patches/react-native-pdf/details.md @@ -61,3 +61,4 @@ - Fork carrying the fix: https://github.com/Expensify/PdfiumAndroidKt/commit/e6c06c2905d1adf8b76f28c7440a2006033ca4c8 (tag `expensify-already-closed-fix-v1`) - Upstream library: https://github.com/johngray1965/PdfiumAndroidKt - E/App issue: https://github.com/Expensify/App/issues/93839 +- PR introducing patch: https://github.com/Expensify/App/pull/94894