“Bottom Overflowed” error caused by the keyboard

Loredana Zdrânc
Zipper Studios
Published in
5 min readJul 20, 2019

Hi! I am happy to come back to you with a new article about Flutter, Google’s portable UI toolkit for building beautiful, natively-compiled applications for mobile, web, and desktop from a single codebase. If you have ever worked with TextFormFields in Flutter, I am pretty sure that you have seen the yellow/black stripes on your screen, just like in the image below. In this article, we will talk about the best solution to avoid the “Bottom Overflowed” error when the keyboard appears.

As you can see, the widgets must be displayed in a vertical array and that’s why we need a Column widget as a parent. Don’t forget the background color and the left/right paddings! To customize the background we need a Container with a decoration property set like in the code below. To validate the text form field, we need a Form widget that must set a key property.

final _formKey = new GlobalKey<FormState>(); // outside the build() method 
...
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
color: Color.fromRGBO(36, 43, 47, 1),
padding: const EdgeInsets.symmetric(horizontal: 43.0),
child: Form(
key: _formKey,
child: Container(
alignment: Alignment.center,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
_buildFirstName(),
_buildLastName(),
_buildEmail(),
_buildPassword(),
_buildConfirmPassword(),
_buildSignUpButton(context)
],
),
),
),
));
}

Let’s take the methods one by one inside the Column widget. Let’s start by creating a custom input decorator for each text field and message validator.

InputDecoration _buildInputDecoration(String hint, String iconPath) {
return InputDecoration(
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(color: Color.fromRGBO(252, 252, 252, 1))),
hintText: hint,
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(color: Color.fromRGBO(151, 151, 151, 1))),
hintStyle: TextStyle(color: Color.fromRGBO(252, 252, 252, 1)),
icon: iconPath != '' ? Image.asset(iconPath) : null,
errorStyle: TextStyle(color: Color.fromRGBO(248, 218, 87, 1)),
errorBorder: UnderlineInputBorder(
borderSide: BorderSide(color: Color.fromRGBO(248, 218, 87, 1))),
focusedErrorBorder: UnderlineInputBorder(
borderSide: BorderSide(color: Color.fromRGBO(248, 218, 87, 1))));
}
  • _buildFirstName()
Widget _buildFirstName() {
return TextFormField(
validator: (value) =>
value.isEmpty ? "First name cannot be empty" : null,
style: TextStyle(
color: Color.fromRGBO(252, 252, 252, 1), fontFamily: 'RadikalLight'),
decoration:
_buildInputDecoration("First name", 'assets/ic_worker.png'),
);
}
  • _buildLastName()
Widget _buildLastName() {
return Container(
margin: const EdgeInsets.only(left: 40),
child: TextFormField(
validator: (value) =>
value.isEmpty ? "Last name cannot be empty" : null,
style: TextStyle(
color: Color.fromRGBO(252, 252, 252, 1), fontFamily: 'RadikalLight'),
decoration: _buildInputDecoration("Last name", ''),
));
}
  • _buildEmail()
Widget _buildEmail() {
return TextFormField(
validator: (value) => !isEmail(value) ? "Sorry, we do not recognize this email address" : null,
style: TextStyle(
color: Color.fromRGBO(252, 252, 252, 1), fontFamily: 'RadikalLight'),
decoration: _buildInputDecoration("Email", 'assets/ic_email.png'),
);
}
  • _buildPassword(). This TextFormField sets the _passwordController used to validate the next field, confirm password, and uses the isEmail() method to validate the email.
final _passwordController = TextEditingController(); //this line is outside the build() method 
...
Widget _buildPassword() {
return TextFormField(
obscureText: true,
controller: _passwordController,
validator: (value) =>
value.length <= 6 ? "Password must be 6 or more characters in length" : null,
style: TextStyle(
color: Color.fromRGBO(252, 252, 252, 1), fontFamily: 'RadikalLight'),
decoration:
_buildInputDecoration("Password", 'assets/ic_password.png'),
);
}
bool isEmail(String value) {
String regex =
r'^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$';

RegExp regExp = new RegExp(regex);

return value.isNotEmpty && regExp.hasMatch(value);
}
  • _buildConfirmPassword()
Widget _buildConfirmPassword() {
return Container(
margin: const EdgeInsets.only(left: 40),
child: TextFormField(
obscureText: true,
validator: (value) => value.isEmpty ||
(value.isNotEmpty && value != _passwordController.text)
? "Must match the previous entry"
: null,
style: TextStyle(
color: Color.fromRGBO(252, 252, 252, 1), fontFamily: 'RadikalLight'),
decoration: _buildInputDecoration("Confirm password", ''),
));
}
  • _buildSignUpButton(). We set the width of the button as beeing 62% of the screen size using the MediaQuery class.
Widget _buildSignUpButton(BuildContext context) {
return Container(
margin: const EdgeInsets.only(top: 43.0),
width: MediaQuery.of(context).size.width * 0.62,
child: RaisedButton(
child: const Text(
"Sign Up",
style: TextStyle(
color: Color.fromRGBO(40, 48, 52, 1),
fontFamily: 'RadikalMedium',
fontSize: 14),
),
color: Colors.white,
elevation: 4.0,
onPressed: () {
_validateAndSubmit();
},
),
);
}

That’s all! Run your code on a device with a smaller screen like an iPhone SE.

As you can see in the left screen, the validation works well for all text fields but, in the right screen, you can see that after the keyboard opens you receive an error: “Bottom overflowed by 74 pixels”. In technical terms, the size of the viewport was reduced and it caused an overflow in our layout.

A quick solution would be to block the widgets inside the Scaffold to resize themselves when the keyboard opens but this way, some widgets can be obscured by the keyboard. We can do this using the resizeToAvoidBottomInset property on the Scaffold widget. With this property, the build() method looks like:

@override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomInset: false, //new line
body: Container(
color: Color.fromRGBO(36, 43, 47, 1),
padding: const EdgeInsets.symmetric(horizontal: 43.0),
child: Form(
key: _formKey,
child: Container(
alignment: Alignment.center,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
_buildFirstName(),
_buildLastName(),
_buildEmail(),
_buildPassword(),
_buildConfirmPassword(),
_buildSignUpButton(context)
],
),
),
),
));
}

Another solution is to wrap the Column widget into a scrollable widget. A built-in widget provided by Flutter which works well is the SingleChildScrollView. This is the best solution to avoid the “Bottom overflowed” error when the keyboard opens.

@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
color: Color.fromRGBO(36, 43, 47, 1),
padding: const EdgeInsets.symmetric(horizontal: 43.0),
child: Form(
key: _formKey,
child: Container(
alignment: Alignment.center,
child: SingleChildScrollView( // new line
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
_buildFirstName(),
_buildLastName(),
_buildEmail(),
_buildPassword(),
_buildConfirmPassword(),
_buildSignUpButton(context)
],
),
),
),
),
));
}

Hopefully, this article helped you understand why the keyboard causes the “Bottom overflowed” error but also how to resolve it.

Stay tuned, more articles about Flutter are on their way!

Thanks for reading!

https://www.zipperstudios.co

Zipper Studios is a group of passionate engineers helping startups and well-established companies build their mobile products. Our clients are leaders in the fields of health and fitness, AI, and Machine Learning. We love to talk to likeminded people who want to innovate in the world of mobile so drop us a line here.

--

--