Skip to content

Add QPurchaseResult to EntitlementsUpdateListener#769

Open
NickSxti wants to merge 2 commits intodevelopfrom
nch/sup3-58-entitlements-listener-purchase-result
Open

Add QPurchaseResult to EntitlementsUpdateListener#769
NickSxti wants to merge 2 commits intodevelopfrom
nch/sup3-58-entitlements-listener-purchase-result

Conversation

@NickSxti
Copy link

Summary

  • Extends QEntitlementsUpdateListener with a new overloaded onEntitlementsUpdated(entitlements, purchaseResult) method
  • When deferred purchases complete in background (no active callback), the listener now receives QPurchaseResult with full purchase details
  • Fixes the issue where consumable purchases that don't create entitlements resulted in an empty map with no transaction info (1768+ instances in API Gateway logs)

Changes

  • QEntitlementsUpdateListener: Added second method with QPurchaseResult? parameter. Both methods have default implementations for mutual delegation — fully backward compatible
  • QProductCenterManager: Success path (line ~1047) and fallback path (calculatePurchasePermissionsLocally) now pass QPurchaseResult to listener when no purchase callback exists
  • Sample app: Updated EntitlementsFragment to demonstrate the new method with consumable purchase handling
  • Tests: Added tests verifying listener receives QPurchaseResult for deferred purchases, and is NOT called when a callback exists

Backward compatibility

  • Old implementations (override only old method) continue to work — new method default calls old
  • New implementations (override only new method) work — old method default calls new
  • @Deprecated annotation guides new adopters to the new method
  • Java interop preserved — both method signatures accessible from Java

Test plan

  • Verify SDK compiles: ./gradlew :sdk:assemble
  • Run unit tests: ./gradlew :sdk:test
  • Verify sample app compiles without changes to old listener code
  • Test deferred consumable purchase flow — listener should receive non-null QPurchaseResult
  • Test normal purchase flow — callback receives result, listener is NOT called

Resolves: SUP3-58

🤖 Generated with Claude Code

@NickSxti
Copy link
Author

Review & Fix: Mutual Recursion in QEntitlementsUpdateListener

Problem Found

Both interface methods had default implementations that delegated to each other:

// Before (dangerous):
fun onEntitlementsUpdated(entitlements) {
    onEntitlementsUpdated(entitlements, null)  // → calls 2-arg
}

fun onEntitlementsUpdated(entitlements, purchaseResult?) {
    onEntitlementsUpdated(entitlements)  // → calls 1-arg → StackOverflowError
}

If a consumer implemented the interface without overriding either method → infinite recursion → StackOverflowError crash.

Fix Applied (commit 745db80)

Replaced the 1-arg default body with a no-op:

// After (safe):
fun onEntitlementsUpdated(entitlements) {
    // No-op default. Overridden by existing consumers.
}

fun onEntitlementsUpdated(entitlements, purchaseResult?) {
    onEntitlementsUpdated(entitlements)  // → calls 1-arg → no-op (terminates)
}

Backward Compatibility Matrix

Scenario SDK calls 2-arg → Result
Old code (overrides 1-arg only) 2-arg default → 1-arg override ✅ Works
New code (overrides 2-arg only) 2-arg override runs ✅ Works
Both overridden 2-arg override runs ✅ Works
Neither overridden 2-arg default → 1-arg no-op ✅ Silent no-op (no crash)

Note on local tests

Could not run ./gradlew :sdk:test locally — requires JDK 11+ (only JDK 8 available on this machine). Please verify CI passes. The change is a 1-line no-op replacement with no logic changes to production flow.

🤖 Generated with Claude Code

@NickSxti
Copy link
Author

Local Test Results ✅

Environment: JDK 17.0.18 (Temurin), Android SDK, macOS (aarch64)

./gradlew :sdk:testDebugUnitTest — All 27 tests PASSED

QHandledPurchasesCacheTest > sequential saving test PASSED
QHandledPurchasesCacheTest > non empty cache PASSED
QHandledPurchasesCacheTest > empty cache PASSED
QHandledPurchasesCacheTest > saving multiple purchases PASSED
QProductCenterManagerTest > normal purchase with callback does not notify listener PASSED
QProductCenterManagerTest > handle pending purchases when launching is finished and query purchases failed PASSED
QProductCenterManagerTest > deferred purchase with no callback notifies listener with purchaseResult PASSED
QProductCenterManagerTest > handle pending purchases when launching is finished and query purchases completed PASSED
QProductCenterManagerTest > handle pending purchases when launching is not finished PASSED
QUserPropertiesManagerTest > should not force send properties when properties storage is empty PASSED
QUserPropertiesManagerTest > send properties with delay on background PASSED
QUserPropertiesManagerTest > should set and not send user property when sending properties is scheduled PASSED
QUserPropertiesManagerTest > should not send facebook attribution when it is null PASSED
QUserPropertiesManagerTest > should force send properties and get response in onError callback PASSED
QUserPropertiesManagerTest > should force send properties and get response in onSuccess callback PASSED
QUserPropertiesManagerTest > should send facebook attribution when it is not null PASSED
QUserPropertiesManagerTest > on app foreground when properties is not empty PASSED
QUserPropertiesManagerTest > on app foreground when properties is empty PASSED
QUserPropertiesManagerTest > should set isRequestInProgress to true and isSendingScheduled to false when properties storage is not empty PASSED
QUserPropertiesManagerTest > setProperty PASSED
QUserPropertiesManagerTest > should force send properties when onAppBackground is called PASSED
QUserPropertiesManagerTest > should force send properties again after failed attempt on foreground PASSED
QUserPropertiesManagerTest > send properties with delay on foreground PASSED
QUserPropertiesManagerTest > should not set user property when its value is empty PASSED
QUserPropertiesManagerTest > should not force send properties again after failed attempt on background PASSED
QUserPropertiesManagerTest > should not force send properties when request is in progress PASSED
QUserPropertiesManagerTest > should set and send user property when it is not empty and sending is not scheduled PASSED
AppRequestTest > appRequestWithCorrectData PASSED
OsRequestTest > osRequestWithCorrectData PASSED
ProviderDataRequestTest > providerDataRequestWithCorrectData PASSED
UserPropertiesStorageTest > saveAndClearProperties PASSED
UserPropertiesStorageTest > saveProperties PASSED

./gradlew :sdk:assemble — BUILD SUCCESSFUL

SDK compiles without errors for both debug and release variants.

Fix applied in commit 745db80

Replaced mutual recursion in QEntitlementsUpdateListener with no-op default for the deprecated 1-arg method.

🤖 Generated with Claude Code

NickChechnev and others added 2 commits March 5, 2026 15:12
…ases

When deferred consumable purchases complete in background (no active
callback), the SDK now passes QPurchaseResult to the entitlements
update listener. This allows developers to access purchase details
for consumables that don't create entitlements.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace the 1-arg method's default body (which delegated to 2-arg)
with a no-op. This breaks the mutual recursion cycle that would cause
StackOverflowError if neither method was overridden.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants