feat: add android install attribution matching#389
Open
Conversation
superwall/src/main/java/com/superwall/sdk/network/MmpService.kt
Outdated
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.
Summary
Notes
Verification
Greptile Summary
This PR adds Android MMP install attribution matching by wiring a new
MmpServiceinto the SDK startup flow: on first install (within a 7-day window) the Play Store Install Referrer is queried for asw_mmp_click_id, a fingerprint payload is posted to/api/match, and the returnedacquisitionAttributesare merged into user attributes before anattribution_matchevent is tracked.Previous review concerns are well-addressed —
clickIdis nowLong?, JSON parsing uses the safeas? JsonPrimitivecast, thewithTimeoutOrNullspin-loop has adelay(50)yield point, double URL-decoding is removed, andendConnection()is called on the backing field rather than the null-guarded computed property.Key remaining observations:
checkForMmpClickId()(up to 5 s) is awaited synchronously beforefetchConfiguration(). BecauseDidCompleteMMPInstallAttributionRequestis only written on a successful match, any launch within the 7-day window where the previous request failed will incur the full Play Store referrer wait again, delaying theconfigure()completion callback and paywall availability on every such launch.attribution_matchwithreason: "request_failed"on every retry launch, which could generate noisy analytics data for the full attribution window.mergeMMPAcquisitionAttributesIfNeededcallsSuperwall.instance.setUserAttributes()from inside theNetworkclass, bypassing theApiFactoryinterface already available in the constructor and making the method untestable viaNetworkMock.Confidence Score: 4/5
Safe to merge with awareness of the startup latency regression on retry launches within the attribution window.
All previously flagged P1 issues are resolved. The three remaining findings are P2 design/quality concerns: startup latency on repeated retries, repeated failure-event noise, and a layer-coupling smell. None block correctness on first install or break existing flows, but the latency issue could noticeably degrade UX on retry launches for up to 7 days post-install.
Superwall.kt (startup critical path), LocalStorage.kt (retry/completion logic), Network.kt (Superwall.instance coupling)
Important Files Changed
checkForMmpClickId()is awaited synchronously beforefetchConfiguration(), adding up to 5 s of startup latency on every retry launch within the 7-day window.matchMMPInstallwith safe JSON helpers and acquisition-attribute merging; reaches up toSuperwall.instancefrom the network layer, breaking layer boundaries and testability.NetworkServicesubclass for/api/match;clickIdis correctly typed asLong?, serialization config is sensible, and the two-retry policy is appropriate.delay(50)added to spin-loop, double URL-decode removed,endConnection()now called on the backing field,getInstallReferrerParamsshared helper extracts common logic cleanly.DidCompleteMMPInstallAttributionRequestis only written on success, causing repeated retries (and repeated failure events) across launches within the 7-day window.Storable<Boolean>cache keys added for MMP attribution state; straightforward and consistent with existing key definitions.ProviderandConfidenceenums; clean, serializable, and well-documented.MmpServiceintoNetwork; usesapi.subscription.hostwithversion = "/"which correctly resolves to/api/matchat the subscription host.timezoneOffsetSeconds,screenWidth,screenHeight,devicePixelRatio, andappInstalledAtMillishelpers;timezoneOffsetSecondsreuses the existingrawOffsetcomputation already used bysecondsFromGMT.subscriptionHostandenrichmentHostto theCustomenvironment withbaseHostdefaults; also surfaces both intoMap()for debugging.matchMMPInstallto theSuperwallAPIinterface with a nullable default parameter;NetworkMockstub correctly returnsfalse.matchMMPInstall, returningfalse— consistent with existing mock behaviour.Sequence Diagram
sequenceDiagram participant App participant Superwall participant LocalStorage participant DeepLinkReferrer participant PlayStore as Play Store Referrer participant Network participant MmpService as MmpService (/api/match) App->>Superwall: configure() Superwall->>LocalStorage: recordAppInstall() Superwall->>Superwall: identityManager.configure() [await] Superwall->>LocalStorage: shouldAttemptInitialMMPInstallAttributionMatch() LocalStorage-->>Superwall: true (first install, within 7-day window) Superwall->>DeepLinkReferrer: checkForMmpClickId() [await, ≤5 s] DeepLinkReferrer->>PlayStore: startConnection() PlayStore-->>DeepLinkReferrer: OK / timeout DeepLinkReferrer-->>Superwall: Result<Long> (clickId or null) Superwall->>LocalStorage: recordMMPInstallAttributionRequest { ... } [fire-and-forget] Superwall->>Superwall: configManager.fetchConfiguration() [await] Superwall-->>App: completion(.success) Note over LocalStorage,MmpService: Fire-and-forget coroutine LocalStorage->>Network: matchMMPInstall(clickId) Network->>MmpService: POST /api/match MmpService-->>Network: MmpMatchResponse alt matched == true Network->>Superwall: setUserAttributes(acquisitionAttributes) Network->>Superwall: track(AttributionMatch(matched=true)) Network-->>LocalStorage: true → write DidCompleteMMPInstallAttributionRequest else network error Network->>Superwall: track(AttributionMatch(reason=request_failed)) Network-->>LocalStorage: false → flag NOT written (retry next launch) endComments Outside Diff (1)
superwall/src/main/java/com/superwall/sdk/analytics/superwall/AttributionMatchInfo.kt, line 121-123 (link)APPLE_SEARCH_ADSis dead code in the Android SDKApple Search Ads is an iOS-only advertising platform and has no equivalent on Android. Shipping this variant in the Android SDK is misleading — consumer code could construct an
AttributionMatchInfowithProvider.APPLE_SEARCH_ADS, but it will never be produced by the SDK itself. Consider removing the variant or annotating it as reserved for future cross-platform use to avoid confusion.Prompt To Fix With AI
Prompt To Fix All With AI
Reviews (5): Last reviewed commit: "fix android referrer and test network mo..." | Re-trigger Greptile