flutter / flutter

Flutter makes it easy and fast to build beautiful apps for mobile and beyond
https://flutter.dev
BSD 3-Clause "New" or "Revised" License
165.01k stars 27.19k forks source link

Text Field loses focus when navigating back by triggering key events #150257

Closed KaushikGupta007 closed 3 months ago

KaushikGupta007 commented 3 months ago

Steps to reproduce

  1. Create three text fields on home page
  2. Create one field on second page
  3. Run project in flutter web
  4. Click on Second Field in home page
  5. Press Enter to navigate to second page
  6. Press Escape to return to previous page
  7. Click on First Field in home page
  8. Press F2 to navigate to second page
  9. Press Escape to return to previous page

Expected results

First Text Field should have kept its focus

Insights that might help

Actual results

First Text Field loses focus Facing this issue in Flutter web (v3.22.2)

Code sample

Code sample ```dart Future main() async { runApp(MaterialApp( initialRoute: "/", routes: { "/": (context) => const Home(), "/secondPage": (context) => const SecondPage(), }, )); } class ChooseIntent extends Intent{} class Home extends StatefulWidget { const Home({super.key}); @override State createState() => _HomeState(); } class _HomeState extends State { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text("Flutter v3.22.2"), ), body: KeyboardListener( focusNode: FocusNode(), onKeyEvent: (event){}, child: Center( child: Padding( padding: const EdgeInsets.all(8.0), child: Column( children: [ Shortcuts( shortcuts: { LogicalKeySet(LogicalKeyboardKey.f2) : ChooseIntent() }, child: Actions( actions: { ChooseIntent : CallbackAction(onInvoke: (intent) async { Navigator.pushNamed(context, "/secondPage"); return null; }) }, child: TextFormField( decoration: const InputDecoration(hintText: "First Field", labelText: "First Field"), ), ), ), TextFormField( textInputAction: TextInputAction.next, decoration: const InputDecoration(hintText: "Second Field", labelText: "Second Field"), onFieldSubmitted: (val) async { await Navigator.pushNamed(context, "/secondPage"); if(context.mounted){ FocusScope.of(context).previousFocus(); } }, ), TextFormField( decoration: const InputDecoration(hintText: "Third Field", labelText: "Third Field"), ), ], ), ), ), ), ); } } class SecondPage extends StatefulWidget { const SecondPage({super.key}); @override State createState() => _SecondPageState(); } class _SecondPageState extends State { @override Widget build(BuildContext context) { return KeyboardListener( focusNode: FocusNode(), onKeyEvent: (event){ if(event.logicalKey == LogicalKeyboardKey.escape){ Navigator.pop(context); } }, child: Scaffold( appBar: AppBar( title: const Text("Second Page"), ), body: Center( child: TextFormField( autofocus: true, decoration: const InputDecoration(hintText: "Second Page", labelText: "Second Page"), ) ), ), ); } } ```

Screenshots or Video

Screenshots / Video demonstration https://github.com/flutter/flutter/assets/129219828/4fbaf662-5065-4d49-8850-97cf74c6f5b1

Logs

Logs ```console [Paste your logs here] ```

Flutter Doctor output

Doctor output ```console [√] Flutter (Channel stable, 3.22.2, on Microsoft Windows [Version 10.0.22621.3593], locale en-IN) • Flutter version 3.22.2 on channel stable at C:\Users\Admin\fvm\versions\3.22.2 • Upstream repository https://github.com/flutter/flutter.git • Framework revision 761747bfc5 (9 days ago), 2024-06-05 22:15:13 +0200 • Engine revision edd8546116 • Dart version 3.4.3 • DevTools version 2.34.3 [√] Windows Version (Installed version of Windows is version 10 or higher) Windows PowerShell [√] Android toolchain - develop for Android devices (Android SDK version 33.0.1) • Android SDK at C:\Users\Admin\AppData\Local\Android\sdk • Platform android-34, build-tools 33.0.1 • Java binary at: C:\Program Files\Android\Android Studio\jbr\bin\java • Java version OpenJDK Runtime Environment (build 17.0.6+0-b2043.56-9586694) • All Android licenses accepted. [√] Chrome - develop for the web • Chrome at C:\Program Files\Google\Chrome\Application\chrome.exe [√] Visual Studio - develop Windows apps (Visual Studio Community 2022 17.9.1) • Visual Studio at C:\Program Files\Microsoft Visual Studio\2022\Community • Visual Studio Community 2022 version 17.9.34616.47 • Windows 10 SDK version 10.0.22621.0 [√] Android Studio (version 2022.2) • Android Studio at C:\Program Files\Android\Android Studio • Flutter plugin can be installed from: https://plugins.jetbrains.com/plugin/9212-flutter • Dart plugin can be installed from: https://plugins.jetbrains.com/plugin/6351-dart • Java version OpenJDK Runtime Environment (build 17.0.6+0-b2043.56-9586694) [√] VS Code (version 1.89.1) • VS Code at C:\Users\Admin\AppData\Local\Programs\Microsoft VS Code • Flutter extension version 3.90.0 [√] Connected device (3 available) • Windows (desktop) • windows • windows-x64 • Microsoft Windows [Version 10.0.22621.3593] • Chrome (web) • chrome • web-javascript • Google Chrome 126.0.6478.56 • Edge (web) • edge • web-javascript • Microsoft Edge 125.0.2535.85 [√] Network resources • All expected network resources are available. • No issues found! ```
tonka3000 commented 3 months ago

I have the same issue since 3.22.

I also see sometimes keyboard input issue like I need to press ENTER twice to trigger the intent. Not quite sure if that issue is connected to the focus issue.

huycozy commented 3 months ago

I can reproduce this on macOS app target as well. Thanks for the report.

flutter doctor -v (stable and master) ```bash [✓] Flutter (Channel stable, 3.22.2, on macOS 14.1 23B74 darwin-x64, locale en-VN) • Flutter version 3.22.2 on channel stable at /Users/huynq/Documents/GitHub/flutter • Upstream repository https://github.com/flutter/flutter.git • Framework revision 761747bfc5 (31 hours ago), 2024-06-05 22:15:13 +0200 • Engine revision edd8546116 • Dart version 3.4.3 • DevTools version 2.34.3 [✓] Android toolchain - develop for Android devices (Android SDK version 34.0.0) • Android SDK at /Users/huynq/Library/Android/sdk • Platform android-34, build-tools 34.0.0 • ANDROID_HOME = /Users/huynq/Library/Android/sdk • Java binary at: /Applications/Android Studio.app/Contents/jbr/Contents/Home/bin/java • Java version OpenJDK Runtime Environment (build 17.0.10+0-17.0.10b1087.21-11572160) • All Android licenses accepted. [✓] Xcode - develop for iOS and macOS (Xcode 15.3) • Xcode at /Applications/Xcode15.3.app/Contents/Developer • Build 15E204a • CocoaPods version 1.15.2 [✓] Chrome - develop for the web • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome [✓] Android Studio (version 2023.3) • Android Studio at /Applications/Android Studio.app/Contents • Flutter plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/9212-flutter • Dart plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/6351-dart • android-studio-dir = /Applications/Android Studio.app/ • Java version OpenJDK Runtime Environment (build 17.0.10+0-17.0.10b1087.21-11572160) [✓] VS Code (version 1.89.1) • VS Code at /Applications/Visual Studio Code.app/Contents • Flutter extension version 3.90.0 [✓] Connected device (3 available) • iPhone (mobile) • d9a94afe2b649fef56ba0bfeb052f0f2a7dae95e • ios • iOS 15.8 19H370 • macOS (desktop) • macos • darwin-x64 • macOS 14.1 23B74 darwin-x64 • Chrome (web) • chrome • web-javascript • Google Chrome 125.0.6422.142 [✓] Network resources • All expected network resources are available. • No issues found! ``` ```bash [!] Flutter (Channel master, 3.23.0-13.0.pre.242, on macOS 14.1 23B74 darwin-x64, locale en-VN) • Flutter version 3.23.0-13.0.pre.242 on channel master at /Users/huynq/Documents/GitHub/flutter_master ! Warning: `flutter` on your path resolves to /Users/huynq/Documents/GitHub/flutter/bin/flutter, which is not inside your current Flutter SDK checkout at /Users/huynq/Documents/GitHub/flutter_master. Consider adding /Users/huynq/Documents/GitHub/flutter_master/bin to the front of your path. ! Warning: `dart` on your path resolves to /Users/huynq/Documents/GitHub/flutter/bin/dart, which is not inside your current Flutter SDK checkout at /Users/huynq/Documents/GitHub/flutter_master. Consider adding /Users/huynq/Documents/GitHub/flutter_master/bin to the front of your path. • Upstream repository https://github.com/flutter/flutter.git • Framework revision 5187cab7bd (2 days ago), 2024-06-14 23:35:34 -0400 • Engine revision 9779c273aa • Dart version 3.5.0 (build 3.5.0-266.0.dev) • DevTools version 2.36.0 • If those were intentional, you can disregard the above warnings; however it is recommended to use "git" directly to perform update checks and upgrades. [✓] Android toolchain - develop for Android devices (Android SDK version 34.0.0) • Android SDK at /Users/huynq/Library/Android/sdk • Platform android-34, build-tools 34.0.0 • ANDROID_HOME = /Users/huynq/Library/Android/sdk • Java binary at: /Applications/Android Studio.app/Contents/jbr/Contents/Home/bin/java • Java version OpenJDK Runtime Environment (build 17.0.10+0-17.0.10b1087.21-11572160) • All Android licenses accepted. [✓] Xcode - develop for iOS and macOS (Xcode 15.3) • Xcode at /Applications/Xcode15.3.app/Contents/Developer • Build 15E204a • CocoaPods version 1.15.2 [✓] Chrome - develop for the web • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome [✓] Android Studio (version 2023.3) • Android Studio at /Applications/Android Studio.app/Contents • Flutter plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/9212-flutter • Dart plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/6351-dart • android-studio-dir = /Applications/Android Studio.app/ • Java version OpenJDK Runtime Environment (build 17.0.10+0-17.0.10b1087.21-11572160) [✓] VS Code (version 1.90.0) • VS Code at /Applications/Visual Studio Code.app/Contents • Flutter extension version 3.90.0 [✓] Connected device (2 available) • macOS (desktop) • macos • darwin-x64 • macOS 14.1 23B74 darwin-x64 • Chrome (web) • chrome • web-javascript • Google Chrome 125.0.6422.142 [✓] Network resources • All expected network resources are available. ! Doctor found issues in 1 category. ```
justinmc commented 3 months ago

CC @gspencergoog in case you have any insights related to focus here.

gspencergoog commented 3 months ago

In the example FocusNode is being used incorrectly throughout. Creating a FocusNode() on each build will cause focus issues, and not disposing of them properly can cause memory leaks.

Using this version of the sample code, it seems to work for me:

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

void main() {
  runApp(MaterialApp(
    initialRoute: '/',
    routes: <String, WidgetBuilder>{
      '/': (BuildContext context) => const Home(),
      '/secondPage': (BuildContext context) => const SecondPage(),
    },
  ));
}

class ChooseIntent extends Intent{}

class Home extends StatefulWidget {
  const Home({super.key});

  @override
  State<Home> createState() => _HomeState();
}

class _HomeState extends State<Home> {
  final FocusNode _focusNode = FocusNode(debugLabel: 'Home');

  @override
  void dispose() {
    _focusNode.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Flutter v3.22.2'),
      ),
      body: KeyboardListener(
        focusNode: _focusNode,
        onKeyEvent: (KeyEvent event){},
        child: Center(
          child: Padding(
            padding: const EdgeInsets.all(8.0),
            child: Column(
              children: <Widget>[
                Shortcuts(
                  shortcuts: <ShortcutActivator, Intent>{
                    LogicalKeySet(LogicalKeyboardKey.f2) : ChooseIntent()
                  },
                  child: Actions(
                    actions: <Type, Action<Intent>>{
                      ChooseIntent : CallbackAction<ChooseIntent>(onInvoke: (ChooseIntent intent) async {
                        Navigator.pushNamed(context, '/secondPage');
                        return null;
                      })
                    },
                    child: TextFormField(
                      decoration: const InputDecoration(hintText: 'First Field', labelText: 'First Field'),
                    ),
                  ),
                ),
                TextFormField(
                  textInputAction: TextInputAction.next,
                  decoration: const InputDecoration(hintText: 'Second Field', labelText: 'Second Field'),
                  onFieldSubmitted: (String val) async {
                    await Navigator.pushNamed(context, '/secondPage');
                    if(context.mounted){
                      FocusScope.of(context).previousFocus();
                    }
                  },
                ),
                TextFormField(
                  decoration: const InputDecoration(hintText: 'Third Field', labelText: 'Third Field'),
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

class SecondPage extends StatefulWidget {
  const SecondPage({super.key});

  @override
  State<SecondPage> createState() => _SecondPageState();
}

class _SecondPageState extends State<SecondPage> {
  final FocusNode _focusNode = FocusNode(debugLabel: 'Home');

  @override
  void dispose() {
    _focusNode.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return KeyboardListener(
      focusNode: _focusNode,
      onKeyEvent: (KeyEvent event){
        if(event.logicalKey == LogicalKeyboardKey.escape){
          Navigator.pop(context);
        }
      },
      child: Scaffold(
        appBar: AppBar(
          title: const Text('Second Page'),
        ),
        body: Center(
          child: TextFormField(
            autofocus: true,
            decoration: const InputDecoration(hintText: 'Second Page', labelText: 'Second Page'),
          )
        ),
      ),
    );
  }
}
KaushikGupta007 commented 3 months ago

@gspencergoog But this used to work in v3.10.6. So now i have to create additional focus nodes for keyboard listener ?

gspencergoog commented 3 months ago

So now i have to create additional focus nodes for keyboard listener ?

Yes, that's correct. It has always been the case that creating a FocusNode on each build caused issues like this. That it happened to work before was probably due to a bug that was fixed. It also probably caused issues that just weren't immediately visible (like a memory leak).

Instead of KeyboardListener, you can also use just a regular Focus widget without giving it a FocusNode at all (it manages one internally for you). That's exactly how KeyboardListener is implemented anyhow, by using a Focus widget. This version of the code also works:

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

void main() {
  runApp(MaterialApp(
    initialRoute: '/',
    routes: <String, WidgetBuilder>{
      '/': (BuildContext context) => const Home(),
      '/secondPage': (BuildContext context) => const SecondPage(),
    },
  ));
}

class ChooseIntent extends Intent {}

class Home extends StatelessWidget {
  const Home({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Flutter 3.23.0-13.0.pre.248 • channel main'),
      ),
      // This Focus widget has no effect, and can be removed.  I just left it
      // here since you seem to want to have a listener here.
      body: Focus(
        onKeyEvent: (FocusNode _, KeyEvent __) => KeyEventResult.ignored,
        child: Center(
          child: Padding(
            padding: const EdgeInsets.all(8.0),
            child: Column(
              children: <Widget>[
                Shortcuts(
                  shortcuts: <ShortcutActivator, Intent>{LogicalKeySet(LogicalKeyboardKey.f2): ChooseIntent()},
                  child: Actions(
                    actions: <Type, Action<Intent>>{
                      ChooseIntent: CallbackAction<ChooseIntent>(onInvoke: (ChooseIntent intent) async {
                        Navigator.pushNamed(context, '/secondPage');
                        return null;
                      })
                    },
                    child: TextFormField(
                      decoration: const InputDecoration(hintText: 'First Field', labelText: 'First Field'),
                    ),
                  ),
                ),
                TextFormField(
                  textInputAction: TextInputAction.next,
                  decoration: const InputDecoration(hintText: 'Second Field', labelText: 'Second Field'),
                  onFieldSubmitted: (String val) async {
                    await Navigator.pushNamed(context, '/secondPage');
                    if (context.mounted) {
                      FocusScope.of(context).previousFocus();
                    }
                  },
                ),
                TextFormField(
                  decoration: const InputDecoration(hintText: 'Third Field', labelText: 'Third Field'),
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

class SecondPage extends StatelessWidget {
  const SecondPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Focus(
      onKeyEvent: (FocusNode _, KeyEvent event) {
        if (event.logicalKey == LogicalKeyboardKey.escape) {
          Navigator.pop(context);
          return KeyEventResult.handled;
        }
        return KeyEventResult.ignored;
      },
      child: Scaffold(
        appBar: AppBar(
          title: const Text('Second Page'),
        ),
        body: Center(
            child: TextFormField(
          autofocus: true,
          decoration: const InputDecoration(hintText: 'Second Page', labelText: 'Second Page'),
        )),
      ),
    );
  }
}
github-actions[bot] commented 2 months ago

This thread has been automatically locked since there has not been any recent activity after it was closed. If you are still experiencing a similar issue, please open a new bug, including the output of flutter doctor -v and a minimal reproduction of the issue.