Skip to content

[IOS] SafeArea maintainBottomViewPadding is not respected when keyboard viewInsets are animated #97609

Closed

Description

@joellurcook

This issue has been introduced as a result of the changes to the flutter engine (iOS) made in PR 29281 (flutter-team-archive/engine#29281). Those changes are in use on the current master and dev branch of this project.

It is now possible for MediaqQuery viewInsets.bottom to be >0 AND for padding.bottom >0 during the keyboard transition. This results in the maintainBottomViewPadding property being ignored within SafeArea build() at various times during the animation.

Steps to Reproduce

  1. Execute flutter run on the code sample
  2. Tap on the TextField to reveal the software keyboard
  3. Tap 'done' to remove the software keyboard

Expected results:
The keyboard should overlay the app and not affect the layout of any widgets. In particular, the SafeArea padding at the bottom of the orange widget should remain unchanged regardless of the viewInsets.bottom value created by the keyboard since maintainBottomViewPadding is set to true.

Actual results:
During animation, the orange widget 'jumps' downward. It is laid out differently during the animation indicating that the bottom viewPadding is not always respected, despite maintainBottomViewPadding being set to true.

Code sample
import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatelessWidget {
  const MyHomePage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      resizeToAvoidBottomInset: false,
      appBar: AppBar(
        title: const Text('SafeArea Demo'),
      ),
      body: Center(
        child: Column(
          children: <Widget>[
            const Expanded(
                child: Padding(
              padding: EdgeInsets.only(top: 20.0),
              child: Text("This scaffold has resizeToAvoidBottomInset=false"),
            )),
            SafeArea(
              maintainBottomViewPadding: true,
              child: Container(
                color: const Color.fromARGB(255, 255, 72, 0),
                height: 500,
                child: Column(
                  children: const [
                    Text(
                      'This container is surrounded by a SafeArea \n with maintainBottomViewPadding=true',
                      style: TextStyle(fontWeight: FontWeight.bold),
                    ),
                    SizedBox(width: 200, child: TextField()),
                  ],
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}
Logs
No relevant runtime logs
No issues found! (ran in 1.8s)
[✓] Flutter (Channel master, 2.10.0-1.0.pre.373, on macOS 12.1 21C52 darwin-x64, locale en-GB)
    • Flutter version 2.10.0-1.0.pre.373 at /Users/joellurcook/development/flutter
    • Upstream repository https://github.com/flutter/flutter.git
    • Framework revision a22cf5d6b9 (28 minutes ago), 2022-02-02 09:16:05 +0100
    • Engine revision 0bd729b972
    • Dart version 2.17.0 (build 2.17.0-74.0.dev)
    • DevTools version 2.10.0

[!] Android toolchain - develop for Android devices (Android SDK version 31.0.0)
    • Android SDK at /Users/joellurcook/Library/Android/sdk
    ✗ cmdline-tools component is missing
      Run `path/to/sdkmanager --install "cmdline-tools;latest"`
      See https://developer.android.com/studio/command-line for more details.
    ✗ Android license status unknown.
      Run `flutter doctor --android-licenses` to accept the SDK licenses.
      See https://flutter.dev/docs/get-started/install/macos#android-setup for more details.

[✓] Xcode - develop for iOS and macOS (Xcode 13.2.1)
    • Xcode at /Applications/Xcode.app/Contents/Developer
    • CocoaPods version 1.10.1

[✓] Chrome - develop for the web
    • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome

[✓] Android Studio (version 4.2)
    • Android Studio at /Applications/Android Studio.app/Contents
    • Flutter plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/9212-flutter
    • Dart plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/6351-dart
    • Java version OpenJDK Runtime Environment (build 11.0.8+10-b944.6916264)

[✓] Android Studio (version 3.5)
    • Android Studio at /Applications/Android Studio 2.app/Contents
    • Flutter plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/9212-flutter
    • Dart plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/6351-dart
    • Java version OpenJDK Runtime Environment (build 1.8.0_202-release-1483-b49-5587405)

[✓] IntelliJ IDEA Community Edition (version 2021.1.3)
    • IntelliJ at /Applications/IntelliJ IDEA CE.app
    • Flutter plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/9212-flutter
    • Dart plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/6351-dart

[✓] VS Code (version 1.63.2)
    • VS Code at /Applications/Visual Studio Code.app/Contents
    • Flutter extension version 3.34.0

[✓] Connected device (2 available)
    • iPhone 13 (mobile) • 63C9A42F-6841-4F83-8922-DCAE0C900EBE • ios            • com.apple.CoreSimulator.SimRuntime.iOS-15-2 (simulator)
    • Chrome (web)       • chrome                               • web-javascript • Google Chrome 97.0.4692.99

[✓] HTTP Host Availability
    • All required HTTP hosts are available

! Doctor found issues in 1 category.
Simulator.Screen.Recording.-.iPhone.13.-.2022-02-01.at.21.01.35.mp4

Activity

  1. joellurcook commented on Feb 2, 2022

    @joellurcook
    ContributorAuthor

    The offending line in SafeArea is:

    if (data.padding.bottom == 0.0 &&
            data.viewInsets.bottom != 0.0 &&
            maintainBottomViewPadding)
          padding = padding.copyWith(bottom: data.viewPadding.bottom);

    I shall raise a PR with a more suitable condition

  2. maheshj01 commented on Feb 2, 2022

    @maheshj01
    Member

    Hi @joellurcook, Thanks for filing the issue. I am able to reproduce the issue on stable and the master channel and works fine on stable, This seems to be a regression affecting IOS platforms and works fine on Android.

    IOS(stable) ✅ IOS (master)❌ Android(stable/master) ✅
    Screen.Recording.2022-02-02.at.3.10.24.PM.mov
    Screen.Recording.2022-02-02.at.3.00.26.PM.mov
    Screen.Recording.2022-02-02.at.3.04.53.PM.mov

    ✅ : No Issue ❌: Issue reproduced

    flutter doctor -v
    [✓] Flutter (Channel stable, 2.8.1, on macOS 12.1 21C52 darwin-arm, locale en-GB)
        • Flutter version 2.8.1 at /Users/mahesh/Documents/flutter
        • Upstream repository https://github.com/flutter/flutter.git
        • Framework revision 77d935af4d (7 weeks ago), 2021-12-16 08:37:33 -0800
        • Engine revision 890a5fca2e
        • Dart version 2.15.1
    
    [✓] Android toolchain - develop for Android devices (Android SDK version 31.0.0)
        • Android SDK at /Users/mahesh/Library/Android/sdk
        • Platform android-31, build-tools 31.0.0
        • ANDROID_HOME = /Users/mahesh/Library/Android/sdk
        • Java binary at: /Applications/Android Studio.app/Contents/jre/jdk/Contents/Home/bin/java
        • Java version OpenJDK Runtime Environment (build 11.0.8+10-b944.6916264)
        • All Android licenses accepted.
    
    [✓] Xcode - develop for iOS and macOS (Xcode 13.2.1)
        • Xcode at /Applications/Xcode.app/Contents/Developer
        • CocoaPods version 1.10.2
    
    [✓] Chrome - develop for the web
        • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome
    
    [✓] Android Studio (version 4.2)
        • Android Studio at /Applications/Android Studio.app/Contents
        • Flutter plugin can be installed from:
          🔨 https://plugins.jetbrains.com/plugin/9212-flutter
        • Dart plugin can be installed from:
          🔨 https://plugins.jetbrains.com/plugin/6351-dart
        • Java version OpenJDK Runtime Environment (build 11.0.8+10-b944.6916264)
    
    [✓] IntelliJ IDEA Community Edition (version 2021.2.1)
        • IntelliJ at /Applications/IntelliJ IDEA CE.app
        • Flutter plugin version 60.1.4
        • Dart plugin version 212.5080.8
    
    [✓] VS Code (version 1.63.2)
        • VS Code at /Applications/Visual Studio Code.app/Contents
        • Flutter extension version 3.34.0
    
    [✓] Connected device (4 available)
        • Redmi K20 Pro (mobile) • 192.168.1.2:5555                     • android-arm64  • Android 11 (API 30)
        • iPhone 12 Pro (mobile) • 14F40EC6-3B73-469A-A1C0-8CF0B152290F • ios            •
          com.apple.CoreSimulator.SimRuntime.iOS-14-5 (simulator)
        • macOS (desktop)        • macos                                • darwin-arm64   • macOS 12.1 21C52 darwin-arm
        • Chrome (web)           • chrome                               • web-javascript • Google Chrome 97.0.4692.99
    
    • No issues found!
    
    [✓] Flutter (Channel master, 2.10.0-1.0.pre.371, on macOS 12.1 21C52 darwin-arm, locale en-GB)
        • Flutter version 2.10.0-1.0.pre.371 at /Users/mahesh/Documents/flutter_master
        • Upstream repository https://github.com/flutter/flutter.git
        • Framework revision d1285eef1b (4 hours ago), 2022-02-02 00:50:20 -0500
        • Engine revision 5bbdb7a6f7
        • Dart version 2.17.0 (build 2.17.0-74.0.dev)
        • DevTools version 2.10.0
    
    [✓] Android toolchain - develop for Android devices (Android SDK version 31.0.0)
        • Android SDK at /Users/mahesh/Library/Android/sdk
        • Platform android-31, build-tools 31.0.0
        • ANDROID_HOME = /Users/mahesh/Library/Android/sdk
        • Java binary at: /Applications/Android Studio.app/Contents/jre/jdk/Contents/Home/bin/java
        • Java version OpenJDK Runtime Environment (build 11.0.8+10-b944.6916264)
        • All Android licenses accepted.
    
    [✓] Xcode - develop for iOS and macOS (Xcode 13.2.1)
        • Xcode at /Applications/Xcode.app/Contents/Developer
        • CocoaPods version 1.10.2
    
    [✓] Chrome - develop for the web
        • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome
    
    [✓] Android Studio (version 4.2)
        • Android Studio at /Applications/Android Studio.app/Contents
        • Flutter plugin can be installed from:
          🔨 https://plugins.jetbrains.com/plugin/9212-flutter
        • Dart plugin can be installed from:
          🔨 https://plugins.jetbrains.com/plugin/6351-dart
        • Java version OpenJDK Runtime Environment (build 11.0.8+10-b944.6916264)
    
    [✓] IntelliJ IDEA Community Edition (version 2021.2.1)
        • IntelliJ at /Applications/IntelliJ IDEA CE.app
        • Flutter plugin version 60.1.4
        • Dart plugin version 212.5080.8
    
    [✓] VS Code (version 1.63.2)
        • VS Code at /Applications/Visual Studio Code.app/Contents
        • Flutter extension version 3.34.0
    
    [✓] Connected device (4 available)
        • Redmi K20 Pro (mobile) • 192.168.1.2:5555                     • android-arm64  • Android 11 (API 30)
        • iPhone 12 Pro (mobile) • 14F40EC6-3B73-469A-A1C0-8CF0B152290F • ios            •
          com.apple.CoreSimulator.SimRuntime.iOS-14-5 (simulator)
        • macOS (desktop)        • macos                                • darwin-arm64   • macOS 12.1 21C52 darwin-arm
        • Chrome (web)           • chrome                               • web-javascript • Google Chrome 97.0.4692.99
    
    [✓] HTTP Host Availability
        • All required HTTP hosts are available
    
    • No issues found!
    
  3. added
    a: layoutSystemChrome and Framework's Layout Issues
    frameworkflutter/packages/flutter repository. See also f: labels.
    has reproducible stepsThe issue has been confirmed reproducible and is ready to work on
    platform-iosiOS applications specifically
    and removed
    in triagePresently being triaged by the triage team
    on Feb 2, 2022
  4. changed the title [-]SafeArea maintainBottomViewPadding is not respected when keyboard viewInsets are animated[/-] [+][IOS] SafeArea maintainBottomViewPadding is not respected when keyboard viewInsets are animated[/+] on Feb 2, 2022
  5. joellurcook commented on Feb 2, 2022

    @joellurcook
    ContributorAuthor

    Hi @maheshmnj

    Thanks for confirming. I notice your Android video shows software back buttons. This would create a MediaQuery viewInsets.bottom, but not a viewPadding.bottom, which is required to see the problem. You would need an android device with a notch at the bottom and hardware buttons to replicate the issue. Unfortunately adding a notch to my AVD (and removing the software buttons) does not result in flutter reporting any bottom inset/padding - I'm not sure if this is another Flutter bug or an issue with emulated notches.

    I suspect that the issue would occur on Android if you could find such a device. I believe viewInset animation for software keyboard has been available for Android for some time, so the bug may be quite an old one, just very hard to see before being introduced to iOS.

  6. added
    engineflutter/engine related. See also e: labels.
    on Feb 2, 2022
  7. HansMuller commented on Feb 2, 2022

    @HansMuller
    Contributor
  8. jmagman commented on Feb 2, 2022

    @jmagman
    Member
  9. gaaclarke commented on Feb 2, 2022

    @gaaclarke
    Member

    I've reviewed everything again.

    The sample code makes sense. The desired behavior is that the app won't shrink when the keyboard is presented and that the widget is fixed to the bottom of the screen even when the keyboard is presented. In practice that would put you at the risk of having a Text widget that is obscured by the keyboard. But if the orientation is locked and the widget is high enough it could be a valid use case.

    The engine PR is correct, meaning I don't think it's doing something incorrect that is causing the visual glitch. The engine PR is modifying the view inset but the reproduction code is explicitly saying it doesn't care about the view inset when laying out the widget. This validates the OP's suggestion that the fix may be in the framework. I'm not that familiar with that code. I'll read it over and the OPs suggestion to see if that makes sense to me.

    Let me know if you think I'm missing something.

  10. added
    P2Important issues not at the top of the work list
    on Feb 2, 2022
  11. luckysmg commented on Feb 3, 2022

    @luckysmg
    Contributor

    I will take a look,both the engine code and framework code.
    @gaaclarke @jmagman @joellurcook

  12. luckysmg commented on Feb 3, 2022

    @luckysmg
    Contributor

    I wrote some debug code in sample code:

     @override
      Widget build(BuildContext context) {
        final MediaQueryData data = MediaQuery.of(context);
        print("viewpadding:${data.viewPadding.bottom}");
        print("padding:${data.padding.bottom}");
        print("inset:${data.viewInsets.bottom}");
        print("====================================");
    
        return Scaffold(
          resizeToAvoidBottomInset: false,
          appBar: AppBar(
            title: const Text('SafeArea Demo'),
          ),
        ............

    And When I tap the textField,the keyboard animation begin,and I get the log below:

    Debug Logs
    flutter: viewpadding:34.0
    flutter: padding:31.666062079370022
    flutter: inset:2.333937920629978
    flutter: ====================================
    flutter: viewpadding:34.0
    flutter: padding:10.885224461555481
    flutter: inset:23.11477553844452
    flutter: ====================================
    flutter: viewpadding:34.0
    flutter: padding:0.0
    flutter: inset:55.99079418182373
    flutter: ====================================
    flutter: viewpadding:34.0
    flutter: padding:0.0
    flutter: inset:93.14588356018066
    flutter: ====================================
    flutter: viewpadding:34.0
    flutter: padding:0.0
    flutter: inset:130.08398866653442
    flutter: ====================================
    flutter: viewpadding:34.0
    flutter: padding:0.0
    flutter: inset:164.37680625915527
    flutter: ====================================
    flutter: viewpadding:34.0
    flutter: padding:0.0
    flutter: inset:194.8385181427002
    flutter: ====================================
    flutter: viewpadding:34.0
    flutter: padding:0.0
    flutter: inset:221.123685836792
    flutter: ====================================
    flutter: viewpadding:34.0
    flutter: padding:0.0
    flutter: inset:243.33586978912354
    flutter: ====================================
    flutter: viewpadding:34.0
    flutter: padding:0.0
    flutter: inset:261.9928722381592
    flutter: ====================================
    flutter: viewpadding:34.0
    flutter: padding:0.0
    flutter: inset:276.981219291687
    flutter: ====================================
    flutter: viewpadding:34.0
    flutter: padding:0.0
    flutter: inset:289.2602319717407
    flutter: ====================================
    flutter: viewpadding:34.0
    flutter: padding:0.0
    flutter: inset:299.18000507354736
    flutter: ====================================
    flutter: viewpadding:34.0
    flutter: padding:0.0
    flutter: inset:307.1148262023926
    flutter: ====================================
    flutter: viewpadding:34.0
    flutter: padding:0.0
    flutter: inset:313.4111051559448
    flutter: ====================================
    flutter: viewpadding:34.0
    flutter: padding:0.0
    flutter: inset:318.3926010131836
    flutter: ====================================
    flutter: viewpadding:34.0
    flutter: padding:0.0
    flutter: inset:321.42823791503906
    flutter: ====================================
    flutter: viewpadding:34.0
    flutter: padding:0.0
    flutter: inset:323.9633159637451
    flutter: ====================================
    flutter: viewpadding:34.0
    flutter: padding:0.0
    flutter: inset:327.80306339263916
    flutter: ====================================
    flutter: viewpadding:34.0
    flutter: padding:0.0
    flutter: inset:331.131817817688
    flutter: ====================================
    flutter: viewpadding:34.0
    flutter: padding:0.0
    flutter: inset:332.2611894607544
    flutter: ====================================
    flutter: viewpadding:34.0
    flutter: padding:0.0
    flutter: inset:333.7997159957886
    flutter: ====================================
    flutter: viewpadding:34.0
    flutter: padding:0.0
    flutter: inset:334.3212833404541
    flutter: ====================================
    flutter: viewpadding:34.0
    flutter: padding:0.0
    flutter: inset:334.7112522125244
    flutter: ====================================
    flutter: viewpadding:34.0
    flutter: padding:0.0
    flutter: inset:335.01712703704834
    flutter: ====================================
    flutter: viewpadding:34.0
    flutter: padding:0.0
    flutter: inset:335.25018310546875
    flutter: ====================================
    flutter: viewpadding:34.0
    flutter: padding:0.0
    flutter: inset:335.42890548706055
    flutter: ====================================
    flutter: viewpadding:34.0
    flutter: padding:0.0
    flutter: inset:336.0
    flutter: ====================================
    

    We can see from log that.
    The viewPadding is always 34.0(which is the ios safeArea height).
    The padding is from 34 to 0

    And Here is the code in SafeArea widget's build(context) method

    @override
      Widget build(BuildContext context) {
        assert(debugCheckHasMediaQuery(context));
        final MediaQueryData data = MediaQuery.of(context);
        EdgeInsets padding = data.padding;
        // Bottom padding has been consumed - i.e. by the keyboard
        if (data.padding.bottom == 0.0 && data.viewInsets.bottom != 0.0 && maintainBottomViewPadding)
          padding = padding.copyWith(bottom: data.viewPadding.bottom);
    
        return Padding(
          padding: EdgeInsets.only(
            left: math.max(left ? padding.left : 0.0, minimum.left),
            top: math.max(top ? padding.top : 0.0, minimum.top),
            right: math.max(right ? padding.right : 0.0, minimum.right),
            bottom: math.max(bottom ? padding.bottom : 0.0, minimum.bottom),
          ),
    ..........

    We can see the code here:

     // Bottom padding has been consumed - i.e. by the keyboard
        if (data.padding.bottom == 0.0 && data.viewInsets.bottom != 0.0 && maintainBottomViewPadding)
          padding = padding.copyWith(bottom: data.viewPadding.bottom);

    When animation is running,we can pick the first log data as example:

    flutter: viewpadding:34.0
    flutter: padding:31.666062079370022
    flutter: inset:2.333937920629978
    

    This data will make the condition:data.padding.bottom == 0.0 && data.viewInsets.bottom != 0.0 && maintainBottomViewPadding to false.

    So the padding = padding.copyWith(bottom: data.viewPadding.bottom); will not execute....

  13. joellurcook commented on Feb 3, 2022

    @joellurcook
    ContributorAuthor

    I've reviewed everything again.

    The sample code makes sense. The desired behavior is that the app won't shrink when the keyboard is presented and that the widget is fixed to the bottom of the screen even when the keyboard is presented. In practice that would put you at the risk of having a Text widget that is obscured by the keyboard. But if the orientation is locked and the widget is high enough it could be a valid use case.

    The engine PR is correct, meaning I don't think it's doing something incorrect that is causing the visual glitch. The engine PR is modifying the view inset but the reproduction code is explicitly saying it doesn't care about the view inset when laying out the widget. This validates the OP's suggestion that the fix may be in the framework. I'm not that familiar with that code. I'll read it over and the OPs suggestion to see if that makes sense to me.

    Let me know if you think I'm missing something.

    To give an example use case... my app brings up popups from the bottom of the screen, of variable size, all of which have a SafeArea at the bottom. It also brings up the keyboard sometimes and disables the app UI when it does so. Because the UI is anyway disabled I want the keyboard to overlay - obscuring widgets is fine. Unles I specify maintainBottomViewPadding, my popup SafeAreas will reduce to zero when the keyboard comes up, causing the popup to change height. This bug makes my popups behave just like the example app.

  14. joellurcook commented on Feb 3, 2022

    @joellurcook
    ContributorAuthor

    Hi @luckysmg
    Yes, exactly that - see my PR with a suggested change to the condition you have mentioned.

  15. luckysmg commented on Feb 3, 2022

    @luckysmg
    Contributor

    Oh I didn't see that. Yes,that 's it! 😀 @joellurcook

  16. added
    r: fixedIssue is closed as already fixed in a newer version
    on Mar 3, 2022
  17. github-actions commented on Mar 17, 2022

    @github-actions

    This thread has been automatically locked since there has not been any recent activity after it was closed. If you are still experiencing a similar issue, please open a new bug, including the output of flutter doctor -v and a minimal reproduction of the issue.

  18. locked as resolved and limited conversation to collaborators on Mar 17, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Important issues not at the top of the work lista: layoutSystemChrome and Framework's Layout Issuesc: regressionIt was better in the past than it is nowengineflutter/engine related. See also e: labels.found in release: 2.10Found to occur in 2.10found in release: 2.8Found to occur in 2.8frameworkflutter/packages/flutter repository. See also f: labels.has reproducible stepsThe issue has been confirmed reproducible and is ready to work onplatform-iosiOS applications specificallyr: fixedIssue is closed as already fixed in a newer versionwaiting for PR to land (fixed)A fix is in flight

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions