Skip to content
This repository was archived by the owner on Apr 26, 2026. It is now read-only.

fix(android): handle JNI exceptions during initConnection setup#3158

Merged
hyochan merged 3 commits into
mainfrom
fix/android-jni-exception-handling
Mar 4, 2026
Merged

fix(android): handle JNI exceptions during initConnection setup#3158
hyochan merged 3 commits into
mainfrom
fix/android-jni-exception-handling

Conversation

@hyochan

@hyochan hyochan commented Mar 4, 2026

Copy link
Copy Markdown
Owner

Summary

  • Wrap setActivity and listener registration in try-catch blocks to convert raw JNI exceptions into structured OpenIapException
  • Prevents cryptic Unknown N8facebook3jni12JniExceptionE error messages on the JS side
  • Developers now receive structured errors with code init-connection and descriptive messages

Problem

During initConnection, the openIap object is lazy-initialized (OpenIapModule(context)). The first access happens at either openIap.setActivity() or openIap.addPurchaseUpdateListener() — both were outside any try-catch block.

On devices without Google Play Services or with billing client issues, the constructor throws a JNI exception that propagates unhandled through Promise.async. Nitro converts the raw C++ exception class name (facebook::jni::JniException) into the unhelpful error message users see.

Meanwhile, the actual openIap.initConnection() call (which IS protected by try-catch) is never reached because the exception occurs earlier.

Changes

Android (android/.../HybridRnIap.kt)

  • Wrap setActivity call in try-catch → throws OpenIapException with message "Failed to set activity: <original error>"
  • Wrap listener registration block in try-catch → throws OpenIapException with message "Failed to register billing listeners: <original error>"
  • Reset listenersAttached = false on listener registration failure for retry on next initConnection call
  • All converted errors use init-connection error code for consistent JS-side handling

Test plan

  • yarn typecheck passes
  • yarn lint passes
  • yarn test passes (251 tests, 12 suites)
  • Pre-commit hooks pass

Closes #3144

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Strengthened in-app purchase connection error handling with clearer diagnostics and logging for more reliable failure visibility.
    • Improved billing listener registration resilience: failures now roll back listener state, cancel pending initialization, and propagate errors to concurrent callers to avoid silent failures and improve purchase flow stability.

Wrap setActivity and listener registration in try-catch to convert
raw JNI exceptions into structured OpenIapException with proper
error code and message. Previously, exceptions from OpenIapModule
lazy initialization or listener registration propagated unhandled
through Promise.async, producing cryptic "Unknown
N8facebook3jni12JniExceptionE error" messages on the JS side.

Now developers receive structured errors with code "init-connection"
and descriptive messages identifying the failure point.

Closes #3144

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly improves the robustness of the Android in-app purchase initialization process by proactively catching and handling potential JNI exceptions that could occur during the setup of the IAP module. By converting these low-level errors into a consistent, structured format, it provides a much better developer experience, making it easier to diagnose and resolve issues related to initConnection failures.

Highlights

  • Enhanced Error Handling for initConnection: Wrapped critical setActivity and listener registration calls within initConnection in try-catch blocks to prevent unhandled JNI exceptions.
  • Structured Error Reporting: Converted raw JNI exceptions into structured OpenIapException instances, providing developers with clear error codes (init-connection) and descriptive messages instead of cryptic JNI errors.
  • Improved Listener State Management: Implemented a mechanism to reset the listenersAttached flag to false if an error occurs during listener registration, allowing for retry on subsequent initConnection calls.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • android/src/main/java/com/margelo/nitro/iap/HybridRnIap.kt
    • Added a try-catch block around the setActivity call within initConnection to catch and rethrow OpenIapException with a specific error message.
    • Introduced a try-catch block around the listener registration logic to handle potential exceptions during setup, resetting listenersAttached on failure.
    • Ensured that any caught exceptions during these critical initialization steps are converted into OpenIapException with the init-connection error code.
Activity
  • No human activity has occurred on this pull request yet.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@coderabbitai

coderabbitai Bot commented Mar 4, 2026

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 97e0363c-b797-4806-b1bf-d5970be6cf95

📥 Commits

Reviewing files that changed from the base of the PR and between 6359a7a and 522fc4f.

📒 Files selected for processing (1)
  • android/src/main/java/com/margelo/nitro/iap/HybridRnIap.kt

📝 Walkthrough

Walkthrough

Guards Activity retrieval and billing-listener registration in initConnection with try/catch blocks. On failures, logs errors, resets listener state, cancels pending initialization deferreds, and throws structured OpenIapException (JSON) to propagate failures to awaiters and concurrent callers.

Changes

Cohort / File(s) Summary
Android IAP init & listeners
android/src/main/java/com/margelo/nitro/iap/HybridRnIap.kt
Wraps main-thread Activity retrieval in try/catch (rethrowing CancellationException); wraps billing listener attachment in try/catch; logs errors, resets listenersAttached, cancels/clears pending init deferreds, and throws OpenIapException with structured error JSON on failures.

Sequence Diagram(s)

sequenceDiagram
    actor RNModule as ReactNative Module
    participant Activity as Android Activity
    participant Billing as BillingClient
    participant Listeners as Billing Listeners

    RNModule->>Activity: retrieve Activity (main thread)
    alt Activity obtained
        RNModule->>Billing: init connection
        RNModule->>Billing: attach listeners
        Billing->>Listeners: register listeners
        Listeners-->>RNModule: success
    else Activity retrieval fails
        Activity-->>RNModule: Throwable
        RNModule->>RNModule: log error
        RNModule->>RNModule: throw OpenIapException (error JSON)
    end

    alt Listener registration throws
        Billing-->>RNModule: error
        RNModule->>RNModule: set listenersAttached = false
        RNModule->>RNModule: cancel/clear pending init deferreds
        RNModule->>RNModule: log error
        RNModule->>RNModule: throw OpenIapException (registration failure JSON)
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • hyochan/react-native-iap#3152: Similar changes around listener synchronization and initialization lifecycle in HybridRnIap.kt.
  • hyochan/react-native-iap#3077: Introduced OpenIapException usage and related initConnection error handling in HybridRnIap.kt.
  • hyochan/react-native-iap#3151: Adjusts listener/state cleanup and endConnection behavior in HybridRnIap.kt.

Poem

🐰 I hopped into init with nimble paws,
Wrapped Activity calls to dodge abrupt flaws,
Listeners snug, errors caught in a net,
Deferreds cleared, no panic or fret,
I nibble logs and tidy up the slots — hooray, no crashes, just dots 🥕✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change—adding exception handling for JNI errors during initConnection setup on Android.
Linked Issues check ✅ Passed The changes directly address issue #3144 by wrapping JNI exception-prone code (setActivity and listener registration) in try-catch blocks that convert raw exceptions into structured OpenIapException errors with the init-connection error code for consistent JS-side handling.
Out of Scope Changes check ✅ Passed All changes are focused on adding error handling to initConnection setup; no unrelated modifications are present.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/android-jni-exception-handling

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@hyochan hyochan added the 🛠 bugfix All kinds of bug fixes label Mar 4, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@android/src/main/java/com/margelo/nitro/iap/HybridRnIap.kt`:
- Around line 118-128: The catch blocks that currently catch Throwable (in the
initConnection.setActivity handler and the other similar catch in
HybridRnIap.kt) should rethrow Kotlin's CancellationException to preserve
coroutine cancellation semantics before converting exceptions to
OpenIapException; update the catch to check if (err is CancellationException)
throw err, then proceed to log with RnIapLog.failure and throw the
OpenIapException created via toErrorJson (keep the existing error, debugMessage,
and messageOverride logic) so cancellation is not swallowed.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b8c11d6e-676d-48e6-b0b7-c2bea4307cdc

📥 Commits

Reviewing files that changed from the base of the PR and between f14d652 and 044a708.

📒 Files selected for processing (1)
  • android/src/main/java/com/margelo/nitro/iap/HybridRnIap.kt

Comment thread android/src/main/java/com/margelo/nitro/iap/HybridRnIap.kt

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request addresses unhandled JNI exceptions during the initConnection process by wrapping critical setup calls in try-catch blocks, converting them into structured OpenIapException objects, and improving error reporting on the JavaScript side. However, a security concern was identified regarding the logging utility (RnIapLog), which has incomplete sanitization logic and could potentially leak sensitive information to the system logs. It is recommended to enhance the sanitization to include a broader range of sensitive keywords and to ensure that non-map objects are also properly handled before being logged. Additionally, minor suggestions have been made to reduce code duplication within the newly added catch blocks for better maintainability.

Comment thread android/src/main/java/com/margelo/nitro/iap/HybridRnIap.kt
Comment thread android/src/main/java/com/margelo/nitro/iap/HybridRnIap.kt Outdated
- Add dedicated CancellationException catch blocks before Throwable
  to preserve coroutine cancellation semantics in both setActivity
  and listeners try-catch blocks
- Extract `err.message ?: err.javaClass.name` into `errorMessage`
  variable to avoid duplicate expressions

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@hyochan hyochan added the 🤖 android Related to android label Mar 4, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@android/src/main/java/com/margelo/nitro/iap/HybridRnIap.kt`:
- Around line 147-221: The listener-registration try/catch can throw before
initDeferred (the Deferred created earlier) is completed/cleared, causing
concurrent initConnection callers to deadlock; update the initConnection flow so
that any thrown Throwable in the listeners block completes exceptionally and
clears/reset initDeferred and listenersAttached before rethrowing—best done by
moving listener registration into a try/finally (or ensure the catch completes
initDeferred.completeExceptionally(err) and sets initDeferred = null and
listenersAttached = false) so callers waiting on initDeferred never wait
forever; refer to symbols initDeferred, listenersAttached,
OpenIap.addPurchaseUpdateListener, addPurchaseErrorListener,
addUserChoiceBillingListener, addDeveloperProvidedBillingListener and the
surrounding initConnection logic to implement the fix.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8456ace3-94aa-4603-ba70-9bea6785a12f

📥 Commits

Reviewing files that changed from the base of the PR and between 044a708 and 6359a7a.

📒 Files selected for processing (1)
  • android/src/main/java/com/margelo/nitro/iap/HybridRnIap.kt

Comment thread android/src/main/java/com/margelo/nitro/iap/HybridRnIap.kt
If listener registration throws, complete initDeferred exceptionally
and reset it to prevent concurrent initConnection callers from
deadlocking on await().

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@hyochan hyochan merged commit f7ff308 into main Mar 4, 2026
3 checks passed
@hyochan hyochan deleted the fix/android-jni-exception-handling branch March 4, 2026 10:11
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

🤖 android Related to android 🛠 bugfix All kinds of bug fixes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

N8facebook3jni12JniExceptionE Error in initconnection

1 participant