Skip to content

DropdownMenuItem does not support use GlobalKey #80200

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
braxtonmckee opened this issue Apr 10, 2021 · 12 comments
Open

DropdownMenuItem does not support use GlobalKey #80200

braxtonmckee opened this issue Apr 10, 2021 · 12 comments
Labels
f: material design flutter/packages/flutter/material repository. found in release: 2.0 Found to occur in 2.0 found in release: 2.1 Found to occur in 2.1 framework flutter/packages/flutter repository. See also f: labels. has reproducible steps The issue has been confirmed reproducible and is ready to work on P3 Issues that are less important to the Flutter project team-design Owned by Design Languages team triaged-design Triaged by Design Languages team

Comments

@braxtonmckee
Copy link

braxtonmckee commented Apr 10, 2021

I'm getting exceptions when using widgets that contain GlobalKeys inside of some widgets, in particular DropdownButton. Am I using it wrong? Based on what I have found on the web, this appears to be the way its supposed to be used.

Here's a simple repro:

code sample
import 'package:flutter/material.dart';

void main() => runApp(GlobalKeyTestApp());

class GlobalKeyTestApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      routes: {
        '/': (context) => TestWidget(),
      },
    );
  }
}

class TestWidget extends StatefulWidget {
  @override
  TestWidgetState createState() => TestWidgetState();
}

class TestWidgetState extends State<TestWidget> {
  List<DropdownMenuItem> items = List<DropdownMenuItem>();

  @override
  Widget build(BuildContext contets) {
    if (items.length == 0) {
      print("BUILD ITEMS (this only happens once)");

      int i = 0;
      while (i < 3) {
        items.add(
          DropdownMenuItem(
            child: KeyedSubtree(
              key: GlobalKey(debugLabel: "item" + i.toString()), 
              child: 
                Container(
                  child: Text("item " + i.toString())
                ), 
              ),
            value: i
          )
        );
        i = i + 1;
      }
    }

    return Scaffold(body: 
      DropdownButton(
        hint: Text("hint"),
        items: items,
        onChanged: (value) {
          print("SET TO " + value.toString());
        }
      )
    );
  }
}

Steps to Reproduce

Run the app and click the dropdown. You'll see an exception thrown internally.

══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════
The following assertion was thrown while finalizing the widget tree:
Duplicate GlobalKeys detected in widget tree.
The following GlobalKeys were specified multiple times in the widget tree. This will lead to parts
of the widget tree being truncated unexpectedly, because the second time a key is seen, the previous
instance is moved to the new location. The keys were:
- [GlobalKey#ceb94 item0]
  [GlobalKey#599b0 item1]
  [GlobalKey#d040d item2]

Expected results:

It not throwing exceptions.

Actual results:

It throws exceptions.

Here's the result of flutter run --verbose
[✓] Flutter (Channel stable, 2.0.4, on Microsoft Windows [Version 10.0.19042.870], locale en-US)
    • Flutter version 2.0.4 at C:\Users\Taha\Code\flutter_stable
    • Framework revision b1395592de (11 days ago), 2021-04-01 14:25:01 -0700
    • Engine revision 2dce47073a
    • Dart version 2.12.2

[!] Android toolchain - develop for Android devices (Android SDK version 30.0.3)
    • Android SDK at C:\Users\Taha\Code\SDK
    • Platform android-30, build-tools 30.0.3
    • ANDROID_HOME = C:\Users\Taha\Code\SDK
    • Java binary at: C:\Users\Taha\Code\android-studio\jre\bin\java
    • Java version OpenJDK Runtime Environment (build 1.8.0_242-release-1644-b01)
    ✗ Android license status unknown.
      Run `flutter doctor --android-licenses` to accept the SDK licenses.
      See https://flutter.dev/docs/get-started/install/windows#android-setup for more details.

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

[✓] Visual Studio - develop for Windows (Visual Studio Community 2019 16.9.2)
    • Visual Studio at C:\Program Files (x86)\Microsoft Visual Studio\2019\Community
    • Visual Studio Community 2019 version 16.9.31112.23
    • Windows 10 SDK version 10.0.19041.0

[✓] Android Studio (version 4.1.0)
    • Android Studio at C:\Users\Taha\Code\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 1.8.0_242-release-1644-b01)

[✓] IntelliJ IDEA Community Edition (version 2021.1)
    • IntelliJ at C:\Program Files\JetBrains\IntelliJ IDEA Community Edition 2020.3.3
    • Flutter plugin version 55.0.5
    • Dart plugin version 211.6693.108

[✓] VS Code (version 1.55.1)
    • VS Code at C:\Users\Taha\AppData\Local\Programs\Microsoft VS Code
    • Flutter extension version 3.21.0

[✓] Connected device (4 available)
    • RMX2001 (mobile)  • EUYTFEUSQSRGDA6D • android-arm64  • Android 10 (API 29)
    • Windows (desktop) • windows          • windows-x64    • Microsoft Windows [Version 10.0.19042.870]
    • Chrome (web)      • chrome           • web-javascript • Google Chrome 89.0.4389.114
    • Edge (web)        • edge             • web-javascript • Microsoft Edge 89.0.774.68

! Doctor found issues in 1 category.
(.venv) braxton@pop-os:~/code/flutter_app/bug$ flutter run --verbose
[  +39 ms] executing: uname -m
[  +19 ms] Exit code 0 from: uname -m
[        ] x86_64
[   +7 ms] executing: [/home/braxton/snap/flutter/common/flutter/] git -c log.showSignature=false log -n 1 --pretty=format:%H
[   +3 ms] Exit code 0 from: git -c log.showSignature=false log -n 1 --pretty=format:%H
[        ] 8264cb3e8a797eef39cbcd32bb56fd07790efb7f
[        ] executing: [/home/braxton/snap/flutter/common/flutter/] git tag --points-at 8264cb3e8a797eef39cbcd32bb56fd07790efb7f
[   +6 ms] Exit code 0 from: git tag --points-at 8264cb3e8a797eef39cbcd32bb56fd07790efb7f
[        ] 2.1.0-12.1.pre
[  +23 ms] executing: [/home/braxton/snap/flutter/common/flutter/] git rev-parse --abbrev-ref --symbolic @{u}
[   +3 ms] Exit code 0 from: git rev-parse --abbrev-ref --symbolic @{u}
[        ] origin/dev
[        ] executing: [/home/braxton/snap/flutter/common/flutter/] git ls-remote --get-url origin
[   +2 ms] Exit code 0 from: git ls-remote --get-url origin
[        ] https://github.com/flutter/flutter.git
[  +18 ms] Unable to locate an Android SDK.
[   +2 ms] executing: [/home/braxton/snap/flutter/common/flutter/] git rev-parse --abbrev-ref HEAD
[   +2 ms] Exit code 0 from: git rev-parse --abbrev-ref HEAD
[        ] dev
[  +31 ms] Artifact Instance of 'AndroidGenSnapshotArtifacts' is not required, skipping update.
[        ] Artifact Instance of 'AndroidInternalBuildArtifacts' is not required, skipping update.
[        ] Artifact Instance of 'IOSEngineArtifacts' is not required, skipping update.
[        ] Artifact Instance of 'FlutterWebSdk' is not required, skipping update.
[   +1 ms] Artifact Instance of 'WindowsEngineArtifacts' is not required, skipping update.
[        ] Artifact Instance of 'MacOSEngineArtifacts' is not required, skipping update.
[        ] Artifact Instance of 'LinuxEngineArtifacts' is not required, skipping update.
[        ] Artifact Instance of 'LinuxFuchsiaSDKArtifacts' is not required, skipping update.
[        ] Artifact Instance of 'MacOSFuchsiaSDKArtifacts' is not required, skipping update.
[        ] Artifact Instance of 'FlutterRunnerSDKArtifacts' is not required, skipping update.
[        ] Artifact Instance of 'FlutterRunnerDebugSymbols' is not required, skipping update.
[  +40 ms] Artifact Instance of 'AndroidGenSnapshotArtifacts' is not required, skipping update.
[        ] Artifact Instance of 'AndroidInternalBuildArtifacts' is not required, skipping update.
[        ] Artifact Instance of 'IOSEngineArtifacts' is not required, skipping update.
[        ] Artifact Instance of 'WindowsEngineArtifacts' is not required, skipping update.
[        ] Artifact Instance of 'MacOSEngineArtifacts' is not required, skipping update.
[        ] Artifact Instance of 'LinuxFuchsiaSDKArtifacts' is not required, skipping update.
[        ] Artifact Instance of 'MacOSFuchsiaSDKArtifacts' is not required, skipping update.
[        ] Artifact Instance of 'FlutterRunnerSDKArtifacts' is not required, skipping update.
[        ] Artifact Instance of 'FlutterRunnerDebugSymbols' is not required, skipping update.
[  +36 ms] Multiple devices found:
[  +32 ms] Linux (desktop) • linux  • linux-x64      • Linux
[        ] Chrome (web)    • chrome • web-javascript • Google Chrome 89.0.4389.114
[        ] [1]: Linux (linux)
[        ] [2]: Chrome (chrome)
[        ] Please choose one (To quit, press "q/Q")
[        ] : 
[+5534 ms] 2
[   +6 ms] Skipping pub get: version match.
[  +67 ms] Generating /home/braxton/code/flutter_app/bug/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java
[  +56 ms] Launching lib/main.dart on Chrome in debug mode...
[  +65 ms] Updating assets
[  +39 ms] Waiting for connection from debug service on Chrome...
[  +71 ms] <- reset
[   +3 ms] /home/braxton/snap/flutter/common/flutter/bin/cache/dart-sdk/bin/dart --disable-dart-dev /home/braxton/snap/flutter/common/flutter/bin/cache/artifacts/engine/linux-x64/frontend_server.dart.snapshot --sdk-root /home/braxton/snap/flutter/common/flutter/bin/cache/flutter_web_sdk/ --incremental
--target=dartdevc --debugger-module-names --experimental-emit-debug-metadata -DFLUTTER_WEB_AUTO_DETECT=true --output-dill /tmp/flutter_tools.CUKVSZ/flutter_tool.QDRJAN/app.dill --libraries-spec file:///home/braxton/snap/flutter/common/flutter/bin/cache/flutter_web_sdk/libraries.json --packages
/home/braxton/code/flutter_app/bug/.dart_tool/package_config.json -Ddart.vm.profile=false -Ddart.vm.product=false --enable-asserts --track-widget-creation --filesystem-root /tmp/flutter_tools.CUKVSZ/flutter_tools.ZBAADP --filesystem-scheme org-dartlang-app --initialize-from-dill
build/c7922d95bf4a2462b75c84a97c312edb.cache.dill.track.dill --platform file:///home/braxton/snap/flutter/common/flutter/bin/cache/flutter_web_sdk/kernel/flutter_ddc_sdk.dill --no-sound-null-safety
[   +7 ms] <- compile org-dartlang-app:/web_entrypoint.dart
[+10607 ms] Waiting for connection from debug service on Chrome... (completed in 10.7s)
[        ] Synced 29.3MB.
[        ] <- accept
[        ] Caching compiled dill
[  +84 ms] Using Google Chrome 89.0.4389.114 

[ +121 ms] [CHROME]:
[        ] [CHROME]:DevTools listening on ws://127.0.0.1:36197/devtools/browser/7f5d7e07-73f0-4be0-9a13-798b92a36127
[ +170 ms] DwdsInjector: Received request for entrypoint at http://localhost:44287/main_module.bootstrap.js
[   +3 ms] MetadataProvider: Loading debug metadata...
[   +7 ms] MetadataProvider: Loaded debug metadata (no sound null safety)
[   +4 ms] DwdsInjector: Injected debugging metadata for entrypoint at http://localhost:44287/main_module.bootstrap.js
[ +861 ms] ChromeProxyService: Initializing expression compiler for main_module.bootstrap.js with sound null safety: false
[ +284 ms] DevHandler: Debug service listening on ws://127.0.0.1:46709/mqXld0dydsQ=/ws

[   +4 ms] Debug service listening on ws://127.0.0.1:46709/mqXld0dydsQ=/ws
[        ] Running with unsound null safety
[        ] For more information see https://dart.dev/null-safety/unsound-null-safety
[   +1 ms] 🔥  To hot restart changes while running, press "r" or "R".
[        ] For a more detailed help message, press "h". To quit, press "q".
[+1119 ms] BUILD ITEMS (this only happens once)
[+2095 ms] ══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════
[        ] The following assertion was thrown while finalizing the widget tree:
[        ] Duplicate GlobalKeys detected in widget tree.
[   +1 ms] The following GlobalKeys were specified multiple times in the widget tree. This will lead to parts
[        ] of the widget tree being truncated unexpectedly, because the second time a key is seen, the previous
[        ] instance is moved to the new location. The keys were:
[        ] - [GlobalKey#aaccd item0]
[        ]   [GlobalKey#d1585 item1]
[        ]   [GlobalKey#33e95 item2]
[        ] This was determined by noticing that after widgets with the above global keys were moved out of
[        ] their respective previous parents, those previous parents never updated during this frame, meaning
[        ] that they either did not update at all or updated before the widgets were moved, in either case
[        ] implying that they still think that they should have a child with those global keys.
[        ] The specific parents that did not update after having one or more children forcibly removed due to
[   +1 ms] GlobalKey reparenting are:
[        ] - Align(alignment: AlignmentDirectional.centerStart, dependencies: [Directionality], renderObject:
[        ] RenderPositionedBox#42fa4 relayoutBoundary=up11 NEEDS-PAINT)
[        ]   Align(alignment: AlignmentDirectional.centerStart, dependencies: [Directionality], renderObject:
[        ] RenderPositionedBox#18a54 relayoutBoundary=up11 NEEDS-PAINT)
[        ]   Align(alignment: AlignmentDirectional.centerStart, dependencies: [Directionality], renderObject:
[        ] RenderPositionedBox#3c2fe relayoutBoundary=up11 NEEDS-PAINT)
[        ] A GlobalKey can only be specified on one widget at a time in the widget tree.
[        ] When the exception was thrown, this was the stack:
[        ] dart-sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/errors.dart 236:49  throw_
[        ] packages/flutter/src/widgets/framework.dart 2896:15                           <fn>
[        ] packages/flutter/src/widgets/framework.dart 2920:16                           finalizeTree
[        ] packages/flutter/src/widgets/binding.dart 877:7                               drawFrame
[        ] packages/flutter/src/rendering/binding.dart 329:5                             [_handlePersistentFrameCallback]
[        ] packages/flutter/src/scheduler/binding.dart 1144:15                           [_invokeFrameCallback]
[        ] packages/flutter/src/scheduler/binding.dart 1082:9                            handleDrawFrame
[        ] packages/flutter/src/scheduler/binding.dart 998:5                             [_handleDrawFrame]
[        ] lib/_engine/engine/platform_dispatcher.dart 931:13                            invoke
[        ] lib/_engine/engine/platform_dispatcher.dart 147:5                             invokeOnDrawFrame
[        ] lib/_engine/engine.dart 262:45                                                <fn>
[        ] ════════════════════════════════════════════════════════════════════════════════════════════════════
[+1063 ms] SET TO 1

flutter doctor

[✓] Flutter (Channel dev, 2.1.0-12.1.pre, on Linux, locale en_US.UTF-8)
    • Flutter version 2.1.0-12.1.pre at /home/braxton/snap/flutter/common/flutter
    • Framework revision 8264cb3e8a (4 weeks ago), 2021-03-10 12:37:57 -0800
    • Engine revision 711ab3fda0
    • Dart version 2.13.0 (build 2.13.0-116.0.dev)

[✗] Android toolchain - develop for Android devices
    ✗ Unable to locate Android SDK.
      Install Android Studio from: https://developer.android.com/studio/index.html
      On first launch it will assist you in installing the Android SDK components.
      (or visit https://flutter.dev/docs/get-started/install/linux#android-setup for detailed instructions).
      If the Android SDK has been installed to a custom location, please use
      `flutter config --android-sdk` to update to that location.


[✓] Chrome - develop for the web
    • Chrome at google-chrome

[✓] Linux toolchain - develop for Linux desktop
    • clang version 10.0.0-4ubuntu1
    • cmake version 3.16.3
    • ninja version 1.10.0
    • pkg-config version 0.29.1

[!] 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/linux#android-setup for detailed instructions).

[✓] Connected device (2 available)
    • Linux (desktop) • linux  • linux-x64      • Linux
    • Chrome (web)    • chrome • web-javascript • Google Chrome 89.0.4389.114

! Doctor found issues in 2 categories.
@iapicca
Copy link
Contributor

iapicca commented Apr 10, 2021

@braxtonmckee
I suspect that global keys aren't meant to be used this way especially

  • not to be "moved in the widget tree" paraphrasing the error [...] the previous instance is moved to the new location
  • to be used for a State
  • not to be instantiated inside the build method

a good practice is to let a State object own the GlobalKey, and instantiate it outside the build method

(source)

may I ask what this implementation is meant to accomplish and why needs to use GlobalKey?

@braxtonmckee
Copy link
Author

braxtonmckee commented Apr 10, 2021 via email

@TahaTesser TahaTesser added the in triage Presently being triaged by the triage team label Apr 12, 2021
@TahaTesser
Copy link
Member

Hi @braxtonmckee
Thanks for filing the issue

flutter doctor -v
[✓] Flutter (Channel stable, 2.0.4, on Microsoft Windows [Version 10.0.19042.870], locale en-US)
    • Flutter version 2.0.4 at C:\Users\Taha\Code\flutter_stable
    • Framework revision b1395592de (11 days ago), 2021-04-01 14:25:01 -0700
    • Engine revision 2dce47073a
    • Dart version 2.12.2

[!] Android toolchain - develop for Android devices (Android SDK version 30.0.3)
    • Android SDK at C:\Users\Taha\Code\SDK
    • Platform android-30, build-tools 30.0.3
    • ANDROID_HOME = C:\Users\Taha\Code\SDK
    • Java binary at: C:\Users\Taha\Code\android-studio\jre\bin\java
    • Java version OpenJDK Runtime Environment (build 1.8.0_242-release-1644-b01)
    ✗ Android license status unknown.
      Run `flutter doctor --android-licenses` to accept the SDK licenses.
      See https://flutter.dev/docs/get-started/install/windows#android-setup for more details.

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

[✓] Visual Studio - develop for Windows (Visual Studio Community 2019 16.9.2)
    • Visual Studio at C:\Program Files (x86)\Microsoft Visual Studio\2019\Community
    • Visual Studio Community 2019 version 16.9.31112.23
    • Windows 10 SDK version 10.0.19041.0

[✓] Android Studio (version 4.1.0)
    • Android Studio at C:\Users\Taha\Code\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 1.8.0_242-release-1644-b01)

[✓] IntelliJ IDEA Community Edition (version 2021.1)
    • IntelliJ at C:\Program Files\JetBrains\IntelliJ IDEA Community Edition 2020.3.3
    • Flutter plugin version 55.0.5
    • Dart plugin version 211.6693.108

[✓] VS Code (version 1.55.1)
    • VS Code at C:\Users\Taha\AppData\Local\Programs\Microsoft VS Code
    • Flutter extension version 3.21.0

[✓] Connected device (4 available)
    • RMX2001 (mobile)  • EUYTFEUSQSRGDA6D • android-arm64  • Android 10 (API 29)
    • Windows (desktop) • windows          • windows-x64    • Microsoft Windows [Version 10.0.19042.870]
    • Chrome (web)      • chrome           • web-javascript • Google Chrome 89.0.4389.114
    • Edge (web)        • edge             • web-javascript • Microsoft Edge 89.0.774.68

! Doctor found issues in 1 category.
[✓] Flutter (Channel master, 2.1.0-13.0.pre.574, on Microsoft Windows [Version 10.0.19042.870], locale en-US)
    • Flutter version 2.1.0-13.0.pre.574 at C:\Users\Taha\Code\flutter_master
    • Framework revision 02efffc134 (2 days ago), 2021-04-10 03:49:01 -0400
    • Engine revision 8863afff16
    • Dart version 2.13.0 (build 2.13.0-222.0.dev)

[!] Android toolchain - develop for Android devices (Android SDK version 30.0.3)
    • Android SDK at C:\Users\Taha\Code\SDK
    • Platform android-30, build-tools 30.0.3
    • ANDROID_HOME = C:\Users\Taha\Code\SDK
    • Java binary at: C:\Users\Taha\Code\android-studio\jre\bin\java
    • Java version OpenJDK Runtime Environment (build 1.8.0_242-release-1644-b01)
    ✗ Android license status unknown.
      Run `flutter doctor --android-licenses` to accept the SDK licenses.
      See https://flutter.dev/docs/get-started/install/windows#android-setup for more details.

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

[✓] Visual Studio - develop for Windows (Visual Studio Community 2019 16.9.2)
    • Visual Studio at C:\Program Files (x86)\Microsoft Visual Studio\2019\Community
    • Visual Studio Community 2019 version 16.9.31112.23
    • Windows 10 SDK version 10.0.19041.0

[✓] Android Studio (version 4.1.0)
    • Android Studio at C:\Users\Taha\Code\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 1.8.0_242-release-1644-b01)

[✓] IntelliJ IDEA Community Edition (version 2021.1)
    • IntelliJ at C:\Program Files\JetBrains\IntelliJ IDEA Community Edition 2020.3.3
    • Flutter plugin version 55.0.5
    • Dart plugin version 211.6693.108

[✓] VS Code (version 1.55.1)
    • VS Code at C:\Users\Taha\AppData\Local\Programs\Microsoft VS Code
    • Flutter extension version 3.21.0

[✓] Connected device (4 available)
    • RMX2001 (mobile)  • EUYTFEUSQSRGDA6D • android-arm64  • Android 10 (API 29)
    • Windows (desktop) • windows          • windows-x64    • Microsoft Windows [Version 10.0.19042.870]
    • Chrome (web)      • chrome           • web-javascript • Google Chrome 89.0.4389.114
    • Edge (web)        • edge             • web-javascript • Microsoft Edge 89.0.774.68

! Doctor found issues in 1 category.

@TahaTesser TahaTesser added f: material design flutter/packages/flutter/material repository. found in release: 2.0 Found to occur in 2.0 found in release: 2.1 Found to occur in 2.1 framework flutter/packages/flutter repository. See also f: labels. has reproducible steps The issue has been confirmed reproducible and is ready to work on and removed in triage Presently being triaged by the triage team labels Apr 12, 2021
@werainkhatri
Copy link
Member

This is a minimal repro of a bug that I encountered building a larger app.
If you had state inside of a dropdown item that you wanted to maintain, how else would you do it?

Can you elaborate on what state are you trying to preserve?

Also, if you look into the widget tree when the DropdownButton is tapped, it has 2 instances of each item widget. One in DropdownButton in IndexedStack and other in the DropdownRoute. This is the reason duplicate GlobalKeys are detected.

Screenshot from 2021-04-14 04-32-03

@braxtonmckee
Copy link
Author

Hi @werainkhatri,

Thanks for getting back to me.

This is a minimal repro, so maybe it seems contrived, but I'm happy to provide some context if it helps motivate the issue.

In my application, I have a tree of UI widgets that get populated over a socket connection (this is a remote desktop application). I'm mirroring this tree into the widget tree. The GlobalKey abstraction should make it possible for me maintain a 1-1 correspondence between flutter widgets and nodes in my tree as identified by my server.

This is perfect for my use case, since that's exactly how my remoting protocol works. Every node in the tree has a unique identity, and each change to the tree on the server side sends a message to me giving the identity of the changed node and the change itself. For instance, if a list-view sees its items reordered, I'll receive an update list of ordered child identities. This should be a perfect analogy to the GlobalKey model: I simply reorder the children in the widget, all of which are identified by GlobalKey objects, and then the rendering layer can reorder them without needing to rebuild the entire tree or destroy the ui state.

For most widgets this works great. Except I'm finding some widgets complain (as is the case with the DropdownButton) if any widget inside of them has a GlobalKey because they expect that their children are "simple" and don't have GlobalKeys inside.

Looking at the tree above, to me it feels like the widgets should only show up in one place in the tree at once since they're only visible in one place at once on the screen. If the point of the MaterialApp is to provide a layer to host animations and popovers, then why do the menu item widgets also need to show up underneath the DropdownButton object in the tree?

In any case, I suppose I could imagine that some widgets expect their children to be stateless, and cannot accept globally keyed widgets or something, but then that should be documented. But really it just seems like a bug that the widget is in the tree twice. After all, my build method only got called once, which means the DropdownMenuItem objects were only called once, and yet they are showing up in two separate places in the tree.

Anyways, thanks again for looking at this.

--braxton

@xu-baolin
Copy link
Member

xu-baolin commented Apr 23, 2021

When the button is opened, the item will attach to two different subtrees at the same time, so if the item or its descendants have a GlobalKey, an assertion will be triggered.
@shihaohong @chunhtai,
Do you have any suggestions to solve this problem?
If we move the items to the popup menu when open the menu it will break something.

Sample Code

/// Flutter code sample for DropdownButton

// This sample shows a `DropdownButton` with a large arrow icon,
// purple text style, and bold purple underline, whose value is one of "One",
// "Two", "Free", or "Four".
//
// ![](https://flutter.github.io/assets-for-api-docs/assets/material/dropdown_button.png)

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

/// This is the main application widget.
class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  static const String _title = 'Flutter Code Sample';

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: _title,
      home: Scaffold(
        appBar: AppBar(title: const Text(_title)),
        body: const Center(
          child: MyStatefulWidget(),
        ),
      ),
    );
  }
}

/// This is the stateful widget that the main application instantiates.
class MyStatefulWidget extends StatefulWidget {
  const MyStatefulWidget({Key? key}) : super(key: key);

  @override
  _MyStatefulWidgetState createState() => _MyStatefulWidgetState();
}

/// This is the private State class that goes with MyStatefulWidget.
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
  String dropdownValue = 'One';
  GlobalKey _key = GlobalKey();

  @override
  Widget build(BuildContext context) {
    return DropdownButton<String>(
      value: dropdownValue,
      icon: const Icon(Icons.arrow_downward),
      iconSize: 24,
      elevation: 16,
      style: const TextStyle(color: Colors.deepPurple),
      underline: Container(
        height: 2,
        color: Colors.deepPurpleAccent,
      ),
      onChanged: (String? newValue) {
        setState(() {
          dropdownValue = newValue!;
        });
      },
      items: <String>['One', 'Two', 'Free', 'Four']
          .map<DropdownMenuItem<String>>((String value) {
        return DropdownMenuItem<String>(
          key: (value == 'One') ? _key : null,
          value: value,
          child: Text(value),
        );
      }).toList(),
    );
  }
}

@xu-baolin xu-baolin changed the title GlobalKey doesn't seem to work correctly when used in the items of a DropdownButton widget DropdownMenuItem does not support use GlobalKey Apr 23, 2021
@shihaohong
Copy link
Contributor

shihaohong commented May 4, 2021

@xu-baolin It's unfortunate because we end up using the passed in DropdownMenuItem widgets in two separate parts of the widget tree. One way I see that we can move forward is to document that global keys do not play nice with DropdownMenuItems (and perhaps assert it as well). Then, one workaround that would help with @braxtonmckee's particular situation is to use the selectedItemBuilder parameter. What that ends up doing is it customizes the widget subtree within the DropdownButton, thereby allowing two sets of global keys to be used. From there, the only extra thing that needs to be done is to make the button appearance identical to the default UI appearance. That should be as simple as wrapping the dropdown button widget in a DropdownMenuItem

@chunhtai
Copy link
Contributor

chunhtai commented May 4, 2021

we end up using the passed in DropdownMenuItem widgets in two separate parts of the widget tree.

That sounds like something we should not do. Do you know why we do that?

@shihaohong
Copy link
Contributor

I think it's because the widget styling for both the button and the dropdown menu item are identical in the default scenario, so items ends up being reused for both. The IndexedStack displays the item that has been selected, and the items are also displayed in the dropdown route when the button is selected/tapped. I agree that I think that this is something that we shouldn't do. I wonder if there's an easy way to decouple the two. I'll play around with the code for a while today to see.

@shihaohong
Copy link
Contributor

shihaohong commented May 5, 2021

Here's a WIP proof of concept that fixes the GlobalKey issue: #81878. Tested this with the code sample that @xu-baolin provided that reproduces the problem, and no errors pop up. It basically just removes the usage of GlobalKeys in the dropdown menu items that are passed into the IndexedStack, but keeps everything else identical.

The only thing that's missing is how we'd provide keys to the menu items in the IndexedStack. We could basically create a new parameter for DropdownButton that takes a List<Key> and then add them when the button items are created. However, this feels wrong and like an anti-pattern. I would prefer that for the default case, we just do not allow the dropdown button items that go into the IndexedStack to be keyed. If the user requires them to be keyed, then we could recommend users to use selectedItemBuilder.

Edit: Never mind, it seems like this approach is a breaking change because there are behaviors that rely on the usage of keys for the dropdown menu items. I also suspect this would break a whole bunch of user code
Edit2: @xu-baolin also pointed out that the child of DropdownMenuItem could also contain global keys, so this approach would not work for that reason as well.

@werainkhatri
Copy link
Member

werainkhatri commented May 5, 2021

Here's an idea. Can we remove the items from the IndexedStack when the route is pushed? Then the items will exist once in the widget tree.

Series of events will be -
DropDownButton is pressed -> items are removed from IndexedStack -> setState is called -> route is pushed.

The only problem I think this will have is performance and possibly UX issues.

@shihaohong
Copy link
Contributor

The problem with that approach is that IndexedStack is used to determine the size of the button. If we were to remove all the items from IndexedStack, it would definitely trigger some kind of layout behavior changes, which is undesired.

@flutter-triage-bot flutter-triage-bot bot added multiteam-retriage-candidate team-design Owned by Design Languages team triaged-design Triaged by Design Languages team labels Jul 8, 2023
@Piinks Piinks added the P3 Issues that are less important to the Flutter project label May 21, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
f: material design flutter/packages/flutter/material repository. found in release: 2.0 Found to occur in 2.0 found in release: 2.1 Found to occur in 2.1 framework flutter/packages/flutter repository. See also f: labels. has reproducible steps The issue has been confirmed reproducible and is ready to work on P3 Issues that are less important to the Flutter project team-design Owned by Design Languages team triaged-design Triaged by Design Languages team
Projects
None yet
Development

Successfully merging a pull request may close this issue.

9 participants
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy