Skip to content

[Paywalls V2] FIT mode sizes video/image components based on native pixel resolution instead of available space #3182

@jensck

Description

@jensck

Describe the bug

Video and image components configured with Fit sizing for both width and height in Paywalls v2 display at inconsistent sizes across devices with different screen densities. Per the RevenueCat documentation, FIT means "resized to fit the available space without cropping." However, the current implementation sizes components based on their native pixel resolution divided by device density, producing different DP sizes on every device.

  1. Environment

    1. Platform: Android
    2. SDK version: Latest (main branch)
    3. OS version: Android 14-15 (tested across multiple)
    4. Android Studio version: Latest stable
    5. How widespread is the issue: 100% of devices. Every device with a different screen density renders FIT-sized media at a different DP size.
  2. Debug logs that reproduce the issue

    Added logging to VideoComponentState.adjustDimension and TextureVideoView.applySizing. A 720x720px video with Fit+Fit sizing produces:

    Device Density Component Size (DP) Screen Width (DP)
    Samsung A15 3.0 240 ~360
    Pixel Fold 2.625 274 ~411
    Pixel 7 Pro 3.5 206 ~411
    720p @ 320dpi 2.0 360 360

    Note: The Pixel Fold and Pixel 7 Pro are both 411dp wide, yet the component renders at 274dp vs 206dp.

  3. Steps to reproduce, with a description of expected vs. actual behavior

    Steps:

    • Create a Paywalls v2 paywall with a video or image component
    • Set both width and height to "Fit"
    • Display the paywall on devices with different screen densities

    Expected: The component fills its available container space while preserving aspect ratio, displaying at a consistent size relative to the screen across all devices.

    Actual: The component's DP size equals nativePixels / density, effectively rendering at native pixel resolution. This produces wildly different visual sizes across devices.

  4. Other information

    Root cause: In both VideoComponentState.kt and ImageComponentState.kt, the adjustDimension method handles Fit+Fit by converting media pixel dimensions to DP:

    is Fit -> Fixed(with(density) { thisDimensionPx.toInt().toDp().value.toUInt() })

    This converts Fit into a Fixed constraint with a density-dependent value.

    Both state classes already have an aspectRatio property with a Fit+Fit branch that correctly returns AspectRatio(ratio = mediaAspectRatio, matchHeightConstraintsFirst = true). However, this code is unreachable because adjustDimension converts Fit to Fixed before aspectRatio is evaluated.

    Historical context: The Fit-to-Fixed conversion was introduced intentionally to give Compose concrete dimensions before async media loads, but its interaction with the documented definition of FIT was overlooked:

    Suggested fix: Return Fit unchanged in the Fit+Fit case:

    // Before:
    is Fit -> Fixed(with(density) { thisDimensionPx.toInt().toDp().value.toUInt() })
    
    // After:
    is Fit -> this

    This makes the existing aspectRatio Fit+Fit branch reachable. The resulting modifier chain (wrapContent + aspectRatio) correctly fills available parent space while preserving aspect ratio. Device testing confirms consistent sizing across all tested devices.

Additional context

Affected files:

  • ui/revenuecatui/src/main/kotlin/.../components/video/VideoComponentState.kt
  • ui/revenuecatui/src/main/kotlin/.../components/image/ImageComponentState.kt

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions