6

I am trying to dismiss the keyboard when the user swipes from the edge to pop route.

Currently the keyboard doesn't dismiss until the route is completely gone popped, messing up some of the other pages layout until it dismisses

I did try to use a WillPopScope to determine when the user was going to pop the route, but unfortunately this disables the swipe to pop functionality from iOS or the CupertinoPageRoute.

I just want to find out if there's anyway I can determine when the user swipes from the edge to pop or taps the back button on the appBar and dismiss the keyboard as they do so.

If possible, I am trying to dismiss keyboard as soon as they start swiping to pop, as it happens in many apps.

I am attaching attaching a gif showing the effect I'm trying to achieve.

Swipe to pop hides keyboard

tapizquent
  • 668
  • 11
  • 24
  • I'm not sure but this may help -: FocusScope.of(context).unfocus(); – GJJ2019 Aug 01 '19 at 03:46
  • Yeah that I know. My question is, how do I do this when the drag to pop starts. Unfocusing the keyboard on a button tap or when tapped anywhere on the screen isn’t a problem, getting it to do so when dragging to pop is – tapizquent Aug 01 '19 at 05:40

4 Answers4

3

As suggested by Ovidiu

class DismissKeyboardNavigationObserver extends NavigatorObserver {
  @override
  void didStartUserGesture(Route route, Route previousRoute) {
    SystemChannels.textInput.invokeMethod('TextInput.hide');
    super.didStartUserGesture(route, previousRoute);
  }
}

and in your Material App

MaterialApp(
  navigatorObservers: [DismissKeyboardNavigationObserver()],
)
tapizquent
  • 668
  • 11
  • 24
2

You need to create a custom class extending NavigatorObserver, and pass an instance of it to the navigatorObservers property of your MaterialApp or CupertinoApp.

Within that custom class, you can override didStartUserGesture and didStopUserGesture, which will be called when the swipe gesture starts/ends. This should allow you to achieve the behavior you are looking for. Note that didStartUserGesture indicates the current route as well as the previous route, based on which you could add logic to determine whether the keyboard should be dismissed or not.

Ovidiu
  • 8,204
  • 34
  • 45
  • This could work but how do I access the focus of the textfields from the navigationObserver to be able to dismiss them? – tapizquent Aug 06 '19 at 16:31
  • Marking as the answer because it does allow for what I wanted. For anyone looking, check my answer for the class I used. – tapizquent Aug 07 '19 at 00:21
0

This should come naturally and you shouldn't be directly concerned with that because actually, when you pop a route with the keyboard on, it should dismiss properly.

However, if you want to detect when the user starts swiping and dismiss the keyboard along with it and then pop the current route, you can easily achieve it by wrapping your screen widget with a GestureDetector like so:

 Widget build(BuildContext context) {
    double dragStart = 0.0;
    return GestureDetector(
      onHorizontalDragStart: (details) => dragStart = details.globalPosition.dx,
      onHorizontalDragUpdate: (details) {
        final double screenWidth = MediaQuery.of(context).size.width;

        // Here I considered a back swipe only when the user swipes until half of the screen width, but you can tweak it to your needs.
        if (dragStart <= screenWidth * 0.05 && details.globalPosition.dx >= screenWidth) {
          FocusScope.of(context).unfocus();
        }
       child: // Your other widgets...
      },
Miguel Ruivo
  • 16,035
  • 7
  • 57
  • 87
  • Will try it now. The problem with the default implementation is that it doesn't dismiss the keyboard up until the page has completely disappeared, which is an odd, unwanted behavior. Ideally the keyboard should stay on that pageRoute or disappear as it pops. Also, is there any way to have this behavior for every page with a textField? – tapizquent Jul 30 '19 at 01:18
  • This will work for any route that will kick in a keyboard and it will be dismissed as soon as you start swiping back, which I believe to be what you’re looking for. – Miguel Ruivo Jul 30 '19 at 01:46
  • Unfortunately this doesn't work. The swipe to pop from the PageRoute has precedence over the GestureDetector. It does work from the left side of the screen but not at the edge where users swipe from to dismiss. – tapizquent Jul 30 '19 at 03:00
  • That’s because I’ve make it to work only after some offset (15% of the screen width). I’ve updated my answer. – Miguel Ruivo Jul 30 '19 at 08:23
  • It does not work either. Now I honestly have no idea what the GestureDetector is doing, but it still does not dismiss the keyboard on dragToPop. I have attached a GIF with the intended behavior. I wouldn't mind not refocusing if the user chooses not to pop the screen and lets go of the swipe to pop, but I do need for it to dismiss the keyboard as soon as that drag starts. There might be a solution with the isPopGestureInProgress function from the CupertinoPageRoute but I have yet to find a way to successfully use it. – tapizquent Jul 31 '19 at 18:39
0

this is something i wrote to handle this issue. doesnt use any external packages, you would just wrap your content in the main function at the top.

Widget swipeOffKeyboard(BuildContext context, {Widget? child}) {
  return Listener(
    onPointerMove: (PointerMoveEvent pointer) {
      disKeyboard(pointer, context);
    },
    child: child, // your content should go here
  );
}

void disKeyboard(PointerMoveEvent pointer, BuildContext context) {
  double insets = MediaQuery.of(context).viewInsets.bottom;
  double screenHeight = MediaQuery.of(context).size.height;
  double position = pointer.position.dy;
  double keyboardHeight = screenHeight - insets;
  if (position > keyboardHeight && insets > 0) FocusManager.instance.primaryFocus?.unfocus();
}
Justin Uzzanti
  • 326
  • 2
  • 3