Open In App

Flutter – Respecting the Softkeyboard Insets

Last Updated : 20 Jun, 2022
Improve
Improve
Like Article
Like
Save
Share
Report

Flutter has made it simpler and easier to make a beautiful and interactive user interface. But there comes a problem faced by many flutter developers while working with soft keyboard inputs in the lower portion of the screen. The problem is whenever a user attempts to fill an input form (whose parent widget is a Card, Container, or something similar) in the lower part of the screen, the soft-keyboard tends to cover that form. The same problem will be replicated below and will show a simple solution to this problem. 

Problem: 

Here, in the app below you can see that whenever the user tries to fill the input form the soft keyboard covers the form and the user can’t move to another line without going back from the keyboard. 

Below is the code of the part which needs to be improved. It mainly contains the input form and some functions to handle the input. If we look at the widget tree of the input form we can see that the parent widget which composed the form is just a simple Card, which further contains two TextFields, a Container with date selection, and a RaisedButton.   

Dart




import 'dart:js_util';
 
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
 
class NewTransaction extends StatefulWidget {
  final Function addTx;
 
  const NewTransaction(this.addTx, {Key? key}) : super(key: key);
 
  @override
  // ignore: library_private_types_in_public_api
  _NewTransactionState createState() => _NewTransactionState();
}
 
class _NewTransactionState extends State<NewTransaction> {
  final _titleController = TextEditingController();
  final _amountController = TextEditingController();
  late DateTime _selectedDate;
 
  void _submitData() {
    if (_amountController.text.isEmpty) {
      return;
    }
    final enteredTitle = _titleController.text;
    final enteredAmount = double.parse(_amountController.text);
 
    // ignore: unrelated_type_equality_checks
    if (enteredTitle.isEmpty ||
        enteredAmount <= 0 ||
        // ignore: unrelated_type_equality_checks
        _selectedDate == NullRejectionException) {
      return;
    }
 
    widget.addTx(
      enteredTitle,
      enteredAmount,
      _selectedDate,
    );
 
    Navigator.of(context).pop();
  }
 
  void _presentDatePicker() {
    showDatePicker(
      context: context,
      initialDate: DateTime.now(),
      firstDate: DateTime(2019),
      lastDate: DateTime(2022),
    ).then((pickedDate) {
      if (pickedDate == null) {
        return;
      }
      setState(() {
        _selectedDate = pickedDate;
      });
    });
    // ignore: avoid_print
    print('...');
  }
 
  @override
 
// widget tree for the card starts hear
// this is the element which appear on
// tap of the floating action button
  Widget build(BuildContext context) {
    return Card(
      elevation: 5,
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.end,
        children: <Widget>[
          TextField(
            decoration: const InputDecoration(labelText: 'Title'),
            controller: _titleController,
            onSubmitted: (_) => _submitData(),
          ),
 
          //TextField
          TextField(
            decoration: const InputDecoration(labelText: 'Amount'),
            controller: _amountController,
            keyboardType: TextInputType.number,
            onSubmitted: (_) => _submitData(),
          ),
          SizedBox(
            height: 70,
            child: Row(
              children: <Widget>[
                Expanded(
                  child: Text(
                    // ignore: unrelated_type_equality_checks
                    _selectedDate == NullRejectionException
                        ? 'No Date Chosen!'
                        : 'Picked Date: ${DateFormat.yMd().format(_selectedDate)}',
                  ), //Text
                ), //Expand
                TextButton(
                  onPressed: _presentDatePicker,
                  child: Container(
                    color: Colors.green,
                    child: const Text(
                      'Choose Date',
                      style: TextStyle(fontWeight: FontWeight.bold),
                    ),
                  ),
                ),
 
                // FlatButton is deprecates and should not be used
                // instead we can use TextButton to achieve the same result
 
                // FlatButton(
                //   textColor: Theme.of(context).primaryColor, //Text
                //   onPressed: _presentDatePicker,
                //   child: const Text(
                //     'Choose Date',
                //     style: TextStyle(
                //       fontWeight: FontWeight.bold,
                //     ), //TextStyle
                //   ),
                // ), //FlatButton
              ], //<Widget>[]
            ), //Row
          ), //Container
 
          ElevatedButton(
            onPressed: _submitData,
            style: ButtonStyle(
                backgroundColor: MaterialStateProperty.all(Colors.green)),
            child: Text(
              'Add Transaction',
              style:
                  TextStyle(color: Theme.of(context).textTheme.button!.color),
            ),
          ),
 
          // RaisedButton is deprecates and should not be used
          // instead we can use ElevatedButton to achieve the same result
 
          // RaisedButton(
          //   child: const Text('Add Transaction'),
          //   color: Theme.of(context).primaryColor,
          //   textColor: Theme.of(context).textTheme.button!.color,
          //   onPressed: _submitData,
          // ), //RaisedButton
        ], //<Widget>[]
      ), //Column
    ); //Card
  }
}


Solution: 

The solution that we want to achieve is whenever a user taps on the TextField and the keyboard comes out the input form should also move up with a height equivalent to the keyboard’s and the form should be scrollable for easy access. This can be achieved in the following steps:

  • Replacing the EdgeInsets.all with EdgeInsets.only in the padding of the Card widget. This will give us control over the individual padding.
  • In the bottom argument of the padding, we will use MediaQuery  (MediaQuery.of(context).viewInsets.bottom) to know the height of the keyboard and add it to the bottom padding. This way we whenever the keyboard will appear the form will move up with the height equivalent to that of the keyboard.
  • At last, we will wrap the entire input form with SingleChildScrollView, to make it scrollable. It will make switching to the next input field easier.

Dart




// Snippet of the Input form widget tree
return SingleChildScrollView(
 
      // change 3
      child: Card(
        elevation: 5,
        child: Container(
          padding: EdgeInsets.only(
 
            // change 1
              top: 10,
              bottom: MediaQuery.of(context).viewInsets.bottom + 10,
 
              // change 2
              left: 10,
              right: 10),


 
 

This is how the input form will behave after the correction. The input form moves up in accordance with the height of the soft-keyboard, which makes all the fields visible. And in addition to that, the form is also scrollable now.

 



Like Article
Suggest improvement
Share your thoughts in the comments

Similar Reads