Calling FocusScopeNode.unfocus() causes FutureBuilder to rebuild

This issue has been tracked since 2022-11-24.

Steps to Reproduce

  1. Execute flutter run on the code sample
  2. Click inside the TextField, then outside the TextField to dismiss it
  3. The FutureBuilder build method will be re-run, without the wrapping build() method being called

Expected results:

I may be misunderstanding what's meant to happen when a TextField loses focus and the soft keyboard disappears, so if that's the case, please excuse me for opening this issue.

I'm not sure why:

  • The issue happens when using FocusScopeNode...unfocus() but not FocusManager.instance.primaryFocus?.unfocus()
  • Why only FutureBuilder rebuilds, and not the containing build() method

Actual results:

FutureBuilder re-runs its build method if the TextField is dismissed using

FocusScopeNode currentFocus = FocusScope.of(context);
if (!currentFocus.hasPrimaryFocus) {
  currentFocus.unfocus();
}

but NOT if it's dismissed using

FocusManager.instance.primaryFocus?.unfocus();
Code sample
import 'package:flutter/material.dart';

void main() async {
  runApp(const TestApp());
}

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

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      title: 'Testapp',
      home: Scaffold(
        body: TestAppHomeScreen(),
      ),
    );
  }
}

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

  @override
  State<TestAppHomeScreen> createState() => _TestAppHomeScreenState();
}

class _TestAppHomeScreenState extends State<TestAppHomeScreen> {
  late final Future myFuture;

  @override
  void initState() {
    super.initState();

    /// simple future that mimics a call to an api
    myFuture = Future.delayed(const Duration(milliseconds: 500), () => true);

    print("[HOME] HOME SCREEN INIT STATE CALLED");
  }

  @override
  Widget build(BuildContext context) {
    print("[HOME] HOME SCREEN BUILD CALLED");
    return FutureBuilder(
      future: myFuture,
      builder: (context, snapshot) {
        print("[HOME] HOME SCREEN FUTURE BUILDER CALLED WITH STATE ${snapshot.connectionState}");

        /// simple loader animation to show if the future is still waiting
        if (snapshot.connectionState == ConnectionState.waiting) {
          return const Scaffold(
            body: Center(
              child: CircularProgressIndicator(),
            ),
          );
        }

        /// wrap our scaffold in a gesture detector so we can detect when we click outside of
        /// the textfield
        return GestureDetector(
          onTapUp: (details) {
            /// hiding the soft keyboard like so will trigger future builder to rebuild
            FocusScopeNode currentFocus = FocusScope.of(context);
            if (!currentFocus.hasPrimaryFocus) {
              currentFocus.unfocus();
            }

            /// hiding the soft keyboard like so WON'T trigger future builder to rebuild
            // FocusManager.instance.primaryFocus?.unfocus();
          },

          /// simple scaffold with a textfield centered on the screen
          child: const Scaffold(
            body: Center(
              child: Padding(
                padding: EdgeInsets.symmetric(horizontal: 32.0),
                child: TextField(),
              ),
            ),
          ),
        );
      },
    );
  }
}
Logs ``` [√] Flutter (Channel stable, 3.3.8, on Microsoft Windows [Version 10.0.19044.2251], locale en-GB) • Flutter version 3.3.8 on channel stable at C:\Users\d.connolly\Documents\Dev\flutter_windows_3.0.1-stable\flutter • Upstream repository https://github.com/flutter/flutter.git • Framework revision 52b3dc2 (2 weeks ago), 2022-11-09 12:09:26 +0800 • Engine revision 857bd6b74c • Dart version 2.18.4 • DevTools version 2.15.0

[√] Android toolchain - develop for Android devices (Android SDK version 33.0.0)
• Android SDK at C:\Users\d.connolly\Documents\Dev\AndroidSDK
• Platform android-33, build-tools 33.0.0
• ANDROID_HOME = C:\Users\d.connolly\Documents\Dev\AndroidSDK
• Java binary at: C:\Program Files\Common Files\Oracle\Java\javapath\java.exe
• Java version Java(TM) SE Runtime Environment (build 17.0.1+12-LTS-39)
• All Android licenses accepted.

[√] Chrome - develop for the web
• Chrome at C:\Program Files\Google\Chrome\Application\chrome.exe

[X] Visual Studio - develop for Windows
X Visual Studio not installed; this is necessary for Windows development.
Download at https://visualstudio.microsoft.com/downloads/.
Please install the "Desktop development with C++" workload, including all of its default components

[!] Android Studio (not installed)
• Android Studio not found; download from https://developer.android.com/studio/index.html
(or visit https://flutter.dev/docs/get-started/install/windows#android-setup for detailed instructions).

[√] VS Code (version 1.70.0)
• VS Code at C:\Users\d.connolly\AppData\Local\Programs\Microsoft VS Code
• Flutter extension version 3.52.0

[√] Connected device (4 available)
• CPH2009 (mobile) • b1c697fc • android-arm64 • Android 12 (API 31)
• Windows (desktop) • windows • windows-x64 • Microsoft Windows [Version 10.0.19044.2251]
• Chrome (web) • chrome • web-javascript • Google Chrome 107.0.5304.107
• Edge (web) • edge • web-javascript • Microsoft Edge 107.0.1418.35

[√] HTTP Host Availability
• All required HTTP hosts are available

! Doctor found issues in 2 categories.


</details>
exaby73 wrote this answer on 2022-11-25

Hello @divillysausages. I cannot reproduce this issue with the code sample you provided. I have tested with an Android emulator (Pixel 4 API 33). Attached, you can find a video. Am I missing any steps?

Video
simplescreenrecorder-2022-11-25_13.00.43.mp4
divillysausages wrote this answer on 2022-11-25

Hi @exaby73 ,

Thanks for taking a look. In the Android emulator, can you verify that you're NOT seeing the log

[HOME] HOME SCREEN FUTURE BUILDER CALLED WITH STATE...

every time you click out of the TextField?

The behaviour is also visible on DartPad: https://dartpad.dev/?id=be1b63b2ee4ffa6128cf73afe0db29d9 - make sure the console is open and click in and out of the TextField a few times (attached image shows the output).

future_builder_bug

If you then change how the TextField is unfocused in onTapUp (lines 62-69), it no longer triggers if you use

FocusManager.instance.primaryFocus?.unfocus();

instead of

FocusScopeNode currentFocus = FocusScope.of(context);
if (!currentFocus.hasPrimaryFocus) {
  currentFocus.unfocus();
}
exaby73 wrote this answer on 2022-11-25

This is expected. Flutter can call the build method 60 times a second (even higher on high refresh displays). This does not mean Flutter is actually rebuilding the UI. It would compare the widget tree and if there is a change, only then it will rebuild.

From the docs of build():

This method can potentially be called in every frame and should not have any side effects beyond building a widget.

Since this is expected behavior, I am closing this issue. If you disagree, please comment and I can reopen it. Thank you

divillysausages wrote this answer on 2022-11-25

Can you explain why build() is called when

FocusScopeNode currentFocus = FocusScope.of(context);
if (!currentFocus.hasPrimaryFocus) {
  currentFocus.unfocus();
}

is used, but not when

FocusManager.instance.primaryFocus?.unfocus();

is used, please? That's the part that made me originally think it was a bug

exaby73 wrote this answer on 2022-11-25

I couldn't give you a reason why this happens. Flutter is quite complex. But all in all, you should build your app, expecting the build method to be called potentially every frame. The build method should not have any side effects like API calls or updates to state.

You might find better help from the community. Visit https://flutter.dev/community for resources and asking questions like this,
you may also get some help if you post it on Stack Overflow and if you need help with your code, please see https://www.reddit.com/r/flutterhelp/

More Details About Repo
Owner Name flutter
Repo Name flutter
Full Name flutter/flutter
Language Dart
Created Date 2015-03-06
Updated Date 2022-12-07
Star Count 147031
Watcher Count 3560
Fork Count 23915
Issue Count 11300

YOU MAY BE INTERESTED

Issue Title Created Date Updated Date