Skip to content

Fix Android paywall ignoring bottom safe area in fullscreen Unity host#926

Open
tonidero wants to merge 4 commits into
mainfrom
fix/android-paywall-safe-area
Open

Fix Android paywall ignoring bottom safe area in fullscreen Unity host#926
tonidero wants to merge 4 commits into
mainfrom
fix/android-paywall-safe-area

Conversation

@tonidero
Copy link
Copy Markdown
Contributor

@tonidero tonidero commented May 12, 2026

Summary

Bug
The Unity Android paywall captured system-bar insets from the host activity's decor view. When the host hides system bars (e.g. Unity with unity.launch-fullscreen=True), the activity's WindowInsetsCompat.getInsets(navigationBars()) returns zero. The dialog opens its own window which still shows the nav bar, so the Compose paywall received a zero bottom inset and drew behind the visible nav bar.

Fix
Read insets from the dialog window itself via an OnApplyWindowInsetsListener on the dialog's content container. The dialog window reports the bars that are actually visible to the dialog, regardless of what the host activity is doing.

Flag sequencing
FLAG_LAYOUT_NO_LIMITS is now applied by the listener after the first inset dispatch (previously it was set immediately after dialog.show()). This avoids mutating window flags during the very first inset traversal.

Cache (defensive)
The listener caches the last non-zero typed insets and re-injects them if a dispatch ever arrives with zero values. This is defensive: on API 30+, typed insets (Type.statusBars() and Type.navigationBars()) are descriptive and keep being dispatched with real values across rotation, fold/unfold, and multi-window resize regardless of FLAG_LAYOUT_NO_LIMITS. The flag only zeroes the legacy getSystemWindowInsets() surface, which this code does not read. The cache covers older APIs and edge dispatches that might transiently carry zero typed values.

Screenshots

Version Before After
36 image image
34 image image

Note how in Android 34 the dialog doesn't show full screen anymore. This is because we don't apply the FLAG_LAYOUT_NO_LIMITS flag unless we discover any insets. I think it makes more sense since the default is to not go full screen in Android < 35, as opposed to before, where we were going full screen always. Lmk if you think otherwise.

…ullscreen

The Unity paywall presenter captured system-bar insets from the host activity
before showing the dialog. When the host (e.g. Unity with
unity.launch-fullscreen=True) hides system bars, getInsets(navigationBars())
returns zero. The dialog opens its own window which still shows the nav bar,
so the Compose paywall received a zero bottom inset and drew behind the
visible navigation bar.

Read insets from the dialog window itself via a stateful
OnApplyWindowInsetsListener that caches the first non-zero status/nav bar
values and re-injects them after FLAG_LAYOUT_NO_LIMITS is applied. The flag
is now applied by the listener once real insets have been captured, avoiding
the race where the previous flow set the flag immediately after dialog.show()
and made the system report zero insets thereafter.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@tonidero tonidero added the pr:fix A bug fix label May 12, 2026
@tonidero tonidero marked this pull request as ready for review May 12, 2026 08:28
@tonidero tonidero requested a review from a team as a code owner May 12, 2026 08:28
Copy link
Copy Markdown
Member

@ajpallares ajpallares left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this looks good, but I will leave approval to someone with more knowledge in the Android layout system. I have one question though

Comment on lines +375 to +378
* Wraps the PaywallView in a container whose insets listener captures the dialog
* window's real system-bar insets on first dispatch, then triggers FLAG_LAYOUT_NO_LIMITS.
* Once the flag is applied, subsequent dispatches arrive with zero insets — the listener
* re-injects the cached values so the PaywallView's Compose content stays padded.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible for this to change at runtime (e.g. if the device rotates) and have the cached values become invalid?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question! and I checked and this actually reapplies the insets, I believe because on rotation, the entire activity and dialog is recreated. So this shouldn't be a problem.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is that always the case in Unity? What if the Activity has android:configChanges=["orientation"]? (Although I believe that is ignored when targeting Android 16.)

@tonidero tonidero requested a review from JayShortway May 21, 2026 21:43
int resourceId = activity.getResources().getIdentifier(resourceName, "dimen", "android");
if (resourceId > 0) {
return activity.getResources().getDimensionPixelSize(resourceId);
if (Build.VERSION.SDK_INT < 35) { // Build.VERSION_CODES.VANILLA_ICE_CREAM
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd pick either the named constant (VANILLA_ICE_CREAM) or the integer, not both 😛 For instance:

Suggested change
if (Build.VERSION.SDK_INT < 35) { // Build.VERSION_CODES.VANILLA_ICE_CREAM
if (Build.VERSION.SDK_INT < 35) {

Comment on lines +375 to +378
* Wraps the PaywallView in a container whose insets listener captures the dialog
* window's real system-bar insets on first dispatch, then triggers FLAG_LAYOUT_NO_LIMITS.
* Once the flag is applied, subsequent dispatches arrive with zero insets — the listener
* re-injects the cached values so the PaywallView's Compose content stays padded.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is that always the case in Unity? What if the Activity has android:configChanges=["orientation"]? (Although I believe that is ignored when targeting Android 16.)

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

Labels

pr:fix A bug fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants