Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Flutter Dismissing the Keyboard Automatically When Clicking the TextField

I am new to Flutter and facing a very strange issue:

I have a simple Stateless Widget with a Custom App Bar, then a TextFormField and a button inside the Column. I am using go_router for the navigation. Screen util for dimen sizes. The problem is that I started my routes from splash screen then login screen with textfield.

The issue is only in Android, not in iOS platform! Occurring on all devices, including Android Emulator and Physical Devices

The textfield on login screen if clicked by the user in 100ms to 200ms the keyboard appears and then hides itself automatically.

I am trying a minimal code to test:

@TypedGoRoute<ComplaintScreenRoute>(path: '/complaintScreenRoute')
class ComplaintScreenRoute extends GoRouteData {
  final String complaintType;

  const ComplaintScreenRoute({required this.complaintType});

  @override
  Widget build(BuildContext context, GoRouterState state) => injector<RouteHelperUtil>().getComplaintScreenWidget(complaintType);
}

This is the way I am creating my go_router. I am supposing this screen after login have been performed by the user. Then lands on this screen:

The build method contains:

Container(
  color: Colors.yellow,
  height: 200,
  width: 200,
  child:
    TextFormField(
      controller: model.myController,
      autofocus: true,
  )
),

I am trying with a simple TextFormField and the autofocus: true, even hiding the keyboard automatically when the screen loads. So if the user clicks the field too quickly, or autofocus is true the keyboard hides.

What I tried is that i have added a delay in my field like:

class BulletproofTextFieldGreat extends StatefulWidget {
  final TextEditingController controller;
  final bool autofocus;
  final FocusNode? focusNode;
  final String? hintText;

  const BulletproofTextFieldGreat({
    super.key,
    required this.controller,
    this.autofocus = false,
    this.focusNode,
    this.hintText,
  });

  @override
  State<BulletproofTextFieldGreat> createState() => _BulletproofTextFieldGreatState();
}

class _BulletproofTextFieldGreatState extends State<BulletproofTextFieldGreat> {
  late FocusNode _internalFocusNode;
  FocusNode get _focusNode => widget.focusNode ?? _internalFocusNode;

  @override
  void initState() {
    super.initState();
    _internalFocusNode = FocusNode();

    if (widget.autofocus) {
      // Delay autofocus until the screen is stable after navigation
      WidgetsBinding.instance.addPostFrameCallback((_) {
        Future.delayed(const Duration(milliseconds: 300), () {
          if (mounted && !_focusNode.hasFocus) {
            FocusScope.of(context).requestFocus(_focusNode);
          }
        });
      });
    }
  }

  @override
  void dispose() {
    if (widget.focusNode == null) {
      _internalFocusNode.dispose();
    }
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return TextField(
      controller: widget.controller,
      focusNode: _focusNode,
      decoration: InputDecoration(hintText: widget.hintText),
    );
  }
}

So what I'm trying is to overcome the default autofocus. I am delaying the field to get the focus if it is getting the focus on build. But I also checked if my widget is rebuiding itself for multiple times by adding a log in widget build method which is showing only 1 time.

Also, I checked the go_router and added a delay in it with future builder. I am adding the delay as a workaround so that if user clicks way fast, the keyboard should not hide itself.

The summary of the problem is:

User lands on the screen, clicks the TextFormField the keyboard appears and hides The textformfield has autofocus is true, the screen loads and keyboard shows as field gets the focus but keyboard suddenly hides itself. I tried with all input fields like CupertinoTextField, editable text etc.

For reference I am attaching a screen recording: https://drive.google.com/file/d/1KhgZah4ScAyIME3_3bIFKYItI2x4ATQR/view?usp=sharing

Also, I tried my Flutter SDK upgraded to 3.29.2 Since I got this issue on 3.24.0 then upgraded to 3.27.4 and then 3.29.2, but still getting the issue.

 flutter doctor
Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel stable, 3.29.2, on macOS 15.3.2 24D81 darwin-x64, locale en-US)
[✓] Android toolchain - develop for Android devices (Android SDK version 36.0.0)
[✓] Xcode - develop for iOS and macOS (Xcode 16.2)
[✓] Chrome - develop for the web
[✓] Android Studio (version 2023.1)
[✓] Connected device (3 available)
[✓] Network resources

• No issues found!

Any hint or solution will be highly appreciated. Thanks in advance

like image 374
Java Nerd Avatar asked Nov 01 '25 14:11

Java Nerd


2 Answers

I can't directly provide the code snippet you might be looking for, however, I could share what I have already experienced before.

The main issue that might be causing the strange behavior is how you're customizing the implementation of both FocusNode() variable instance and autofocus: true.

Another possible cause is when you create a custom widget (which you can consider a child widget) that is used by another widget (the parent), but the child widget becomes unmounted shortly after it's initialized.

The probable cause of the problem may be an unintended behavior in your code — such as setting autofocus: true, customizing the behavior of your FocusNode(), and unintentionally causing widgets to become unmounted after being mounted (probably the mixture of those that is already mentioned).

The confusing part is why the keyboard is automatically collapsed if the user quickly taps a TextFormField in a very short amount of time right after the splash screen...

For me, you have to observe which code you included in your program that constantly changes the behavior if that scenario arises.

For example:

If a user taps the TextFormField, what other focus handler affects the behavior of the focus of that widget? If there's one that is redundant, then remove or revise it. You need to check which focus handler triggers the auto-dismissal of the keyboard. Technically, it is usually how you manage how FocusNode() should behave according to your use case, also, if it introduces conflict with autofocus: true. Breakpoint each instance of your focus handler to see if it executed after the user interacted with your TextFormField widget.

You could also use this package for a deep assessment of the keyboard visibility in your program.

Additionally, you can also wrap your TextField or TextFormField with PopScope and it should look something like this:

return PopScope<Object?>(
      canPop: false,
      onPopInvokedWithResult: (bool didPop, Object? result) {
        if (didPop) {
          return;
        } else {
          debugPrint('hasPopped');
        }
      },
      child: TextField(
        controller: widget.controller,
        focusNode: _focusNode,
        decoration: InputDecoration(hintText: widget.hintText),
      ),
    );

You can investigate whether the current widget is being disposed of automatically.

This might not directly resolve your problem, but I can ensure that it will provide insight into why you encountered that strange behavior.

P.S. Please correct me if I'm wrong.

like image 80
DevQt Avatar answered Nov 03 '25 14:11

DevQt


  • In iOS, by default, Flutter uses adjustPan, so when the keyboard comes up the view just shifts upward—and nothing gets rebuilt.

  • In Android, the default is adjustResize, so Flutter actually shrinks your whole app window to fit the keyboard.

  • So, anything that listens to MediaQuery (like ScreenUtil) sees that size change and rebuilds your page. That rebuild knocks your text field’s focus away, and poof—the keyboard disappears again.

As solutions you can make your Scaffold not to resize:

return Scaffold(
  // Don’t resize when the keyboard appears—
  // just let it float above your UI
  resizeToAvoidBottomInset: false,

  appBar: AppBar(),
  body: Column(),
);

Or you can customize ScreenUtli :

ScreenUtilInit(
  designSize: Size(360, 690),
  // Only rebuild when the true screen width/height changes,
  // ignore temporary inset changes from the keyboard
  rebuildFactor: (old, now) =>
    old.screenWidth  != now.screenWidth ||
    old.screenHeight != now.screenHeight,

  builder: (context, child) =>
    MaterialApp.router(/* … */),
);

The best way is to use AndroidManifest.xml and add some line:

<activity
  android:name=".MainActivity"
  android:windowSoftInputMode="stateVisible|adjustPan">
  …
</activity>
like image 39
KTZaw Avatar answered Nov 03 '25 16:11

KTZaw



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!