Calling FocusScopeNode.unfocus() causes FutureBuilder to rebuild

This issue has been created 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-10
Star Count 147243
Watcher Count 3561
Fork Count 23950
Issue Count 11325

YOU MAY BE INTERESTED

Issue Title Created Date Comment Count Updated Date
make request json with authorization 0 2022-04-28 2022-11-19
UnicodeDecodeError: 'gbk' codec can't decode byte 0xa6 in position 363: illegal multibyte sequence 5 2021-06-06 2022-11-03
The known vulnerability in the shared library zstd which FastAsyncWorldEdit-Core depends on.Can you help upgrade to patch versions? 1 2022-04-12 2022-09-20
Lack of ConfigureAwait(false) 5 2021-05-20 2022-06-19
Speeding up Inference 0 2021-08-19 2022-11-20
Bug in _mm_storel_epi64 1 2022-01-28 2022-11-06
Compatibility with DynamicalODEProblem 4 2020-07-23 2022-08-19
Support stacking in timeseries panel 0 2022-09-06 2022-11-21
Support `calculate` field for heatmap 1 2022-09-05 2022-11-21
Support for Snapshots 3 2022-09-13 2022-11-21
Allow Parallelization Core Limits 2 2022-09-13 2022-11-27
Error 2 on make retrojsvice.co on Raspberry 3 2 2021-03-01 2022-11-27
Hyperlinks on pages does not work anymore 3 2022-10-17 2022-11-06
P value (FDR) and interpretation of "Final Parameter" 2 2022-07-22 2022-11-11
Access denied when i try to connect with AWS S3 2 2019-06-26 2022-10-26
Feature request FlatList syntax 4 2022-08-01 2022-12-01
How to set placeholder in an inputbox 1 2022-03-05 2022-12-01
Link lable or link text 2 2022-03-05 2022-09-13
Left pane no longer scrolls and objects no longer expand 3 2022-01-27 2022-10-10
Compose button missing in recent Sanity releases 5 2021-12-28 2022-11-21
Confusion about XDG Base Directory specification and what is $XDG_DATA_DIR? 1 2022-06-28 2022-11-29
requiring react/addons takes a long time to resolve its dependencies 2 2017-08-04 2022-10-23
[Feature request] add template {{branchName}} to edit/open command 1 2022-02-25 2022-10-23
Unable to make request to raw IP address (Error: connect ECONNREFUSED) 1 2022-07-23 2022-12-07
[Bug]: Connection on the property pane shows "Appsmith" as incoming connection 0 2022-07-11 2022-09-30
[Bug]-[440]:Dropdown search behaviour is incorrect when search field does not match listed options 1 2022-07-11 2022-08-27
PrusaSlicer crashes when doing this commands 1 2021-12-13 2021-12-23
Incorrect files published 1 2021-01-26 2022-12-09
Sucrase support 1 2019-08-17 2022-01-10
Toolbar Button states cause pixel shifting 0 2022-02-15 2022-07-06
make fails for hub-tool 0 2022-07-07 2022-11-25
[email protected] strange visual bug 3 2021-06-21 2022-11-19
Volumes are not working for windows container. 0 2022-07-20 2022-12-04
Registry controller: renaming placeholder flag 0 2021-08-31 2022-12-01
Annotations on List and Create Artifacts is missing `locations/global` on some bindings. 0 2021-09-01 2022-10-07
handshake error -0x50 - NET - Connection was reset by peer 4 2021-12-08 2022-11-11
Access new or existing axios instance in custom module 0 2022-04-02 2022-12-05
Transparently support reading into arrays of types that are byte-compatible with integers 1 2021-12-21 2022-10-29
Rust is failing to find type definitions or enumerations 2 2021-03-25 2022-11-25
Invalid syntax of 'openssl list' command options 1 2022-08-17 2022-08-30
`"Undefined name 'x'"` in `lambda: x` before `del x` 1 2022-02-02 2022-12-02
Forgot 'subscriber_id:' in code snippet yaml 4 2021-12-12 2022-12-01
Add supported_features flags for optional Air Quality Sensor values 7 2020-01-03 2022-11-25
[Feature Request] Use a keyboard shortcut to trigger a window with content by html 5 2021-12-27 2022-12-09
[PowerToys Run] Programs can't find its files 0 2022-02-13 2022-09-13
Compression Handler Falsely Assumes All Raw Data is JSON 2 2021-10-05 2022-10-18
azurerm_api_management fails to apply on 3.10.0 provider 7 2022-06-10 2022-09-29
KeyError during ./extraction/full_process.sh {DATA_DIR} en 2 2018-02-09 2022-11-18
External custom setup 13 2016-10-28 2022-11-04
Dose TF-serving docker container support TensorRT,how can I use it? 0 2022-07-15 2022-11-28