Skip to content

Conversation

@JZDesign
Copy link
Contributor

Checklist

  • If applicable, unit tests
  • If applicable, create follow-up issues for purchases-ios and hybrids

Motivation

Price variables such as {{ product.price_per_week }} were displayed as literal text on initial paywall render, then correctly resolved after user interaction (e.g., tapping a tab, switch, or package).

Description

Root Cause

A logic inconsistency in PaywallState.kt where selectedTabIndex and initialSelectedPackage used different null-handling strategies for the initialSelectedTabIndex parameter.

The Problem

// Line 203: Correctly defaults to 0 when initialSelectedTabIndex is null
var selectedTabIndex by mutableIntStateOf(initialSelectedTabIndex ?: 0)

// Lines 206-207: Uses ?.let which short-circuits when initialSelectedTabIndex is null
private val initialSelectedPackage = initialSelectedPackageOutsideTabs
    ?: initialSelectedTabIndex?.let { selectedPackageByTab[it] }  // BUG: Returns null!

When initialSelectedTabIndex is null (which occurs when a TabsComponent has no defaultTabId set):

Variable Value Explanation
selectedTabIndex 0 Correctly defaults via ?: 0
initialSelectedTabIndex?.let { ... } null ?.let short-circuits on null receiver
initialSelectedPackage null Even though selectedPackageByTab[0] has a valid package

Impact

  1. selectedPackage initialized to null
  2. selectedPackageInfo becomes null
  3. TextComponentState.applicablePackage becomes null
  4. Variable processing is skipped in TextComponentView.rememberProcessedText()
  5. Literal template text {{ product.price_per_week }} is displayed

After user interaction, update() is called with proper fallback logic that finds the package, triggering recomposition and correct variable processing.

Fix

File: ui/revenuecatui/src/main/kotlin/com/revenuecat/purchases/ui/revenuecatui/data/PaywallState.kt

Before:

private val initialSelectedPackage = initialSelectedPackageOutsideTabs
    ?: initialSelectedTabIndex?.let { selectedPackageByTab[it] }

After:

private val initialSelectedPackage = initialSelectedPackageOutsideTabs
    ?: selectedPackageByTab[selectedTabIndex]

This ensures the package lookup uses selectedTabIndex (which already defaults to 0) instead of relying on the nullable initialSelectedTabIndex.

Affected Scenarios

Paywalls where:

  • A TabsComponent exists without an explicit defaultTabId
  • Text components with product variables are rendered outside of a PackageComponent
  • The text relies on selectedPackageProvider() for package context

@JZDesign
Copy link
Contributor Author

Note

This needs to be tested well. I need to replicate the issue and test this in several scenarios

@RevenueCat-Danger-Bot
Copy link

1 Error
🚫 Label the PR using one of the change type labels. If you are not sure which label to use, choose pr:other.
Label Description
pr:feat A new feature. Use along with pr:breaking to force a major release.
pr:fix A bug fix. Use along with pr:force_minor to force a minor release.
pr:other Other changes. Catch-all for anything that doesn't fit the above categories. Releases that only contain this label will not be released. Use along with pr:force_patch, or pr:force_minor to force a patch or minor release.
pr:RevenueCatUI Use along any other tag to mark a PR that only contains RevenueCatUI changes
pr:next_release Preparing a new release
pr:dependencies Updating a dependency
pr:phc_dependencies Updating purchases-hybrid-common dependency
pr:changelog_ignore The PR will not be included in the changelog. This label doesn't determine the type of bump of the version and must be combined with pr:feat, pr:fix or pr:other.

Generated by 🚫 Danger

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.

3 participants