Skip to content

Improve ScaffoldMessenger/Scaffold/Snackbar docs #105406

@LostInDarkMath

Description

@LostInDarkMath

Steps to Reproduce

  1. Execute flutter run on the code sample (see below)
  2. Click on the navigate button
  3. Enter a text in the text field to open the keyboard
  4. Type something and click on the send IconButton

bug

Expected results:
The SnackBar should be attached to the Scaffold of Screen2, not to the Scaffold of the first screen. That would mean that the SnackBar is displayed in the foreground and therefore would be opaque.

Actual results:
bug_1

The SnackBar is attached to the first screen and therefore is transparent.

Code sample

Dependencies

keyboard_utils: ^1.3.4

My code (fully executable)

import 'dart:io';
import 'package:keyboard_utils/keyboard_listener.dart';
import 'package:keyboard_utils/keyboard_utils.dart';
import 'package:flutter/material.dart' hide KeyboardListener;

void main() => runApp(const MyApp());

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

  @override
  Widget build(BuildContext context) => const MaterialApp(home: MyHomePage());
}

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

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Title')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[const Text('You have pushed the button this many times:'),
            Text('$_counter', style: Theme.of(context).textTheme.headline4),
            ElevatedButton(
              onPressed: () {
                Navigator.of(context).push(PageRouteBuilder(
                  opaque: false, // push route with transparency
                  pageBuilder: (context, animation, secondaryAnimation) => const Screen2(),
                ));
              },
              child: const Text('navigate'),
            )
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => setState(() => _counter++),
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ),
    );
  }
}

class Screen2 extends StatefulWidget {
  const Screen2({Key? key}) : super(key: key);

  @override
  State<Screen2> createState() => _Screen2State();
}

class _Screen2State extends State<Screen2> {
  final _keyboardUtils = KeyboardUtils();
  late int _idKeyboardListener;
  final focusNode = FocusNode();

  bool isEmojiKeyboardVisible = false;
  bool isKeyboardVisible = false;
  double _keyboardHeight = 300;

  @override
  void initState() {
    super.initState();
    _idKeyboardListener = _keyboardUtils.add(
        listener: KeyboardListener(
          willHideKeyboard: () {
            if (isKeyboardVisible) {
              isKeyboardVisible = false;
              isEmojiKeyboardVisible = false;
            }

            setState(() {}); // show correct Icon in IconButton
          },
          willShowKeyboard: (double keyboardHeight) async {
            if (Platform.isAndroid) {
              _keyboardHeight = keyboardHeight + WidgetsBinding.instance.window.viewPadding.top / WidgetsBinding.instance.window.devicePixelRatio;
            } else {
              _keyboardHeight = keyboardHeight;
            }

            isKeyboardVisible = true;
            isEmojiKeyboardVisible = true;
            setState(() {});
          },
        )
    );
  }

  @override
  void dispose() {
    _keyboardUtils.unsubscribeListener(subscribingId: _idKeyboardListener);

    if (_keyboardUtils.canCallDispose()) {
      _keyboardUtils.dispose();
    }

    focusNode.dispose();
    super.dispose();
  }

  Future<void> onEmojiButtonPressed() async {
    if(isEmojiKeyboardVisible){
      if(isKeyboardVisible){
        FocusManager.instance.primaryFocus?.unfocus();
        isKeyboardVisible = false;
      } else {
        focusNode.unfocus();
        await Future<void>.delayed(const Duration(milliseconds: 1));
        if(!mounted) return;
        FocusScope.of(context).requestFocus(focusNode);
      }
    } else {
      assert(!isKeyboardVisible);
      setState(() {
        isEmojiKeyboardVisible = true;
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold( // wrapping with ScaffoldMessenger does NOT fix this bug
      backgroundColor: Colors.white.withOpacity(0.5),
      resizeToAvoidBottomInset: false,
      body: SafeArea(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.end,
          children: [
            Expanded(child: SizedBox(
              height: MediaQuery.of(context).size.height,
              width: MediaQuery.of(context).size.width,
              child: Column(
                children: [
                  Expanded(
                    child: Container(
                      height: 200,
                    ),
                  ),
                  Row(
                    children: [
                      IconButton(
                        icon: Icon(isKeyboardVisible || !isEmojiKeyboardVisible ? Icons.emoji_emotions_outlined : Icons.keyboard_rounded),
                        onPressed: onEmojiButtonPressed,
                      ),
                      Expanded(
                        child: TextField(
                          focusNode: focusNode,
                        ),
                      ),
                      IconButton(
                        icon: const Icon(Icons.send),
                        onPressed: () => ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('A snack!'))),
                      ),
                    ],
                  ),
                ],
              ),
            ),
            ),
            Offstage(
              offstage: !isEmojiKeyboardVisible,
              child: SizedBox(
                height: _keyboardHeight,
                child: Container(color: Colors.red),
              ),
            ),
          ],
        ),
      ),
    );
  }
}
Logs

There are no logs after installing the application.

[   +2 ms] Bundle processing done.
[ +131 ms] Updating files.
[        ] DevFS: Sync finished
[   +1 ms] Synced 0.0MB.
[   +2 ms] <- accept
[   +7 ms] Connected to _flutterView/0x7115282e20.
[+2780 ms] W/Gralloc3( 9582): mapper 3.x is not supported

flutter analyze gives

No issues found! (ran in 6.6s)

flutter doctor -v:

[√] Flutter (Channel stable, 3.0.1, on Microsoft Windows [Version 6.1.7601], locale de-DE)
    • Flutter version 3.0.1 at C:\flutter
    • Framework revision fb57da5f94 (2 weeks ago), 2022-05-19 15:50:29 -0700
    • Engine revision caaafc5604
    • Dart version 2.17.1
    • DevTools version 2.12.2

[√] Android toolchain - develop for Android devices (Android SDK version 31.0.0)
    • Android SDK at C:\Users\WILLI\AppData\Local\Android\Sdk
    • Platform android-31, build-tools 31.0.0
    • ANDROID_HOME = C:\Users\WILLI\AppData\Local\Android\Sdk
    • Java binary at: C:\Program Files\Android\Android Studio1\jre\bin\java
    • Java version OpenJDK Runtime Environment (build 11.0.12+7-b1504.28-7817840)
    • All Android licenses accepted.

[√] Chrome - develop for the web
    • Chrome at C:\Program Files (x86)\Google\Chrome\Application\chrome.exe

[√] Visual Studio - develop for Windows (Visual Studio Build Tools 2019 16.10.3)
    • Visual Studio at C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools
    • Visual Studio Build Tools 2019 version 16.10.31424.327
    • Windows 10 SDK version 10.0.19041.0

[√] Android Studio (version 2020.3)
    • Android Studio at C:\Program Files\Android\Android Studio
    • 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.11+9-b60-7590822)

[√] Android Studio (version 2021.2)
    • Android Studio at C:\Program Files\Android\Android Studio1
    • 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.12+7-b1504.28-7817840)

[√] VS Code (version 1.67.2)
    • VS Code at C:\Users\WILLI\AppData\Local\Programs\Microsoft VS Code
    • Flutter extension version 3.42.0

[√] Connected device (3 available)
    • Mi A2 Lite (mobile) • c1c1eca99806 • android-arm64  • Android 10 (API 29)
    • Windows (desktop)   • windows      • windows-x64    • Microsoft Windows [Version 6.1.7601]
    • Chrome (web)        • chrome       • web-javascript • Google Chrome 102.0.5005.63

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

• No issues found!

Metadata

Metadata

Assignees

Labels

P3Issues that are less important to the Flutter projectd: api docsIssues with https://api.flutter.dev/d: stackoverflowGood question for Stack Overflowf: material designflutter/packages/flutter/material repository.found in release: 3.0Found to occur in 3.0found in release: 3.1Found to occur in 3.1frameworkflutter/packages/flutter repository. See also f: labels.has reproducible stepsThe issue has been confirmed reproducible and is ready to work onr: fixedIssue is closed as already fixed in a newer version

Type

No type

Projects

Status

Done (PR merged)

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions