59
votes

I have a BottomNavigationBar at the upper-most level of my app. I want to detect keyboard open/close basically anywhere in the app/subtree, so i can show/hide the BottomNavigationBar whenever the keyboard is visible.

This is a general issue and may not be directly related to the BottomNavigationBar. In other words, abstract from the BottomNavigationBar :-)

12

12 Answers

67
votes

To check for keyboard visibility, just check for the viewInsets property anywhere in the widget tree. The keyboard is hidden when viewInsets.bottom is equal to zero.

You can check for the viewInsets with MediaQuery like:

MediaQuery.of(context).viewInsets.bottom

Hope that helped!

37
votes

I just created a flutter plugin to notify about keyboard open/close events. Works both on Android and iOS. Hope this helps

https://github.com/adee42/flutter_keyboard_visibility


import 'package:keyboard_visibility/keyboard_visibility.dart';

@override
void initState() {
  super.initState();

  KeyboardVisibilityNotification().addNewListener(
    onChange: (bool visible) {
      print(visible);
    },
  );
}
33
votes

KeyboardVisibilityBuilder

If you don't want to rely on external libs, you can listen for keyboard show/hide events using WidgetsBindingObserver mixin. I prepared a lightweight KeyboardVisibilityBuilder widget that handles the behavior for you. The usage is quite similar to AnimatedBuilder:

return KeyboardVisibilityBuilder(
  builder: (context, child, isKeyboardVisible) {
    if (isKeyboardVisible) {
      // build layout for visible keyboard
    } else {
      // build layout for invisible keyboard
    }
  },
  child: child, // this widget goes to the builder's child property. Added for better performance.
);

KeyboardVisibilityBuilder implementation:

/// Calls `builder` on keyboard close/open.
/// https://stackoverflow.com/a/63241409/1321917
class KeyboardVisibilityBuilder extends StatefulWidget {
  final Widget? child;
  final Widget Function(
    BuildContext context,
    Widget? child,
    bool isKeyboardVisible,
  ) builder;

  const KeyboardVisibilityBuilder({
    Key? key,
    this.child,
    required this.builder,
  }) : super(key: key);

  @override
  _KeyboardVisibilityBuilderState createState() =>
      _KeyboardVisibilityBuilderState();
}

class _KeyboardVisibilityBuilderState extends State<KeyboardVisibilityBuilder>
    with WidgetsBindingObserver {
  var _isKeyboardVisible = false;

  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance!.addObserver(this);
  }

  @override
  void dispose() {
    WidgetsBinding.instance!.removeObserver(this);
    super.dispose();
  }

  @override
  void didChangeMetrics() {
    final bottomInset = WidgetsBinding.instance!.window.viewInsets.bottom;
    final newValue = bottomInset > 0.0;
    if (newValue != _isKeyboardVisible) {
      setState(() {
        _isKeyboardVisible = newValue;
      });
    }
  }

  @override
  Widget build(BuildContext context) => widget.builder(
        context,
        widget.child,
        _isKeyboardVisible,
      );
}

20
votes

You can use the keyboard_visibility package to do this effectively, I've used it and it works like charm.

To install

dependencies:
  keyboard_visibility: ^0.5.2

Usage

import 'package:keyboard_visibility/keyboard_visibility.dart';

@protected
void initState() {
  super.initState();

  KeyboardVisibilityNotification().addNewListener(
    onChange: (bool visible) {
      print(visible);
    },
  );
}

It also supports listeners like show/hide.

here is the link

11
votes

You can use WidgetsBinding.instance.window.viewInsets.bottom if it's value greater than 0.0 then the Keyboard is visible.

    if(WidgetsBinding.instance.window.viewInsets.bottom > 0.0)
    {
       //Keyboard is visible.
    }
    else
    {
       //Keyboard is not visible.
    }
9
votes

In your StatefullWidget create a variable

bool _keyboardVisible = false;

Then initialize that variable on build widget;

@override
Widget build(BuildContext context) {
    _keyboardVisible = MediaQuery.of(context).viewInsets.bottom != 0;
   return child;
}
6
votes

You can use MediaQuery.of(context).viewInsets.bottom. Just look at the documentation below.

/// The parts of the display that are completely obscured by system UI, /// typically by the device's keyboard. /// /// When a mobile device's keyboard is visible viewInsets.bottom /// corresponds to the top of the keyboard. /// /// This value is independent of the [padding]: both values are /// measured from the edges of the [MediaQuery] widget's bounds. The /// bounds of the top level MediaQuery created by [WidgetsApp] are the /// same as the window (often the mobile device screen) that contains the app. ///
/// See also: /// /// * [MediaQueryData], which provides some additional detail about this /// property and how it differs from [padding]. final EdgeInsets viewInsets;

2
votes

You can use Flutter keyboard visibility plugin

@override
Widget build(BuildContext context) {
  return KeyboardVisibilityBuilder(
    builder: (context, isKeyboardVisible) {
      return Text(
        'The keyboard is: ${isKeyboardVisible ? 'VISIBLE' : 'NOT VISIBLE'}',
      );
    }
  );
1
votes

With Flutter 2.0 and null safety, I use this package - it has no streams, pure dart, gives additional info about keyboard height and etc.

https://pub.dev/packages/flutter_keyboard_size

enter image description here

0
votes

Found a easier solution here:

Put the DesiredBottomWidget in a Stack() with a Positioned(top: somevalue), and it will be hided when the keyboard appears.

Example:

Stack(
 "Somewidget()",
 Positioned(
   top: "somevalue",
   child: "DesiredBottomWidget()"),
),
0
votes

I used a workaround, I added a focusNode to the input and added a listener to that. see the implementation here add focus listener to input

0
votes

This is my solution, which uses WidgetsBindingObserver to observe window size changes, and determine whether the keyboard is hidden based on this.

/// My widget state,it can remove the focus to end editing when the keyboard is hidden.
class MyWidgetState extends State<MyWidget> with WidgetsBindingObserver {
  /// Determine whether the keyboard is hidden.
  Future<bool> get keyboardHidden async {
    // If the embedded value at the bottom of the window is not greater than 0, the keyboard is not displayed.
    final check = () => (WidgetsBinding.instance?.window.viewInsets.bottom ?? 0) <= 0;
    // If the keyboard is displayed, return the result directly.
    if (!check()) return false;
    // If the keyboard is hidden, in order to cope with the misjudgment caused by the keyboard display/hidden animation process, wait for 0.1 seconds and then check again and return the result.
    return await Future.delayed(Duration(milliseconds: 100), () => check());
  }

  @override
  void initState() {
    super.initState();
    // Used to obtain the change of the window size to determine whether the keyboard is hidden.
    WidgetsBinding.instance?.addObserver(this);
  }

  @override
  void dispose() {
    // stop Observing the window size changes.
    WidgetsBinding.instance?.removeObserver(this);
    super.dispose();
  }
  
  @override
  void didChangeMetrics() {
    // When the window insets changes, the method will be called by the system, where we can judge whether the keyboard is hidden.
    // If the keyboard is hidden, unfocus to end editing.
    keyboardHidden.then((value) => value ? FocusManager.instance.primaryFocus?.unfocus() : null);
  }
}