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.
-
Environment
- Platform: Android
- SDK version: Latest (main branch)
- OS version: Android 14-15 (tested across multiple)
- Android Studio version: Latest stable
- How widespread is the issue: 100% of devices. Every device with a different screen density renders FIT-sized media at a different DP size.
-
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.
-
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.
-
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
Describe the bug
Video and image components configured with
Fitsizing 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.Environment
Debug logs that reproduce the issue
Added logging to
VideoComponentState.adjustDimensionandTextureVideoView.applySizing. A 720x720px video with Fit+Fit sizing produces:Note: The Pixel Fold and Pixel 7 Pro are both 411dp wide, yet the component renders at 274dp vs 206dp.
Steps to reproduce, with a description of expected vs. actual behavior
Steps:
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.Other information
Root cause: In both
VideoComponentState.ktandImageComponentState.kt, theadjustDimensionmethod handles Fit+Fit by converting media pixel dimensions to DP:This converts
Fitinto aFixedconstraint with a density-dependent value.Both state classes already have an
aspectRatioproperty with a Fit+Fit branch that correctly returnsAspectRatio(ratio = mediaAspectRatio, matchHeightConstraintsFirst = true). However, this code is unreachable becauseadjustDimensionconvertsFittoFixedbeforeaspectRatiois 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:
ImageComponentStateand override functionality #2012 (Jan 2025): IntroducedadjustForImage, converting allFittoFixed(px.toDp())ImageComponentViewsize when axes are Fit and Fixed #2024 (Jan 2025): Fixed Fit+Fixed scaling, kept Fit+Fit asFixed(nativePixels.toDp())adjustDimension, addedaspectRatiofor Fit+Fill. The Fit+Fit aspect ratio branch was added but became dead codeVideoComponentStateSuggested fix: Return
Fitunchanged in the Fit+Fit case:This makes the existing
aspectRatioFit+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.ktui/revenuecatui/src/main/kotlin/.../components/image/ImageComponentState.kt