dotnet / dotnet/maui

[Android] SafeAreaEdges stops working when `Scale < 1` is set on Grid and then animated

Open
#33,544 7 comments 0 reactions 0 assignees View on GitHub
area-safearea platform/android s/triaged s/verified t/bug
Dominant language
C#
Stars
23.3k
Forks
2k
Avg merge
1d 15h
Merged PRs (30d)
290

Description

### Description

When a Grid has `SafeAreaEdges` enabled (e.g., `SafeAreaRegions.Container`) and is initialized with a `Scale` value other than 1 (e.g., `Scale = 0.8` for an entry animation), the `SafeAreaEdges` configuration stops working correctly on Android.

Unlike on iOS, where the Safe Area constraints are respected regardless of the view's scale transformation, Android seems to ignore the safe area insets once scaling is applied. When the element is animated back to `Scale = 1`, the content ends up positioned **under** the camera notch/status bar.

**Visual Symptoms on Android:**

* Content expands UNDER the camera notch/cutout (overlapping) when scaling up.
* The `SafeAreaEdges` constraints seem to get "lost" during the scale transformation.

**Root Cause Analysis:**
It appears Android fails to invalidate the layout or recalculate insets after a `Scale` transformation.

### Steps to Reproduce

1. Setup a Grid with `SafeAreaEdges="Container"`, `Opacity="0"` and **`Scale="0.8"`** (simulating an entrance state).
2. In `OnAppearing`, animate both properties:
```csharp
await Task.WhenAll(
ContentLayer.FadeToAsync(1, 200),
ContentLayer.ScaleToAsync(1, 200)
);

```

3. Observe that on Android, the content now overlaps the Status Bar/Notch.

### Link to public reproduction project repository

https://github.com/paulober/maui-android-safearea-opacity-bug

### Version with bug

10.0.20

### Affected platforms

Android

### Did you find any workaround?

**Yes, but it is extremely "hacky" and requires platform-specific code.**

We found that we must manually force a layout invalidation **after** the animation finishes. However, simply re-setting `SafeAreaEdges` to the desired value (e.g., `Container`) does **not** work because the internal property setter probably checks for equality because then it skips the update if the value hasn't changed.

To make this work on Android, we have to "toggle" the value to something else to force the system to react.

**The "Dirty" Workaround:**

```csharp
await ContentLayer.ScaleToAsync(1, 200);

#if ANDROID
// HACK: Force layout invalidation on Android.
// We must change the value to something DIFFERENT than the current state
// to bypass the property equality check and trigger an update.
ContentLayer.SafeAreaEdges = new SafeAreaEdges(_initialRegions == SafeAreaRegions.Container
? SafeAreaRegions.SoftInput : SafeAreaRegions.Container);
ContentLayer.SafeAreaEdges = new SafeAreaEdges(_initialRegions);
#endif

```

**Why this is problematic:**

1. **iOS behavior:** On iOS, we want the correct `SafeAreaRegions.Container` set right from the start (initialization).
2. **Android behavior:** On Android, keeping that correct value causes the bug. We are forced to write platform-specific logic to toggle the property back and forth just to wake up the layout engine.

We shouldn't need to toggle properties to dummy values to get the Safe Area to respect the notch after a simple Scale animation.

Contributor guide

Open the contributing guide

Research direction

Start with the public reproduction project and its OnAppearing handler, then trace the Grid's SafeAreaEdges, Scale, and ScaleToAsync behavior during the animation. Compare Android with iOS and verify that the final layout respects the camera notch and status bar without toggling SafeAreaEdges manually.

Written by the indexing model from the issue text.

Assessment

Tech stack
android, csharp
Domain
mobile
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
42/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.