Detect Keyboard Visibility In Flutter App

In this post, we will learn how we can detect keyboard visibility in flutter application.

Introduction

In Flutter, user input fields like TextField automatically pops up keyboard when in focus. Similarly, the keyboard hides itself when you move the focus to a different widget or press back button.

Now, if you want to keep track of the keyboard open/close event, how do you do so?

DecisionMentor app

MediaQuery To The Rescue

In Flutter, we can lookup device properties like size with the help of MediaQuery widget. As it extends an InheritedWidget, MediaQuery can propagate this information even during run-time anywhere within the application.

Learn More: Basics Of Inherited Widget

Use the viewInsets property of the MediaQuery class to get the part of mobile device which is hidden by the device’s keyboard.

So, you can use this property in your code like this:

bool _keyboardIsVisible() {
    return !(MediaQuery.of(context).viewInsets.bottom == 0.0);
  }
Detect Keyboard Visibility In Flutter
Detect Keyboard Visibility In Flutter

Checkout: Build HangMan Game In Flutter

Full Demo App

Code for the demo above:

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Keyboard Visibility Demo'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

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

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text(widget.title),
        ),
        body: Container(
          width: double.infinity,
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            crossAxisAlignment: CrossAxisAlignment.center,
            children: <Widget>[
              _keyboardIsVisible()
                  ? Text(
                      "Keyboard is visible",
                      style: Theme.of(context)
                          .textTheme
                          .display1
                          .copyWith(color: Colors.blue),
                    )
                  : RichText(
                      text: TextSpan(children: [
                        TextSpan(
                          text: "Keyboard is ",
                          style: Theme.of(context)
                              .textTheme
                              .display1
                              .copyWith(color: Colors.blue),
                        ),
                        TextSpan(
                          text: "not ",
                          style: Theme.of(context)
                              .textTheme
                              .display1
                              .copyWith(color: Colors.red),
                        ),
                        TextSpan(
                          text: "visible",
                          style: Theme.of(context)
                              .textTheme
                              .display1
                              .copyWith(color: Colors.blue),
                        )
                      ]),
                    ),
              SizedBox(
                height: 20,
              ),
              Container(
                width: 200.0,
                child: TextField(
                  style: Theme.of(context).textTheme.display1,
                  decoration: InputDecoration(
                    focusedBorder: OutlineInputBorder(
                      borderSide: BorderSide(
                        color: Colors.blue,
                      ),
                      borderRadius: BorderRadius.circular(10.0),
                    ),
                  ),
                ),
              )
            ],
          ),
        ));
  }

  bool _keyboardIsVisible() {
    return !(MediaQuery.of(context).viewInsets.bottom == 0.0);
  }
}

Here, a text label is updated based upon keyboard open/close state.

MediaQuery Isn’t Working

When using the MediaQuery.of(context) method, it is important to realize that in Flutter every widget gets it’s own unique BuildContext.

So, if your widget is inside a different widget, you will notice that the solution above will not work.

For example:

...


class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    print(context);
    return Scaffold(
        appBar: AppBar(
          title: Text(widget.title),
        ),
        body: Column(children: [         
          AnotherWidget()
        ]));
  }
}

class AnotherWidget extends StatefulWidget {
  @override
  _AnotherWidgetState createState() => _AnotherWidgetState();
}

class _AnotherWidgetState extends State<AnotherWidget> {
  Widget build(BuildContext context) {
    print(context);
    return Container(
      width: double.infinity,
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        crossAxisAlignment: CrossAxisAlignment.center,
        children: <Widget>[
          _keyboardIsVisible()
              ? Text(
                  "Keyboard is visible",
                  style: Theme.of(context)
                      .textTheme
                      .display1
                      .copyWith(color: Colors.blue),
                )
              : RichText(
                  text: TextSpan(children: [
                    TextSpan(
                      text: "Keyboard is ",
                      style: Theme.of(context)
                          .textTheme
                          .display1
                          .copyWith(color: Colors.blue),
                    ),
                    TextSpan(
                      text: "not ",
                      style: Theme.of(context)
                          .textTheme
                          .display1
                          .copyWith(color: Colors.red),
                    ),
                    TextSpan(
                      text: "visible",
                      style: Theme.of(context)
                          .textTheme
                          .display1
                          .copyWith(color: Colors.blue),
                    )
                  ]),
                ),
          SizedBox(
            height: 20,
          ),
          Container(
            width: 200.0,
            child: TextField(
              style: Theme.of(context).textTheme.display1,
              decoration: InputDecoration(
                focusedBorder: OutlineInputBorder(
                  borderSide: BorderSide(
                    color: Colors.blue,
                  ),
                  borderRadius: BorderRadius.circular(10.0),
                ),
              ),
            ),
          )
        ],
      ),
    );
  }

  bool _keyboardIsVisible() {
    return !(MediaQuery.of(context).viewInsets.bottom == 0.0);
  }
}

Here, AnotherWidget is inside the _MyHomePageState widget. With respect to AnotherWidget context, the keyboard isn’t obscuring any part of the device. So the code above will always show “Keyboard is not visible” as MediaQuery.of(context).viewInsets.bottom will always return 0.0.

For this reason you will not be able to detect keyboard open/close using the MediaQuery of AnotherWidget context.

So how to detect keyboard open/close visibility if MediaQuery.of(context).viewInsets.bottom isn’t working?

Solution

Since, the context from AnotherWidget isn’t able to give us the right answer. In such condition, compare the viewInsets of the parent widget.

For example you could do something like this:

  • Get the height of parent container widget first. You can get this heigh using MediaQuery.
  • Pass the height of parent as parameter to child widget.
  • In the child widget (AnotherWidget) compare the parent widget’s height with the current height.
 bool _keyboardIsVisible() {
    var currentScreenSize = getSafeAreaSize(MediaQuery.of(context));
    return widget.sizeOfParentWidget.height < currentScreenSize.height;
  }

Wrapping Up

In this post we saw how we can detect keyboard visibility in a Flutter app. We looked at how to properly use MediaQuery for this purpose.

Don’t forget to checkout out our other articles!