Skip to content

fix(sdk): preserve fetch products all results#193

Merged
hyochan merged 8 commits into
mainfrom
fix/expo-fetch-products-all
Jun 25, 2026
Merged

fix(sdk): preserve fetch products all results#193
hyochan merged 8 commits into
mainfrom
fix/expo-fetch-products-all

Conversation

@hyochan

@hyochan hyochan commented Jun 25, 2026

Copy link
Copy Markdown
Member

Summary

  • Fix fetchProducts({ type: 'all' }) empty-result regression by preserving the FetchProductsResultAll mixed product/subscription union at the Expo Android bridge boundary.
  • Keep the same mixed-result contract in Flutter Android, KMP Android/iOS, and Google Play/Horizon so SDK parity does not collapse subscriptions into plain products.
  • Fix Flutter's generated queryHandlers.fetchProducts compatibility layer so ProductQueryType.All returns FetchProductsResultAll instead of dropping subscriptions through the product-only branch.
  • Align omitted product-query type defaults to InApp while preserving explicit All behavior.
  • Address review feedback in the Expo Onside iOS bridge: main-actor queue mutation, stable transaction-date serialization, alsoPublish event publishing, and decimal-safe price serialization.
  • Update docs for fetchProducts(type: 'all'), correct Flutter enum/generic examples, and add post-release notes for the expected patch releases.
  • Audited React Native, Godot, and MAUI paths; they already preserve the mixed all result and did not need code changes.

Closes #192

Scope note

Although #192 reproduces through the Expo Android example, the bug is a shared SDK contract issue around FetchProductsResultAll. This PR keeps the fix in one branch because each touched adapter is part of the same fetch-products parity surface; splitting only the Expo Android line would leave the same contract mismatch in sibling libraries.

Release note scope

Assuming this PR is released after merge, the docs release note records the expected package patch releases as:

  • openiap-google 2.2.3
  • expo-iap 4.3.4
  • flutter_inapp_purchase 9.3.5
  • kmp-iap 2.3.3

The OpenIAP Spec remains 2.0.2. Apple, React Native, Godot, and MAUI are documented as audited/unchanged for this regression.

Test plan

  • cd libraries/expo-iap && bun run lint:tsc
  • cd libraries/expo-iap && bun run lint
  • cd libraries/expo-iap && bun run test
  • cd libraries/expo-iap/example && bun run test
  • cd libraries/expo-iap/example/android && ANDROID_HOME="$HOME/Library/Android/sdk" ANDROID_SDK_ROOT="$HOME/Library/Android/sdk" ./gradlew :expo-iap:compileDebugKotlin
  • cd packages/google && ANDROID_HOME="$HOME/Library/Android/sdk" ANDROID_SDK_ROOT="$HOME/Library/Android/sdk" ./gradlew :openiap:compilePlayDebugKotlin
  • cd packages/google && ANDROID_HOME="$HOME/Library/Android/sdk" ANDROID_SDK_ROOT="$HOME/Library/Android/sdk" ./gradlew :openiap:compileHorizonDebugKotlin
  • cd libraries/kmp-iap && ANDROID_HOME="$HOME/Library/Android/sdk" ANDROID_SDK_ROOT="$HOME/Library/Android/sdk" ./gradlew :library:compileDebugKotlinAndroid
  • cd libraries/kmp-iap && ANDROID_HOME="$HOME/Library/Android/sdk" ANDROID_SDK_ROOT="$HOME/Library/Android/sdk" ./gradlew :library:testDebugUnitTest
  • cd libraries/kmp-iap && ANDROID_HOME="$HOME/Library/Android/sdk" ANDROID_SDK_ROOT="$HOME/Library/Android/sdk" ./gradlew :library:compileKotlinIosSimulatorArm64
  • xcodebuild -workspace libraries/expo-iap/example/ios/expoiapexample.xcworkspace -scheme expoiapexample -configuration Debug -destination 'id=0ED50699-1016-4DFE-8DAF-6BB29AD7FAF1' build
  • Android device E2E on Pixel 2 (HT79F1A00473): product listing, function calls, and real Google Play purchase path verified. Amazon device was intentionally skipped because it is outside this PR.
  • iOS Expo example E2E: product loading and real purchase sheet cancel path verified when the system auth prompt appears.
  • bun run audit:parity
  • cd libraries/flutter_inapp_purchase && PATH="$HOME/.cache/codex/flutter-3.41.9/bin:$PATH" flutter pub get && flutter analyze && flutter test
  • cd libraries/flutter_inapp_purchase/example && PATH="$HOME/.cache/codex/flutter-3.41.9/bin:$PATH" flutter pub get && ANDROID_HOME="$HOME/Library/Android/sdk" ANDROID_SDK_ROOT="$HOME/Library/Android/sdk" flutter build apk --debug
  • Confirmed cd libraries/flutter_inapp_purchase/android && ANDROID_HOME="$HOME/Library/Android/sdk" ANDROID_SDK_ROOT="$HOME/Library/Android/sdk" ./gradlew compileDebugKotlin --no-daemon --stacktrace is not a representative standalone check without a Flutter host project; it fails before this patch on missing io.flutter.* embedding classes. Android compile coverage is provided by the Flutter example host build above.
  • cd packages/docs && PATH="$HOME/.bun/bin:$PATH" bun run lint
  • PATH="$HOME/.bun/bin:$PATH" bun audit:docs
  • git diff --check

Preview recording: not applicable; this is a native bridge/result mapping and documentation/release-note fix with no visual UI surface.

Summary by CodeRabbit

  • New Features
    • "All" product queries now return a combined list containing both in-app products and subscriptions.
  • Bug Fixes
    • Fixed product fetching so "All" results are converted and delivered consistently across platforms.
    • Improved type-aware handling for empty "All" results (and removed inconsistent fallbacks).
    • Updated behavior when no product type is specified to return in-app products only.
    • Refined Onside transaction handling and improved transaction/product data serialization (date, currency, storefront).

@coderabbitai

coderabbitai Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

FetchProductsResultAll is now handled across Android, iOS, Expo, and Flutter fetch paths, and the Onside iOS module updates queue access plus product and transaction serialization.

Changes

Mixed all-query result handling

Layer / File(s) Summary
Android result construction
packages/google/openiap/src/horizon/java/dev/hyo/openiap/OpenIapModule.kt, libraries/kmp-iap/library/src/androidMain/kotlin/io/github/hyochan/kmpiap/InAppPurchaseAndroid.kt, packages/google/openiap/src/play/java/dev/hyo/openiap/OpenIapModule.kt
ProductQueryType.All now builds mixed ProductOrSubscription lists and returns FetchProductsResultAll, and omitted type now defaults to ProductQueryType.InApp.
iOS all-query conversion
libraries/kmp-iap/library/src/iosMain/kotlin/io/github/hyochan/kmpiap/InAppPurchaseIOS.kt
fetchProducts now returns FetchProductsResultAll for ProductQueryType.All, uses type-specific empty results, and adds convertAnyListToProductOrSubscriptions.
Expo and Flutter result mapping
libraries/expo-iap/android/src/main/java/expo/modules/iap/ExpoIapModule.kt, libraries/flutter_inapp_purchase/android/src/main/kotlin/io/github/hyochan/flutter_inapp_purchase/AndroidInappPurchasePlugin.kt
Both adapters add FetchProductsResultAll branches and convert the mixed result items to JSON.

Onside iOS queue and serialization updates

Layer / File(s) Summary
Main-actor queue access
libraries/expo-iap/ios/onside/OnsideIapModule.swift
fetchProducts, finishTransaction, getAvailableItems, getOnsideStorefront, and OnsideProductFetcher.cleanup() now use MainActor.run or @MainActor around queue and storefront access.
Product and transaction serialization
libraries/expo-iap/ios/onside/OnsideIapModule.swift
Product and transaction payloads now use direct currency codes, value-based decimal numbers, and a cached transaction date for serialization fields.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • hyodotdev/openiap#32: Adds FetchProductsResultAll handling and mixed ProductOrSubscription result support that these fetch-product changes now consume.
  • hyodotdev/openiap#33: Introduces the ProductOrSubscription and FetchProductsResultAll type modeling used by the Android and iOS fetch-product updates.

Poem

🐰 I hopped through all with a mixed-up grin,
subscriptions and products all tucked in.
JSON sparkled, swift and bright,
while Onside kept its queues just right.
Hop-hop—two paths now land on time!

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning Several Flutter, KMP, Google, and iOS Onside changes go beyond #192's Android Expo bridge fix. Split the non-#192 parity and iOS cleanup changes into separate PRs or link the corresponding issues.
Docstring Coverage ⚠️ Warning Docstring coverage is 12.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately reflects the main change: preserving fetchProducts 'all' results.
Linked Issues check ✅ Passed The Android Expo bridge now handles FetchProductsResultAll, which addresses the empty-array bug in #192.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/expo-fetch-products-all

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.

@hyochan hyochan added cross-platform Cross-platform (both Android & iOS) expo-iap expo-iap library flutter-iap kmp-iap kmp-iap library 🛠 bugfix All kinds of bug fixes 🤖 android Related to android labels Jun 25, 2026

@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 introduces support for fetching all products and subscriptions combined via a new FetchProductsResultAll result type and ProductOrSubscription model. It updates the Android and iOS implementations across expo-iap, flutter_inapp_purchase, kmp-iap, and openiap packages to handle ProductQueryType.All by mapping products and subscriptions into their respective ProductOrSubscription wrappers. There are no review comments provided, so I have no feedback to address.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@libraries/kmp-iap/library/src/iosMain/kotlin/io/github/hyochan/kmpiap/InAppPurchaseIOS.kt`:
- Around line 1271-1295: The mixed decode path in
convertAnyListToProductOrSubscriptions is too broad because the outer catch
returns emptyList() and drops all valid items when a single element fails.
Tighten the error handling so one bad bridge payload only skips that item while
preserving the rest of the list, and keep the fallback flow intact
(ProductOrSubscription.fromJson first, then type-based recovery via
convertAnyListToProductSubscriptions or convertAnyListToProducts). Add a brief
comment in this function explaining the decode order and the platform-specific
fallback behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: baf07d7f-c453-49a7-b761-2eb664416562

📥 Commits

Reviewing files that changed from the base of the PR and between 80b00d4 and 5635420.

📒 Files selected for processing (5)
  • libraries/expo-iap/android/src/main/java/expo/modules/iap/ExpoIapModule.kt
  • libraries/flutter_inapp_purchase/android/src/main/kotlin/io/github/hyochan/flutter_inapp_purchase/AndroidInappPurchasePlugin.kt
  • libraries/kmp-iap/library/src/androidMain/kotlin/io/github/hyochan/kmpiap/InAppPurchaseAndroid.kt
  • libraries/kmp-iap/library/src/iosMain/kotlin/io/github/hyochan/kmpiap/InAppPurchaseIOS.kt
  • packages/google/openiap/src/horizon/java/dev/hyo/openiap/OpenIapModule.kt

@hyochan

hyochan commented Jun 25, 2026

Copy link
Copy Markdown
Member Author

Device-backed verification run on 2026-06-25 KST for #193.

No dedicated .claude/commands/e2e-tests.md or Detox/Maestro-style E2E runner exists on this branch, so I ran the available device-backed Gradle/Xcode/example smoke paths.

Passed:

  • Google native Android on Pixel 2 / Android 11:
    • :openiap:testPlayDebugUnitTest :openiap:testHorizonDebugUnitTest
    • :Example:installPlayDebug, :Example:connectedPlayDebugAndroidTest, launch dev.hyo.martie/.MainActivity
    • :Example:installHorizonDebug, :Example:connectedHorizonDebugAndroidTest, launch dev.hyo.martie/.MainActivity
  • Expo Android:
    • :expo-iap:connectedDebugAndroidTest
    • :app:installDebug, :app:connectedDebugAndroidTest, launch dev.hyo.martie/.MainActivity
    • Note: app install required a temporary local-only switch of the example generated Gradle fallback from platform=amazon to platform=play, then I reverted it. The clean tree has no leftover change.
  • KMP:
    • :library:testDebugUnitTest :example:composeApp:testDebugUnitTest :library:iosSimulatorArm64Test :example:composeApp:iosSimulatorArm64Test
    • :library:connectedDebugAndroidTest :example:composeApp:connectedDebugAndroidTest
    • :example:composeApp:installDebug, Android launch dev.hyo.martie/.MainActivity
    • iOS simulator framework link, Xcode simulator build, install, and launch on iPhone 17 / iOS 26.5 (dev.hyo.martie)
  • React Native, not changed by this PR but checked because the same error pattern was audited:
    • yarn test:all
    • :app:installDebug, :app:connectedDebugAndroidTest :react-native-iap:connectedDebugAndroidTest, Android launch dev.hyo.martie/.MainActivity

Blocked / not runnable in this local environment:

  • Flutter: flutter and dart are not installed, so Flutter unit/widget/E2E could not run. The standalone Android Gradle path is not representative because it needs Flutter embedding from a Flutter host project.
  • Godot: godot CLI is not installed, so headless GDScript tests under libraries/godot-iap/Example/tests could not run.
  • MAUI: dotnet CLI is not installed, so MAUI build/test paths could not run.
  • Live store E2E for fetchProducts(type: all) was not run because it requires configured App Store / Play sandbox tester accounts and live product availability on a physical device or proper store test setup.

@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 introduces support for querying all product types simultaneously (ProductQueryType.All) by adding the FetchProductsResultAll result type and mapping it across Expo, Flutter, KMP (Android/iOS), and Google OpenIAP modules. Feedback on the changes includes a suggestion to use case-insensitive comparison when parsing product types from raw native payloads on iOS, and a recommendation to resolve a platform behavior discrepancy where Android and iOS default to different query types when params.type is null.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

@coderabbitai coderabbitai 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.

Actionable comments posted: 3

🧹 Nitpick comments (1)
libraries/kmp-iap/library/src/androidMain/kotlin/io/github/hyochan/kmpiap/InAppPurchaseAndroid.kt (1)

840-850: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Document the mixed All union wrapping.

This branch is the regression-sensitive contract: all must preserve distinct product vs subscription variants. A short comment makes that intent harder to accidentally collapse later. As per coding guidelines, “Comment complex logic, especially platform-specific code.”

Proposed comment
                 ProductQueryType.Subs -> FetchProductsResultSubscriptions(subsDetails.mapNotNull { it.toSubscriptionProduct() })
                 ProductQueryType.All -> {
+                    // Preserve the mixed OpenIAP `all` union by wrapping in-app
+                    // products and subscriptions in their distinct variants.
                     val combined = buildList<ProductOrSubscription> {
                         addAll(inAppDetails.map { ProductOrSubscription.ProductItem(it.toProduct()) })
                         addAll(
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@libraries/kmp-iap/library/src/androidMain/kotlin/io/github/hyochan/kmpiap/InAppPurchaseAndroid.kt`
around lines 840 - 850, Add a short inline comment around the combined list
creation in InAppPurchaseAndroid’s product fetch flow to document that
FetchProductsResultAll must preserve the distinct ProductItem and
ProductSubscriptionItem variants. Keep the note near the buildList/combine step
so future changes in this branch do not flatten the mixed union contract.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@libraries/expo-iap/ios/onside/OnsideIapModule.swift`:
- Around line 453-454: The transaction payload is using the current time instead
of the purchase’s actual timestamp, so the serialized transactionDate changes on
every emission. Update OnsideIapModule’s transaction serialization logic to use
the transaction’s original date field when building the dictionary, and only
default to Date() if that source date is unavailable. Keep the change localized
to the code path that populates transactionDate so getAvailableItems and any
repeated returns preserve the same value.
- Line 249: The transaction cleanup in OnsideIapModule’s purchase flow is still
mutating the payment queue off the main actor. Update the same async path that
reads Onside.defaultPaymentQueue().transactions and ensure the finishTransaction
call is also executed inside MainActor.run, using the existing
Onside.defaultPaymentQueue() and finishTransaction symbols so the read and
mutation stay consistently main-actor isolated.

In
`@libraries/kmp-iap/library/src/androidMain/kotlin/io/github/hyochan/kmpiap/InAppPurchaseAndroid.kt`:
- Line 780: `OpenIapModule` is defaulting `params.type` to
`ProductQueryType.All`, which conflicts with the KMP Android path in
`InAppPurchaseAndroid` and the documented `OpenIapStore` behavior; update the
default in both `OpenIapModule` variants (play and horizon) so the `queryType`
fallback uses `ProductQueryType.InApp` when `params.type` is omitted, keeping
product queries consistent across the Android implementations.

---

Nitpick comments:
In
`@libraries/kmp-iap/library/src/androidMain/kotlin/io/github/hyochan/kmpiap/InAppPurchaseAndroid.kt`:
- Around line 840-850: Add a short inline comment around the combined list
creation in InAppPurchaseAndroid’s product fetch flow to document that
FetchProductsResultAll must preserve the distinct ProductItem and
ProductSubscriptionItem variants. Keep the note near the buildList/combine step
so future changes in this branch do not flatten the mixed union contract.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f37914cd-cbab-4231-ba80-f5317175d3b9

📥 Commits

Reviewing files that changed from the base of the PR and between 9f3fee3 and 532591e.

📒 Files selected for processing (3)
  • libraries/expo-iap/ios/onside/OnsideIapModule.swift
  • libraries/kmp-iap/library/src/androidMain/kotlin/io/github/hyochan/kmpiap/InAppPurchaseAndroid.kt
  • libraries/kmp-iap/library/src/iosMain/kotlin/io/github/hyochan/kmpiap/InAppPurchaseIOS.kt
🚧 Files skipped from review as they are similar to previous changes (1)
  • libraries/kmp-iap/library/src/iosMain/kotlin/io/github/hyochan/kmpiap/InAppPurchaseIOS.kt

Comment thread libraries/expo-iap/ios/onside/OnsideIapModule.swift Outdated
Comment thread libraries/expo-iap/ios/onside/OnsideIapModule.swift Outdated
@hyodotdev hyodotdev deleted a comment from coderabbitai Bot Jun 25, 2026
@hyodotdev hyodotdev deleted a comment from coderabbitai Bot Jun 25, 2026

@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 introduces support for the FetchProductsResultAll result type (representing a union of products and subscriptions) across several platform modules, and changes the default product query type from All to InApp when unspecified. Additionally, the iOS OnsideIapModule is updated to run store-related calls on the MainActor and caches transaction dates to maintain stability. The review feedback suggests avoiding potential floating-point precision issues in OnsideIapModule.swift by initializing NSDecimalNumber with a Decimal instead of a raw floating-point value.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread libraries/expo-iap/ios/onside/OnsideIapModule.swift Outdated
Comment thread libraries/expo-iap/ios/onside/OnsideIapModule.swift Outdated

@coderabbitai coderabbitai 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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
libraries/expo-iap/ios/onside/OnsideIapModule.swift (1)

303-314: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Honor alsoPublish before returning available items.

getAvailablePurchases({ alsoPublishToEventListenerIOS: true }) forwards this flag into this method, but the returned purchases are never emitted to the purchase listener. Publish each serialized payload when alsoPublish is true, or explicitly document that Onside does not support this option.

🐛 Proposed fix
             let payload: [[String: Any]] = try await MainActor.run {
                 try Onside.defaultPaymentQueue().transactions.compactMap { transaction -> [String: Any]? in
                     switch transaction.transactionState {
                     case .purchased, .restored:
                         return try serialize(transaction: transaction)
                     default:
                         return nil
                     }
                 }
             }
+            if alsoPublish {
+                payload.forEach {
+                    sendEvent(OnsideEvent.purchaseUpdated.rawValue, $0)
+                }
+            }
             ExpoIapLog.result("getAvailableItemsOnside", value: payload)
             return payload
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@libraries/expo-iap/ios/onside/OnsideIapModule.swift` around lines 303 - 314,
The getAvailableItemsOnside path ignores the alsoPublish flag even though
getAvailablePurchases forwards it here, so update this method to honor that
option by emitting each serialized transaction payload to the purchase listener
before returning it, or otherwise make the Onside-specific behavior explicit.
Use the existing getAvailableItemsOnside flow, the serialize(transaction:)
helper, and the purchase-listener publishing code path to locate where the
publish call should be added.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@libraries/expo-iap/ios/onside/OnsideIapModule.swift`:
- Around line 303-314: The getAvailableItemsOnside path ignores the alsoPublish
flag even though getAvailablePurchases forwards it here, so update this method
to honor that option by emitting each serialized transaction payload to the
purchase listener before returning it, or otherwise make the Onside-specific
behavior explicit. Use the existing getAvailableItemsOnside flow, the
serialize(transaction:) helper, and the purchase-listener publishing code path
to locate where the publish call should be added.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 45fecb9a-124f-4463-b9bd-64a78722061d

📥 Commits

Reviewing files that changed from the base of the PR and between 532591e and 64c4973.

📒 Files selected for processing (4)
  • libraries/expo-iap/ios/onside/OnsideIapModule.swift
  • libraries/kmp-iap/library/src/androidMain/kotlin/io/github/hyochan/kmpiap/InAppPurchaseAndroid.kt
  • packages/google/openiap/src/horizon/java/dev/hyo/openiap/OpenIapModule.kt
  • packages/google/openiap/src/play/java/dev/hyo/openiap/OpenIapModule.kt
🚧 Files skipped from review as they are similar to previous changes (1)
  • libraries/kmp-iap/library/src/androidMain/kotlin/io/github/hyochan/kmpiap/InAppPurchaseAndroid.kt

@hyochan

hyochan commented Jun 25, 2026

Copy link
Copy Markdown
Member Author

Fixed in 01d46ef.

Addressed CodeRabbit's outside-diff caution for Onside getAvailableItems:

  • getAvailableItemsOnside now honors alsoPublish by emitting each returned payload through the purchase-updated listener before returning.
  • Also addressed the two Gemini NSDecimalNumber precision comments in the same commit.
  • Verified with Expo iOS simulator build, Expo lint/TS/test, Expo example tests, and parity audit.

@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 introduces support for FetchProductsResultAll across multiple platforms (Android, iOS, Flutter, and KMP) to preserve mixed in-app products and subscriptions in a single query, while updating the default query type fallback to ProductQueryType.InApp. On iOS, it refactors OnsideIapModule to run UI and payment queue operations on the MainActor and implements a transaction date cache. The review feedback suggests avoiding redundant Decimal conversions for NSDecimalNumber to prevent precision loss and dispatching events inside the MainActor.run block to ensure strict concurrency safety.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread libraries/expo-iap/ios/onside/OnsideIapModule.swift Outdated
Comment thread libraries/expo-iap/ios/onside/OnsideIapModule.swift Outdated
Comment thread libraries/expo-iap/ios/onside/OnsideIapModule.swift
@hyodotdev hyodotdev deleted a comment from coderabbitai Bot Jun 25, 2026
@hyochan hyochan merged commit f5188a8 into main Jun 25, 2026
15 checks passed
@hyochan hyochan deleted the fix/expo-fetch-products-all branch June 25, 2026 16:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🤖 android Related to android 🛠 bugfix All kinds of bug fixes cross-platform Cross-platform (both Android & iOS) expo-iap expo-iap library flutter-iap kmp-iap kmp-iap library

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Android: fetchProducts({ type: 'all' }) returns [] — FetchProductsResultAll dropped in ExpoIapModule.kt

1 participant