Skip to content

DropdownMenu leaks parent's state when state is accessed in onSelected handler #187853

Description

@hamaaja

Steps to reproduce

  1. Start the app in profile mode
  2. Open DevTool Memoty profiler
  3. Take a head snapshot in profiler
  4. Switch tabs a few times in the app
  5. Take another heap snapshot in profiler
  6. Diff the first and the second snapshot
  7. Observe _HeavyWidgetState leaks

The leak can also be reproduced in release build and observed by watching the program memory usage. The reproducer has been tested on Linux.

Expected results

_HeavyWidgetState should not leak. I haven't noticed similar leaks in other event handlers (like ElevatedButton.onPressed or ListTile.onTap).

Actual results

_HeavyWidgetState leaks. The retaining path of _HeavyWidgetState is:

Closure Context
dart:core/_Closure
package:flutter/src/material/dropdown_menu.dart/DropdownMenu
package:flutter/src/material/dropdown_menu.dart/_DropdownMenuState
package:flutter/src/widgets/raw_menu_anchor.dart/RawMenuAnchor
package:flutter/src/widgets/raw_menu_anchor.dart/_RawMenuAnchorState
dart:core/_List
package:flutter/src/foundation/change_notifier.dart/ValueNotifier
package:flutter/src/widgets/page_view.dart/_PagePosition
package:flutter/src/widgets/scrollable.dart/_ScrollSemantics
package:flutter/src/widgets/framework.dart/SingleChildRenderObjectElement
dart:_compact_hash/_Map
package:flutter/src/widgets/framework.dart/BuildOwner
package:flutter/src/widgets/binding.dart/WidgetsFlutterBinding
Isolate

Code sample

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

void main() {
  runApp(const TabBarDemo());
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: DefaultTabController(
        length: 2,
        child: Scaffold(
          appBar: AppBar(
            bottom: const TabBar(
              tabs: [
                Tab(icon: Text('Heavy widget')),
                Tab(icon: Text('Empty')),
              ],
            ),
            title: const Text('Switch tabs to leak'),
          ),
          body: const TabBarView(children: [HeavyWidget(), SizedBox()]),
        ),
      ),
    );
  }
}

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

  @override
  State<HeavyWidget> createState() => _HeavyWidgetState();
}

class _HeavyWidgetState extends State<HeavyWidget> {
  // Simulates a big data object just to make it consume more memory.
  final data = List.generate(2_000_000, (i) => i);
  int? selected;

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        DropdownMenu(
          selectOnly: true,
          dropdownMenuEntries: data
              // Don't kill the dropdown at build, use less entries.
              .sublist(0, 20)
              .map((i) => DropdownMenuEntry(value: i, label: '$i'))
              .toList(),
          onSelected: (value) {
            // Commenting out the state modification removes the leak.
            setState(() {
              selected = value;
            });
          },
        ),
        Text('Selected: $selected'),
      ],
    );
  }
}

Screenshots or Video

Screenshots / Video demonstration

N/A

Logs

Logs

N/A

Flutter Doctor output

Doctor output
$ flutter doctor -v
[✓] Flutter (Channel stable, 3.44.1, on Fedora Linux 44 (KDE Plasma Desktop Edition) 7.0.12-200.fc44.x86_64, locale en_US.UTF-8) [62ms]
    • Flutter version 3.44.1 on channel stable at /home/nuumio/dev-tools/flutter
    • Upstream repository https://github.com/flutter/flutter.git
    • Framework revision 924134a44c (13 days ago), 2026-05-29 12:13:22 -0400
    • Engine revision c416acfeb8
    • Dart version 3.12.1
    • DevTools version 2.57.0
    • Feature flags: enable-web, enable-linux-desktop, enable-macos-desktop, enable-windows-desktop, enable-android, enable-ios, cli-animations, enable-native-assets,
      enable-swift-package-manager, omit-legacy-version-file, enable-lldb-debugging, enable-uiscene-migration

[✓] Android toolchain - develop for Android devices (Android SDK version 36.1.0) [1,559ms]
    • Android SDK at /home/nuumio/dev-tools/android/sdk
    • Emulator version 36.6.11.0 (build_id 15507667) (CL:N/A)
    • Platform android-36, build-tools 36.1.0
    • Java binary at: /home/nuumio/dev-tools/android/android-studio/jbr/bin/java
      This is the JDK bundled with the latest Android Studio installation on this machine.
      To manually set the JDK path, use: `flutter config --jdk-dir="path/to/jdk"`.
    • Java version OpenJDK Runtime Environment (build 21.0.6+-13368085-b895.109)
    • All Android licenses accepted.

[✓] Chrome - develop for the web [13ms]
    • CHROME_EXECUTABLE = /usr/bin/chromium-browser

[✓] Linux toolchain - develop for Linux desktop [457ms]
    • clang version 22.1.6 (Fedora 22.1.6-1.fc44)
    • cmake version 4.3.0
    • ninja version 1.13.2
    • pkg-config version 2.5.1
    • OpenGL core renderer: AMD Radeon RX 7600 (radeonsi, navi33, ACO, DRM 3.64, 7.0.12-200.fc44.x86_64)
    • OpenGL core version: 4.6 (Core Profile) Mesa 26.0.8
    • OpenGL core shading language version: 4.60
    • OpenGL ES renderer: AMD Radeon RX 7600 (radeonsi, navi33, ACO, DRM 3.64, 7.0.12-200.fc44.x86_64)
    • OpenGL ES version: OpenGL ES 3.2 Mesa 26.0.8
    • OpenGL ES shading language version: OpenGL ES GLSL ES 3.20
    • GL_EXT_framebuffer_blit: yes
    • GL_EXT_texture_format_BGRA8888: yes

[✓] Connected device (2 available) [349ms]
    • Linux (desktop) • linux  • linux-x64      • Fedora Linux 44 (KDE Plasma Desktop Edition) 7.0.12-200.fc44.x86_64
    • Chrome (web)    • chrome • web-javascript • Chromium 149.0.7827.53 Built from source for Fedora release 44 (Forty Four)

[✓] Network resources [385ms]
    • All expected network resources are available.

• No issues found!

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions