Conversation
SubscriptionPeriod converted between days and weeks via a 52/365 factor. But 52 weeks is only 364 days, so a 7-day period resolved to ~0.997 weeks instead of exactly 1 — inflating a 7-day product's weekly price and skewing a week product's daily price (e.g. a $7.00/week product reported $0.99/day instead of the exact $1.00). 7 days is exactly 1 week. Use the exact ratio in both directions: - SubscriptionPeriod.pricePerDay .week: 365/52 -> 7 - SubscriptionPeriod.pricePerWeek .day: 52/365 -> 1/7 - RawStoreProduct.periodsPerUnit (trial path): the same two day↔week entries. Month↔year was already exact (12); week/month and week/year stay as conventional approximations — those aren't whole-number relationships. Mirrors the same fix in the iOS SDK and the web editor so all three agree. Adds two regression tests (a 7-day period's weekly price equals the price; a weekly product's daily price divides by exactly 7) in the active test region — the existing period-price tests are still inside the disabled "TODO: Re-enable in CI" block. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two issues in the new regression tests: - BigDecimal(6.99) calls the double constructor, which stores the inexact IEEE-754 value rather than exactly 6.99 — the two sides of the comparison could resolve to different values. Use the String constructor for exact decimals. - Kotlin's assert() is compiled to a no-op unless the JVM runs with -ea, so the tests could silently pass even on a regression. Switch to JUnit's assertEquals, which always evaluates. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Fix day↔week price conversion
Improve preloading
Collaborator
Author
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
Checklist
CHANGELOG.mdfor any breaking changes, enhancements, or bug fixes.ktlintin the main directory and fixed any issues.Greptile Summary
This release (2.7.15) introduces a fingerprint-based deduplication system for paywall preloading, fixes overenthusiastic
subscriptionStatus_didChangeemissions, repairs delegate event drops inSerialTaskManager, and corrects week↔day price calculation rounding errors.PaywallPreloadgains anAtomicReference<String?> lastFingerprintthat is CAS-committed before a preload starts;ConfigManager.recheckPreloadIfNeeded()is wired into subscription status changes,ComponentCallbacks2locale/config events, and interface-style overrides to re-trigger preload only when device state actually changed.ConfigLogicis also revised to short-circuit after the first matchingIF_TRUErule per campaign rather than preloading all matches.distinctUntilChangedwithEntitlement.isDistinct()(which ignores product-ID-only enrichment changes) prevents spurioussubscriptionStatus_didChangeevents.SerialTaskManagertasks are now wrapped inwithErrorTrackingso an exception no longer drops subsequent queued events;DependencyContainer.receipts()usesfirstOrNull/filterNotNullto avoidNoSuchElementExceptionon empty product lists; an unsafeas SuperwallPaywallActivity?cast is replaced withas?.Confidence Score: 4/5
Safe to merge with awareness of one edge case in the fingerprint rollback path under task cancellation.
The fingerprint CAS rollback only fires when another task is already running at function entry. If the launched task itself is cancelled mid-flight (e.g., the 5-second delay is interrupted during scope teardown), neither lastFingerprint nor currentPreloadingTask is reset. On a subsequent device-state change the CAS to the new fingerprint succeeds, but currentPreloadingTask != null immediately rolls it back to the old value — so recheckPreloadIfNeeded() keeps seeing a mismatch and dispatching on every triggering event without ever starting a real preload. The rest of the changes (dedup logic, subscription-status filtering, price math, error-tracking in the task queue) are straightforward and well-tested.
superwall/src/main/java/com/superwall/sdk/config/PaywallPreload.kt — the CAS guard and task lifecycle around the new fingerprint field.
Important Files Changed
Comments Outside Diff (1)
superwall/src/main/java/com/superwall/sdk/config/PaywallPreload.kt, line 54-114 (link)lastFingerprintpermanently stuck after task cancellation or mid-flight failureThe CAS rollback (
lastFingerprint.compareAndSet(fingerprint, previous)) only fires when another task is already running at entry. If the task is successfully launched but later cancelled — e.g.delay(5000)receives aCancellationExceptionduring scope teardown, or any suspend call inside the coroutine throws beforecurrentPreloadingTask = nullon line 113 — bothlastFingerprintandcurrentPreloadingTaskremain permanently set with no reset path.This creates a compounding loop when device state subsequently changes:
recheckPreloadIfNeeded()seeslastFingerprint.get() ("X") != newFingerprint ("Y")and keeps dispatchingPreloadIfEnabled; each dispatch CAS-commits "Y", hitscurrentPreloadingTask != null, rolls back to "X", and returns — solastFingerprintnever advances and the dispatch fires again on every subsequent triggering event.A
try/finallyblock inside the launched coroutine that resetslastFingerprint(andcurrentPreloadingTask) on abnormal exit would close this gap.Prompt To Fix With AI
Prompt To Fix All With AI
Reviews (2): Last reviewed commit: "Fix stale preload fingerprint issues" | Re-trigger Greptile