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?
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);
}

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!
You must be logged in to post a comment.