Skip to content

Add onboarding analytics support#391

Merged
ianrumac merged 1 commit intodevelopfrom
ir/feat/pw_analytics
Apr 1, 2026
Merged

Add onboarding analytics support#391
ianrumac merged 1 commit intodevelopfrom
ir/feat/pw_analytics

Conversation

@ianrumac
Copy link
Copy Markdown
Collaborator

@ianrumac ianrumac commented Apr 1, 2026

Changes in this pull request

Checklist

  • All unit tests pass.
  • All UI tests pass.
  • Demo project builds and runs.
  • I added/updated tests or detailed why my change isn't tested.
  • I added an entry to the CHANGELOG.md for any breaking changes, enhancements, or bug fixes.
  • I have run ktlint in the main directory and fixed any issues.
  • I have updated the SDK documentation as well as the online docs.
  • I have reviewed the contributing guide

Greptile Summary

This PR adds onboarding/multi-page paywall analytics support by introducing a new paywall_page_view event fired whenever the user navigates between pages in a multi-step paywall. The implementation is end-to-end: a new PageViewData model, a page_view WebView message type, an internal PaywallPageView trackable event, and a public SuperwallEvent.PaywallPageView — all backed by solid tests.

Key changes:

  • PageViewData — new data class capturing page node IDs, flow position, navigation type, and optional timing info
  • PaywallMessage.PageView — parsed from the JS bridge; required fields are guarded with !!, optional ones use safe access
  • PaywallMessageHandler — dispatches InternalSuperwallEvent.PaywallPageView on the IO scope, consistent with existing event tracking patterns
  • SuperwallEvent.PaywallPageView / SuperwallEvents.PaywallPageView — public and enum entries wired correctly
  • Tests — four integration-style tests covering all-fields, optional-null, handler tracking, and missing-required-field failure paths

Minor concerns to address before merging:

  • Import ordering in TrackableSuperwallEvent.kt and SuperwallEvent.kt splits alphabetical groups and will likely fail ktlint
  • The navigationType field is mapped from the JSON key "type" rather than the expected "navigation_type", inconsistent with every other field in the same struct
  • PageViewData is placed in paywall.view.webview.messaging (an internal package) but is exposed via the public SuperwallEvent.PaywallPageView; consider relocating it to a presentation/model package

Confidence Score: 5/5

Safe to merge; all remaining findings are P2 style/design suggestions with no correctness impact.

The implementation is correct, follows existing patterns, and is well-tested. The three open findings are all P2: import ordering (purely cosmetic/ktlint), the "type" vs "navigation_type" JSON key (works correctly if it matches the JS contract), and the PageViewData package placement (a design preference). None affect runtime behaviour.

Import ordering in TrackableSuperwallEvent.kt and SuperwallEvent.kt should be fixed before ktlint runs.

Important Files Changed

Filename Overview
superwall/src/main/java/com/superwall/sdk/paywall/view/webview/messaging/PageViewData.kt New data class for page-view analytics payload; clean structure with appropriate nullability. timeOnPreviousPageMs as Int? is fine for page-dwell durations.
superwall/src/main/java/com/superwall/sdk/paywall/view/webview/messaging/PaywallMessage.kt Adds PageView message type and parses it from JSON; required field handling is consistent with other messages, but navigationType is mapped from the generic JSON key "type" rather than the expected "navigation_type".
superwall/src/main/java/com/superwall/sdk/paywall/view/webview/messaging/PaywallMessageHandler.kt Handles the new PageView message by tracking an InternalSuperwallEvent.PaywallPageView on the IO scope; follows existing patterns correctly.
superwall/src/main/java/com/superwall/sdk/analytics/internal/trackable/TrackableSuperwallEvent.kt Adds PaywallPageView internal event with correct parameter mapping; import ordering for PageViewData breaks the alphabetical grouping of analytics.superwall imports.
superwall/src/main/java/com/superwall/sdk/analytics/superwall/SuperwallEvent.kt Adds public PaywallPageView event; exposes PageViewData from an internal webview package in the public API surface, and has an out-of-order import.
superwall/src/main/java/com/superwall/sdk/analytics/superwall/SuperwallEvents.kt Adds PaywallPageView("paywall_page_view") enum entry in the correct position; no issues.
superwall/src/test/java/com/superwall/sdk/analytics/internal/trackable/InternalSuperwallEventTest.kt Two well-structured tests covering all-fields and optional-fields-absent scenarios for the new event; assertions are thorough.
superwall/src/test/java/com/superwall/sdk/paywall/view/webview/PaywallMessageHandlerTest.kt Four tests covering all-fields parsing, optional-fields null, handler integration, and missing-required-field failure; good coverage of the new behaviour.

Sequence Diagram

sequenceDiagram
    participant JS as Paywall JS (WebView)
    participant MH as PaywallMessageHandler
    participant MQ as parseWrappedPaywallMessages
    participant ISE as InternalSuperwallEvent.PaywallPageView
    participant T as track()
    participant SE as SuperwallEvent.PaywallPageView

    JS->>MH: postMessage(json with event_name="page_view")
    MH->>MQ: parseWrappedPaywallMessages(json)
    MQ-->>MH: Result containing PageView(PageViewData)
    MH->>MH: handle(PaywallMessage.PageView)
    MH->>MH: messageHandler?.state?.info (get PaywallInfo)
    MH->>ISE: PaywallPageView(paywallInfo, data)
    MH->>T: ioScope.launch { track(event) }
    T->>SE: SuperwallEvent.PaywallPageView emitted to delegate
Loading
Prompt To Fix All With AI
This is a comment left during a code review.
Path: superwall/src/main/java/com/superwall/sdk/analytics/internal/trackable/TrackableSuperwallEvent.kt
Line: 1-4

Comment:
**Import ordering breaks ktlint grouping**

`PageViewData` is inserted between two `analytics.superwall` imports, splitting what should be a contiguous alphabetical group. This will likely fail `ktlint`. The same issue occurs in `SuperwallEvent.kt` (lines 8-9), where `paywall.view.webview.messaging.PageViewData` precedes `paywall.presentation.internal.*` imports out of alphabetical order.

```suggestion
package com.superwall.sdk.analytics.internal.trackable

import com.superwall.sdk.analytics.superwall.SuperwallEvent
import com.superwall.sdk.analytics.superwall.TransactionProduct
import com.superwall.sdk.paywall.view.webview.messaging.PageViewData
```

In `SuperwallEvent.kt`, move the `PageViewData` import after the `PaywallInfo` import and before `PaywallPresentationRequestStatus` to maintain alphabetical ordering within the `paywall.*` group.

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: superwall/src/main/java/com/superwall/sdk/paywall/view/webview/messaging/PaywallMessage.kt
Line: 273

Comment:
**JSON key `"type"` is inconsistent with field name `navigationType`**

Every other field in `PageViewData` maps directly from a snake_case JSON key that matches its Kotlin name (e.g. `navigation_node_id``navigationNodeId`), but `navigationType` maps from the ambiguous generic key `"type"`. This deviates from the established convention and is easy to misread.

If the upstream JS contract really sends `"type"`, consider documenting this explicitly, or (if the contract can be changed) align it to `"navigation_type"`.

```suggestion
                    navigationType = json["navigation_type"]!!.jsonPrimitive.content,
```

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: superwall/src/main/java/com/superwall/sdk/analytics/superwall/SuperwallEvent.kt
Line: 143-149

Comment:
**`PageViewData` is a public API type from an internal package**

`SuperwallEvent.PaywallPageView` is part of the SDK's public surface, but its `data` parameter is typed as `PageViewData` from `com.superwall.sdk.paywall.view.webview.messaging` — an implementation-detail package. SDK consumers using this event via `SuperwallDelegate` will need to import from the webview messaging internals.

Consider moving `PageViewData` to a more appropriate location (e.g. alongside `PaywallInfo` in `paywall.presentation`) or to a dedicated model package so the public API doesn't expose an internal layer.

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "Add onboarding analytics support" | Re-trigger Greptile

Greptile also left 3 inline comments on this PR.

@ianrumac ianrumac merged commit 75b6fdb into develop Apr 1, 2026
1 of 2 checks passed
Comment on lines 1 to +4
package com.superwall.sdk.analytics.internal.trackable

import com.superwall.sdk.analytics.superwall.SuperwallEvent
import com.superwall.sdk.paywall.view.webview.messaging.PageViewData
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.

P2 Import ordering breaks ktlint grouping

PageViewData is inserted between two analytics.superwall imports, splitting what should be a contiguous alphabetical group. This will likely fail ktlint. The same issue occurs in SuperwallEvent.kt (lines 8-9), where paywall.view.webview.messaging.PageViewData precedes paywall.presentation.internal.* imports out of alphabetical order.

Suggested change
package com.superwall.sdk.analytics.internal.trackable
import com.superwall.sdk.analytics.superwall.SuperwallEvent
import com.superwall.sdk.paywall.view.webview.messaging.PageViewData
package com.superwall.sdk.analytics.internal.trackable
import com.superwall.sdk.analytics.superwall.SuperwallEvent
import com.superwall.sdk.analytics.superwall.TransactionProduct
import com.superwall.sdk.paywall.view.webview.messaging.PageViewData

In SuperwallEvent.kt, move the PageViewData import after the PaywallInfo import and before PaywallPresentationRequestStatus to maintain alphabetical ordering within the paywall.* group.

Prompt To Fix With AI
This is a comment left during a code review.
Path: superwall/src/main/java/com/superwall/sdk/analytics/internal/trackable/TrackableSuperwallEvent.kt
Line: 1-4

Comment:
**Import ordering breaks ktlint grouping**

`PageViewData` is inserted between two `analytics.superwall` imports, splitting what should be a contiguous alphabetical group. This will likely fail `ktlint`. The same issue occurs in `SuperwallEvent.kt` (lines 8-9), where `paywall.view.webview.messaging.PageViewData` precedes `paywall.presentation.internal.*` imports out of alphabetical order.

```suggestion
package com.superwall.sdk.analytics.internal.trackable

import com.superwall.sdk.analytics.superwall.SuperwallEvent
import com.superwall.sdk.analytics.superwall.TransactionProduct
import com.superwall.sdk.paywall.view.webview.messaging.PageViewData
```

In `SuperwallEvent.kt`, move the `PageViewData` import after the `PaywallInfo` import and before `PaywallPresentationRequestStatus` to maintain alphabetical ordering within the `paywall.*` group.

How can I resolve this? If you propose a fix, please make it concise.

navigationNodeId = json["navigation_node_id"]!!.jsonPrimitive.content,
previousPageNodeId = json["previous_page_node_id"]?.jsonPrimitive?.contentOrNull,
previousFlowPosition = json["previous_flow_position"]?.jsonPrimitive?.intOrNull,
navigationType = json["type"]!!.jsonPrimitive.content,
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.

P2 JSON key "type" is inconsistent with field name navigationType

Every other field in PageViewData maps directly from a snake_case JSON key that matches its Kotlin name (e.g. navigation_node_idnavigationNodeId), but navigationType maps from the ambiguous generic key "type". This deviates from the established convention and is easy to misread.

If the upstream JS contract really sends "type", consider documenting this explicitly, or (if the contract can be changed) align it to "navigation_type".

Suggested change
navigationType = json["type"]!!.jsonPrimitive.content,
navigationType = json["navigation_type"]!!.jsonPrimitive.content,
Prompt To Fix With AI
This is a comment left during a code review.
Path: superwall/src/main/java/com/superwall/sdk/paywall/view/webview/messaging/PaywallMessage.kt
Line: 273

Comment:
**JSON key `"type"` is inconsistent with field name `navigationType`**

Every other field in `PageViewData` maps directly from a snake_case JSON key that matches its Kotlin name (e.g. `navigation_node_id``navigationNodeId`), but `navigationType` maps from the ambiguous generic key `"type"`. This deviates from the established convention and is easy to misread.

If the upstream JS contract really sends `"type"`, consider documenting this explicitly, or (if the contract can be changed) align it to `"navigation_type"`.

```suggestion
                    navigationType = json["navigation_type"]!!.jsonPrimitive.content,
```

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +143 to +149
data class PaywallPageView(
val paywallInfo: PaywallInfo,
val data: PageViewData,
) : SuperwallEvent() {
override val rawName: String
get() = SuperwallEvents.PaywallPageView.rawName
}
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.

P2 PageViewData is a public API type from an internal package

SuperwallEvent.PaywallPageView is part of the SDK's public surface, but its data parameter is typed as PageViewData from com.superwall.sdk.paywall.view.webview.messaging — an implementation-detail package. SDK consumers using this event via SuperwallDelegate will need to import from the webview messaging internals.

Consider moving PageViewData to a more appropriate location (e.g. alongside PaywallInfo in paywall.presentation) or to a dedicated model package so the public API doesn't expose an internal layer.

Prompt To Fix With AI
This is a comment left during a code review.
Path: superwall/src/main/java/com/superwall/sdk/analytics/superwall/SuperwallEvent.kt
Line: 143-149

Comment:
**`PageViewData` is a public API type from an internal package**

`SuperwallEvent.PaywallPageView` is part of the SDK's public surface, but its `data` parameter is typed as `PageViewData` from `com.superwall.sdk.paywall.view.webview.messaging` — an implementation-detail package. SDK consumers using this event via `SuperwallDelegate` will need to import from the webview messaging internals.

Consider moving `PageViewData` to a more appropriate location (e.g. alongside `PaywallInfo` in `paywall.presentation`) or to a dedicated model package so the public API doesn't expose an internal layer.

How can I resolve this? If you propose a fix, please make it concise.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant