How to Dismiss the Keyboard in Flutter the Right Way

Last reviewed in August 2019 by James Dixon

If you’ve tried to dismiss the keyboard in a Flutter app by tapping outside of a form field and nothing happened, don’t fret. You’re not crazy.

This isn’t a behavior that Flutter provides out of the box.

So how do we dismiss the keyboard in Flutter? Fortunately, there’s a relatively simple solution.

What’s the Expected Behavior?

Before doing anything, let’s define exactly what the expected behavior is that we’re trying to recreate:

  1. The user should be able to tap on any non-interactive widget to dismiss the keyboard. For simplicity sake, this means that if the user taps on anything but a button or link, the keyboard should be dismissed.

  2. The behavior should be accessible on any screen in our app.

Now that we’ve defined the behavior we’d like to achieve, let’s get started.

Step 1: Detect the tap

The first thing we need to do is detect when a user has tapped outside of the currently focused text field. This is trivial thanks to the GestureDetector widget, which makes it super simple to detect interactions such as taps, drags, holds and more.

For our use case, we’re going to be implementing the onTap handler.

GestureDetector(
  onTap: () {},
  child: ...
);

So, where should we put the GestureDetector in order to achieve the desired result?

Since our requirements dictate that this is a behavior we want throughout our entire app, we need to place our GestureDetector at the top of the widget tree. For most Flutter apps, the MaterialApp widget is going to occupy that position.

With that, let’s wrap MaterialApp with our GestureDetector:

// main.dart
import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () {},
      child: MaterialApp(
        title: 'Flutter Demo',
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: MyHomePage(),
      ),
    );
  }
}

Step 2: Dismiss the Keyboard

To trigger the keyboard to dismiss itself, we need to remove “focus” from our text field. By removing the current focus from the text field’s FocusNode, we can achieve the desired result.

Wait, a focus-what?

Great question! Here’s a snippet from the Flutter docs:

FocusNodes are persistent objects that form a focus tree that is a representation of the widgets in the hierarchy that are interested in focus.

First, we use FocusScope.of(context) to get the current FocusNode, which will be the node associated with our text field (assuming that you’ve tapped the field to activate it).

// main.dart
import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () {
        FocusScopeNode currentFocus = FocusScope.of(context);
      },
      child: MaterialApp(
        title: 'Flutter Demo',
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: MyHomePage(),
      ),
    );
  }
}

Next, we need to check to see if the current FocusNode has the “primary focus.” If it doesn’t, we call unfocus() on the current node to remove focus and trigger the keyboard to dismiss. If you attempt to unfocus() a node that currently has the primary focus, Flutter will throw an exception.

Checking hasPrimaryFocus is necessary to prevent Flutter from throwing an exception when trying to unfocus the node at the top of the tree. Check out this thread on Github for more information.
// main.dart
import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () {
        FocusScopeNode currentFocus = FocusScope.of(context);

        if (!currentFocus.hasPrimaryFocus) {
          currentFocus.unfocus();
        }
      },
      child: MaterialApp(
        title: 'Flutter Demo',
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: MyHomePage(),
      ),
    );
  }
}

You may have seen the following solution to this problem on StackOverflow and Github:

FocusScope.of(context).requestFocus(new FocusNode())

While this does technically work, it’s not recommended as FocusNode is a ChangeNotifier and therefore needs to be disposed of properly. With the above method, a FocusNode is created and then thrown into the abyss never to be seen again.

All done! Here’s the new behavior in action:

App showing ability to dismiss keyboard in Flutter

The best from the Flutter-verse in 3 minutes or less? Join Snacks!

Delivered twice monthly. No link walls. No spam. EVER.