Conversation
Fix expo lock issues with single queueu calls
superwall/src/main/java/com/superwall/sdk/misc/CurrentActivityTracker.kt
Show resolved
Hide resolved
.../com/superwall/sdk/store/abstractions/product/receipt/RenewedSubscriptionActiveStatusTest.kt
Show resolved
Hide resolved
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Changes in this pull request
2.7.5
Enhancements
Fixes
Checklist
CHANGELOG.mdfor any breaking changes, enhancements, or bug fixes.ktlintin the main directory and fixed any issues.Greptile Summary
This release (2.7.5) contains four targeted fixes and one enhancement: it adds the
APPSTACKattribution provider, fixes test mode's Expo/React Native compatibility by using aCurrentActivityTrackerto resolve the true foreground activity instead of the rootMainActivity, corrects subscription active-status detection by basing it on Google Play'spurchaseStaterather than a naive expiration date calculation, addresses a memory leak when the WebView render process crashes, and prevents redundant O(n) cache cleanup runs.Key changes:
AttributionProvider.kt: newAPPSTACK("appstackId")enum entry — clean, no concerns.PlayStorePurchaseAdapter.kt:calculateIsActive()now returnstrueforPurchaseState.PURCHASEDregardless of expiration, correctly handling renewed subscriptions — well-tested.ConfigManager.kt/TestModeTransactionHandler.kt: both now accept aCurrentActivityTracker?and prefer lifecycle-tracked activity to fix Expo/RN root-activity clobbering.PaywallView.cleanup(): clears webview delegate, unregisters theactivityResultLauncher, and removes views — but usesremoveAllViews()on the parent instead ofremoveView(this), which could remove unintended sibling views (see comment).CurrentActivityTracker.onActivityStopped: clearsactivityStatebut notcurrentActivityWeakReference, meaninggetCurrentActivity()can return a stopped activity and bypass theawaitActivityfallback inpresentTestModeModalandgetForegroundActivity()(see comment).RenewedSubscriptionActiveStatusTest.kt: new BDD-style test suite covering theisActivefix — a minor comment inconsistency on the product-not-found edge case says "null expiration means isActive=true" when expiration is no longer used.Confidence Score: 3/5
isActivefix and attribution enum addition are clean and well-tested. However,PaywallView.cleanup()callsremoveAllViews()on the parent (rather thanremoveView(this)), which could destructively strip sibling views in any layout that places PaywallView alongside other children. Additionally,CurrentActivityTracker.onActivityStoppeddoes not clearcurrentActivity, allowinggetCurrentActivity()to return a stopped activity — which prevents theawaitActivitysafety-net from activating and could cause test-mode modals to be shown on a backgrounded activity. Both issues were introduced as part of the intended fixes in this PR.PaywallView.kt(cleanup logic) andCurrentActivityTracker.kt(onActivityStopped) need the most attention before merging.Important Files Changed
removeAllViews()on parent is overly broad and could remove unintended sibling views.onActivityStoppeddoes not clearcurrentActivityWeakReference, sogetCurrentActivity()can return a stopped activity, defeating theawaitActivityfallback.isActiveto usepurchaseState == PURCHASEDinstead of expiration-date calculation, correctly handling renewed subscriptions that Google Play returns as PURCHASED with an outdatedpurchaseTime.activityTracker: CurrentActivityTracker?parameter and updatedpresentTestModeModalto prefer lifecycle-tracked activity over user-providedActivityProvider, withawaitActivityas last fallback — resolves the Expo/RN root activity issue.activityTracker: CurrentActivityTracker?parameter so purchase/restore drawers prefer the lifecycle-tracked foreground activity over the user-provided provider, consistent with the Expo fix in ConfigManager.resetCache()to calldestroyWebview()on non-active views beforeremoveAll(), ensuring webview resources are cleaned up and cache clear doesn't run redundant O(n) iterations.Sequence Diagram
sequenceDiagram participant App participant ConfigManager participant CurrentActivityTracker participant ActivityProvider participant TestModeModal participant TestModeTransactionHandler participant PlayStorePurchaseAdapter App->>ConfigManager: processConfig(config) ConfigManager->>ConfigManager: evaluateTestMode() ConfigManager->>ConfigManager: ioScope.launch { presentTestModeModal() } ConfigManager->>CurrentActivityTracker: getCurrentActivity() alt Activity is available (Expo fix) CurrentActivityTracker-->>ConfigManager: foreground Activity else Lifecycle tracker returns null ConfigManager->>ActivityProvider: getCurrentActivity() ActivityProvider-->>ConfigManager: root Activity (may be stale in Expo/RN) else Both null ConfigManager->>CurrentActivityTracker: awaitActivity(10s) CurrentActivityTracker-->>ConfigManager: Activity (or null on timeout) end ConfigManager->>TestModeModal: show(activity, ...) TestModeModal-->>ConfigManager: TestModeModalResult App->>TestModeTransactionHandler: handlePurchase(product) TestModeTransactionHandler->>CurrentActivityTracker: getCurrentActivity() (prefers lifecycle-tracked) CurrentActivityTracker-->>TestModeTransactionHandler: foreground Activity App->>PlayStorePurchaseAdapter: isActive Note over PlayStorePurchaseAdapter: purchaseState == PURCHASED → true\n(no longer uses expiration date)\nFixes renewed subscription detection PlayStorePurchaseAdapter-->>App: true/falseComments Outside Diff (1)
General comment
removeAllViews()on parent removes sibling views(parent as? ViewGroup)?.removeAllViews()removes all children of the parent container, not just thisPaywallView. If the parentViewGrouphas any sibling views (backdrop overlays, dimming layers, etc.), they will also be removed.Compare this with
prepareToDisplay()just above (line 1054), which correctly usesparentViewGroup?.removeView(this@PaywallView)to remove only this specific view.Prompt To Fix With AI
Last reviewed commit: 56e81c0