Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
a19238b
Fix dead buy button on cached paywall re-presentation
claude Jul 13, 2026
583fd9a
Update paywall preload benchmark report [skip ci]
github-actions[bot] Jul 13, 2026
be41e61
test(app): add FTL re-present regression tests for transient state reset
claude Jul 14, 2026
f5882e7
Update paywall preload benchmark report [skip ci]
github-actions[bot] Jul 14, 2026
532bfea
Move transient-state reset to PaywallManager cache-hit so embedded ge…
claude Jul 14, 2026
67440be
Update paywall preload benchmark report [skip ci]
github-actions[bot] Jul 14, 2026
a977aca
Ensure loading is reset after purchase
ianrumac Jul 21, 2026
716d63e
Update paywall preload benchmark report [skip ci]
github-actions[bot] Jul 21, 2026
9882774
Improve tests, cleanup comments
ianrumac Jul 22, 2026
dfa6e6e
Update paywall preload benchmark report [skip ci]
github-actions[bot] Jul 22, 2026
0964503
Improve tests, ensure getPresentationResult doesnt break live pw
ianrumac Jul 22, 2026
3e0586c
Ensure benchmark only runs on labels
ianrumac Jul 22, 2026
9c1275f
Redeem existing web codes after successful purchase and restore
claude Jul 22, 2026
f29f744
Update paywall preload benchmark report [skip ci]
github-actions[bot] Jul 22, 2026
63a2441
Rename redeem closure to notifyBackendOfReceipts, drop comments
claude Jul 22, 2026
59e1fba
Update paywall preload benchmark report [skip ci]
github-actions[bot] Jul 22, 2026
7bec0c1
Merge pull request #434 from superwall/fix/paywall-transient-state-re…
ianrumac Jul 22, 2026
e774c87
Merge remote-tracking branch 'origin/develop' into redeem-after-purch…
claude Jul 22, 2026
fc18eb4
Update coverage badge [skip ci]
github-actions[bot] Jul 22, 2026
dc595df
Ensure redeem is called via fire and forget
ianrumac Jul 22, 2026
bc692ef
Merge pull request #435 from superwall/redeem-after-purchase-restore
ianrumac Jul 22, 2026
8dc6671
Fix purchase hang when purchase controller resolves outside Google Play
claude Jul 22, 2026
930f445
Update coverage badge [skip ci]
github-actions[bot] Jul 22, 2026
e1aa427
Stub purchaseResults on strict Billing mock in instrumentation test
claude Jul 22, 2026
3e5244b
Merge pull request #436 from superwall/claude/galaxy-store-purchase-h…
ianrumac Jul 22, 2026
23e738d
Changelog & version bump
ianrumac Jul 22, 2026
6657f35
Fix changelog
ianrumac Jul 22, 2026
af0fef3
Merge branch 'main' into develop
ianrumac Jul 22, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/badges/branches.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion .github/badges/jacoco.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
26 changes: 18 additions & 8 deletions .github/workflows/preload-benchmark.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# Paywall preload benchmark — runs on PRs into main or develop.
# Paywall preload benchmark — opt-in only. Runs on PRs into main or develop
# that carry the `release` or `benchmark` label, or on manual dispatch.
#
# Measures the time from Superwall.preloadAllPaywalls() until every paywall for
# the dev app's embedded API key reaches PaywallLoadingState.Ready, on 3
Expand All @@ -19,14 +20,16 @@
name: Paywall Preload Benchmark

on:
# PRs into main (release) and develop (feature) both run the benchmark, so
# preload regressions surface before they land anywhere.
# PRs into main (release) and develop (feature) run the benchmark only when
# opted in with the `release` or `benchmark` label — it burns 3 emulators for
# up to 40 minutes, so it's not worth paying on every PR push. `labeled` is in
# the trigger list so adding the label to an open PR starts the run.
pull_request:
branches: [ main, develop ]
# Merges re-run the benchmark AND roll the baseline forward automatically,
# so release PRs always gate against the latest mainline numbers.
push:
branches: [ main, develop ]
types: [ opened, synchronize, reopened, labeled ]
# No push trigger: a merge into main/develop carries no labels, so it could
# never satisfy the opt-in. Roll the baseline forward with a manual dispatch
# (update_baseline: true) off the branch you want to become the new mainline.
workflow_dispatch:
inputs:
update_baseline:
Expand All @@ -43,6 +46,13 @@ jobs:
build-apks:
runs-on: ubuntu-latest
timeout-minutes: 20
# Opt-in guard: PRs need the `release` or `benchmark` label; manual
# dispatches always run. The downstream jobs `needs` this one, so they skip
# with it.
if: >-
github.event_name == 'workflow_dispatch' ||
contains(github.event.pull_request.labels.*.name, 'release') ||
contains(github.event.pull_request.labels.*.name, 'benchmark')

steps:
- name: Checkout code
Expand Down Expand Up @@ -254,7 +264,7 @@ jobs:
--config .benchmark/config.json \
--report .benchmark/REPORT.md \
--normalized-out .benchmark/results \
${{ (github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && inputs.update_baseline)) && '--update-baseline' || '' }}
${{ (github.event_name == 'workflow_dispatch' && inputs.update_baseline) && '--update-baseline' || '' }}
echo "exit_code=$?" >> "$GITHUB_OUTPUT"

- name: Publish report to job summary
Expand Down
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@

The changelog for `Superwall`. Also see the [releases](https://github.com/superwall/Superwall-Android/releases) on GitHub.

## 2.7.22

## Enhancements
- Always notify backend of purchased entitlements
- Adds a 5 second timeout to resolving latest purchases, allowing non-play store purchases to resolve

## Fixes
- Fix a dead buy button when re-presenting a cached paywall in the same session after a purchase, for both `register()` and embedded paywalls (`getPaywall`/`getPaywallView`). Per-presentation transient state (loading spinner and presentation-prepared flag) is now reset on each new presentation, so it no longer leaks from a previous presentation that was stopped without a finishing teardown.

## 2.7.21

## Enhancements
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package com.example.superapp.test

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import com.superwall.sdk.paywall.presentation.get_paywall.builder.PaywallBuilder
import com.superwall.sdk.paywall.presentation.internal.state.PaywallResult
import com.superwall.sdk.paywall.view.PaywallView
import com.superwall.sdk.paywall.view.delegate.PaywallViewCallback
import kotlinx.coroutines.launch

/**
* Minimal test-host screen that EMBEDS a paywall inside a [Fragment] via the public
* embed API ([PaywallBuilder] -> [com.superwall.sdk.Superwall.getPaywall]), NOT via
* [com.superwall.sdk.paywall.presentation.register].
*/
class PaywallHostFragment : Fragment() {
// Parameterised so the whole flow keys off a single placement constant.
var placement: String = RepresentTests.CONSUMABLE_REBUY_PLACEMENT

private lateinit var container: FrameLayout
private var paywallView: PaywallView? = null

// Minimal embed delegate. The fragment host stays on screen so the test can
// re-embed the same paywall, so we intentionally do NOT finish the activity here.
private val delegate =
object : PaywallViewCallback {
override fun onFinished(
paywall: PaywallView,
result: PaywallResult,
shouldDismiss: Boolean,
) {
// no-op: keep the host alive for re-embedding.
}
}

override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?,
): View = FrameLayout(requireContext()).also { this.container = it }

// NOTE: no auto-present on view-create. The SDK's config fetch is gated on the app
// being in the FOREGROUND (Network.getConfig -> awaitUntilAppInForeground), so the
// test must launch this host first, await Configured, and only then call [present].

/**
* Obtains the embedded paywall via the public [PaywallBuilder]/`getPaywall` API and
* attaches it into the fragment's container. On the second call the SDK reuses the
* CACHED `PaywallView` (via `PaywallManager.getPaywallView`'s cache-hit branch),
* which is precisely the path whose stale `LoadingPurchase` overlay used to swallow
* the buy tap before PR #434.
*/
fun present() {
viewLifecycleOwner.lifecycleScope.launch {
embedPaywall()
}
}

private suspend fun embedPaywall() {
// Detach any previously-embedded instance before re-embedding the (cached) view.
paywallView?.let { (it.parent as? ViewGroup)?.removeView(it) }

PaywallBuilder(placement)
.delegate(delegate)
.activity(requireActivity())
.build()
.onSuccess { view ->
paywallView = view
// `build()` already ran getPaywall() (cache-hit reset) + beforeViewCreated();
// ensure the view isn't still parented, attach it, then drive onViewCreated()
// as PaywallComposable does.
(view.parent as? ViewGroup)?.removeView(view)
container.addView(view)
view.onViewCreated()
}
}
}
Loading
Loading