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.51k stars 27.32k forks source link

GlobalKey is corrupted when it used with child ListView widget #151776

Closed pic16f887 closed 4 weeks ago

pic16f887 commented 3 months ago

Steps to reproduce

  1. Press Action button.
  2. Check _widgetKey.currentState in actionHandler.

Expected results

_widgetKey should have valid currentState, currentWidgetvalues

Actual results

_widgetKey has null values for currentState, currentWidget and other fields

Code sample

Code sample ```dart import 'package:flutter/material.dart'; void main() { runApp(const MyApp()); } class MyPage extends StatefulWidget { const MyPage({super.key}); @override MyPageState createState() => MyPageState(); } class MyPageState extends State { ListBody _widgets = const ListBody(); final GlobalKey _widgetKey = GlobalKey(); Widget inputWidget = const TextField(); @override void initState() { setState(() { inputWidget = TextField(key: _widgetKey); _widgets = widgets(context); }); super.initState(); } @override Widget build(BuildContext context) { var widget = Scaffold( body: ListView( children: [ inputWidget, const Text("Widgets"), _widgets, TextButton(onPressed: actionHandler, child: const Text("Action")), ], ), ); return widget; } void actionHandler() async { if (_widgetKey.currentState == null) { debugPrint("state is null"); } } ListBody widgets(BuildContext context) { return ListBody( children: List.generate(18, (index) { return const TextField(); })); } } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), useMaterial3: true, ), home: const MyHomePage(title: 'Flutter Demo Home Page'), ); } } class MyHomePage extends StatefulWidget { const MyHomePage({super.key, required this.title}); final String title; @override State createState() => _MyHomePageState(); } class _MyHomePageState extends State { @override void initState() { super.initState(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( backgroundColor: Theme.of(context).colorScheme.inversePrimary, title: Text(widget.title), ), body: const Center(child: MyPage()), ); } } ```

Screenshots or Video

Screenshots / Video demonstration [Upload media here]

Logs

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

Flutter Doctor output

Doctor output ```console flutter doctor Doctor summary (to see all details, run flutter doctor -v): [√] Flutter (Channel stable, 3.22.2, on Microsoft Windows [Version 10.0.22631.3880], locale uk-UA) [√] Windows Version (Installed version of Windows is version 10 or higher) [√] Android toolchain - develop for Android devices (Android SDK version 33.0.0) [√] Chrome - develop for the web [√] Visual Studio - develop Windows apps (Visual Studio Community 2022 17.8.3) [√] Android Studio (version 2021.2) [√] IntelliJ IDEA Community Edition (version 2023.1) [√] VS Code (version 1.91.1) [√] Connected device (3 available) [√] Network resources • No issues found! ```
pic16f887 commented 3 months ago

It's important to note if widgets method generates less than 18 children widgets then everything works as expected.

darshankawar commented 3 months ago

Thanks for the report @pic16f887 Can you check https://stackoverflow.com/questions/75274817/global-key-is-not-restoring-state-sometimes-of-child-widget and see if it helps in your case or not ?

pic16f887 commented 3 months ago

@darshankawar I am not sure that these issues are the same as I am passing _widgetKeyto the TextField(key: _widgetKey) only once. And even when I pass key in build method the result is the same. The problem is reproduced only when ListView contains less than some number of widgets (in my case if widgets generates more 18 widgets) everything works as expected. When I replacing ListViewfor SingleChildScrollViewand Column as a Child the problem is gone.

darshankawar commented 3 months ago

Thanks for the update. I think it works with less than 18 widgets because it can render all that are available on screen. For 18, some of the elements are off-the screen so they may not be counted. Can you try with ListView.builder to get the items by their index, you can also try to pass reverse: true to access children on demand.

pic16f887 commented 3 months ago

I think it works with less than 18 widgets because it can render all that are available on screen. For 18, some of the elements are off-the screen so they may not be counted. Can you try with ListView.builder to get the items by their index, you can also try to pass reverse: true to access children on demand.

@darshankawar When I add 17 widgets in addition to the inputWidget and Textthat has globalKey, some elements are off the screen but I don't observe this issue. ListView.builder doesn't solve the issue.

darshankawar commented 2 months ago

Thanks for the update. I am not sure if this is a bug or not. With 18 elements, it prints:

I/flutter (23250): state is null

With less than 18, it does not. Will keep the issue open for team's tracking.

stable, master flutter doctor -v ``` [!] Flutter (Channel stable, 3.22.2, on macOS 12.2.1 21D62 darwin-x64, locale en-GB) • Flutter version 3.22.2 on channel stable at /Users/dhs/documents/fluttersdk/flutter ! Warning: `flutter` on your path resolves to /Users/dhs/Documents/Fluttersdk/flutter/bin/flutter, which is not inside your current Flutter SDK checkout at /Users/dhs/documents/fluttersdk/flutter. Consider adding /Users/dhs/documents/fluttersdk/flutter/bin to the front of your path. ! Warning: `dart` on your path resolves to /Users/dhs/Documents/Fluttersdk/flutter/bin/dart, which is not inside your current Flutter SDK checkout at /Users/dhs/documents/fluttersdk/flutter. Consider adding /Users/dhs/documents/fluttersdk/flutter/bin to the front of your path. • Upstream repository https://github.com/flutter/flutter.git • Framework revision 761747bfc5 (33 hours ago), 2024-06-05 22:15:13 +0200 • Engine revision edd8546116 • Dart version 3.4.3 • DevTools version 2.34.3 • If those were intentional, you can disregard the above warnings; however it is recommended to use "git" directly to perform update checks and upgrades. [!] Xcode - develop for iOS and macOS (Xcode 12.3) • Xcode at /Applications/Xcode.app/Contents/Developer ! Flutter recommends a minimum Xcode version of 13. Download the latest version or update via the Mac App Store. • CocoaPods version 1.11.2 [✓] Chrome - develop for the web • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome [✓] VS Code (version 1.62.0) • VS Code at /Applications/Visual Studio Code.app/Contents • Flutter extension version 3.21.0 [✓] Connected device (5 available) • SM G975F (mobile) • RZ8M802WY0X • android-arm64 • Android 11 (API 30) • Darshan's iphone (mobile) • 21150b119064aecc249dfcfe05e259197461ce23 • ios • iOS 14.4.1 18D61 • iPhone 12 Pro Max (mobile) • A5473606-0213-4FD8-BA16-553433949729 • ios • com.apple.CoreSimulator.SimRuntime.iOS-14-3 (simulator) • macOS (desktop) • macos • darwin-x64 • Mac OS X 10.15.4 19E2269 darwin-x64 • Chrome (web) • chrome • web-javascript • Google Chrome 98.0.4758.80 [✓] HTTP Host Availability • All required HTTP hosts are available ! Doctor found issues in 1 category. [!] Flutter (Channel master, 3.24.0-1.0.pre.197, on macOS 12.2.1 21D62 darwin-x64, locale en-GB) • Flutter version 3.24.0-1.0.pre.197 on channel master at /Users/dhs/documents/fluttersdk/flutter ! Warning: `flutter` on your path resolves to /Users/dhs/Documents/Fluttersdk/flutter/bin/flutter, which is not inside your current Flutter SDK checkout at /Users/dhs/documents/fluttersdk/flutter. Consider adding /Users/dhs/documents/fluttersdk/flutter/bin to the front of your path. ! Warning: `dart` on your path resolves to /Users/dhs/Documents/Fluttersdk/flutter/bin/dart, which is not inside your current Flutter SDK checkout at /Users/dhs/documents/fluttersdk/flutter. Consider adding /Users/dhs/documents/fluttersdk/flutter/bin to the front of your path. • Upstream repository https://github.com/flutter/flutter.git • Framework revision 0bac2be379 (76 minutes ago), 2024-07-19 00:15:26 -0400 • Engine revision ea1e53a4e8 • Dart version 3.6.0 (build 3.6.0-55.0.dev) • DevTools version 2.37.1 • 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 30.0.3) • Android SDK at /Users/dhs/Library/Android/sdk ✗ cmdline-tools component is missing Run `path/to/sdkmanager --install "cmdline-tools;latest"` See https://developer.android.com/studio/command-line for more details. ✗ Android license status unknown. Run `flutter doctor --android-licenses` to accept the SDK licenses. See https://flutter.dev/docs/get-started/install/macos#android-setup for more details. [✓] Xcode - develop for iOS and macOS (Xcode 13.2.1) • Xcode at /Applications/Xcode.app/Contents/Developer • Build 13C100 • CocoaPods version 1.11.2 [✓] Chrome - develop for the web • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome [✓] IntelliJ IDEA Ultimate Edition (version 2021.3.2) • IntelliJ at /Applications/IntelliJ IDEA.app • Flutter plugin version 65.1.4 • Dart plugin version 213.7228 [✓] VS Code (version 1.62.0) • VS Code at /Applications/Visual Studio Code.app/Contents • Flutter extension version 3.29.0 [✓] Connected device (3 available) • Darshan's iphone (mobile) • 21150b119064aecc249dfcfe05e259197461ce23 • ios • iOS 15.3.1 19D52 • macOS (desktop) • macos • darwin-x64 • macOS 12.2.1 21D62 darwin-x64 • Chrome (web) • chrome • web-javascript • Google Chrome 109.0.5414.119 [✓] Network resources • All expected network resources are available. ! Doctor found issues in 1 category. [!] Xcode - develop for iOS and macOS (Xcode 12.3) • Xcode at /Applications/Xcode.app/Contents/Developer ! Flutter recommends a minimum Xcode version of 13. Download the latest version or update via the Mac App Store. • CocoaPods version 1.11.2 [✓] Chrome - develop for the web • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome [✓] VS Code (version 1.62.0) • VS Code at /Applications/Visual Studio Code.app/Contents • Flutter extension version 3.21.0 [✓] Connected device (5 available) • SM G975F (mobile) • RZ8M802WY0X • android-arm64 • Android 11 (API 30) • Darshan's iphone (mobile) • 21150b119064aecc249dfcfe05e259197461ce23 • ios • iOS 14.4.1 18D61 • iPhone 12 Pro Max (mobile) • A5473606-0213-4FD8-BA16-553433949729 • ios • com.apple.CoreSimulator.SimRuntime.iOS-14-3 (simulator) • macOS (desktop) • macos • darwin-x64 • Mac OS X 10.15.4 19E2269 darwin-x64 • Chrome (web) • chrome • web-javascript • Google Chrome 98.0.4758.80 [✓] HTTP Host Availability • All required HTTP hosts are available ! Doctor found issues in 1 category. ```
goderbauer commented 2 months ago

Is the widget that has the global key actually visible on screen? The ListView builds its children lazily. If the widget is not on screen, it is not included in the tree and hence not connected to the global key.

pic16f887 commented 2 months ago

@goderbauer Yes, it's visible on the screen when I read the state. In my application it's added dynamically so I have added a similar logic to the example. Even with this code the issue is reproduced

@override
  Widget build(BuildContext context) {
    var widget = Scaffold(
      body: ListView(
        children: [
          TextField(key: _widgetKey), // inputWidget is replaced with  TextField(key: _widgetKey)
          const Text("Widgets"),
          _widgets,
          TextButton(onPressed: actionHandler, child: const Text("Action")),
        ],
      ),
    );
    return widget;
  }
Piinks commented 1 month ago

@pic16f887 can you share the device you are experiencing this on? I have not been able to reproduce the issue.

pic16f887 commented 1 month ago

@Piinks I have this issue on Windows 11.

Piinks commented 1 month ago

Thanks for letting us know! I have tried it, and it looks like it only happens when the widget in question is scrolled off screen. This is expected, since it being off screen means it will be disposed. The API docs discusses ways to modify this behavior, see under the heading Destruction mitigation: https://api.flutter.dev/flutter/widgets/ListView-class.html

There is also some sample code here: https://api.flutter.dev/flutter/widgets/ListView/ListView.custom.html#widgets.ListView.custom.1

pic16f887 commented 1 month ago

@Piinks Thank you for the information about Destruction mitigation. I have modified my example and added AutomaticKeepAliveClientMixin to MyPageState but I still unable to get state from _widgetKey when ListBody generates 18 or more widgets.

import 'package:flutter/material.dart';

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

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

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

class MyPageState extends State<MyPage> with AutomaticKeepAliveClientMixin {
  final ListBody _widgets = ListBody(
      children: List.generate(18, (index) {
    return const TextField();
  }));
  final GlobalKey _widgetKey = GlobalKey();
  Widget inputWidget = const TextField();

  @override
  bool get wantKeepAlive => true;

  @override
  Widget build(BuildContext context) {
    super.build(context);
    return Scaffold(
      body: ListView(
        children: [
          TextField(key: _widgetKey),
          const Text("Widgets"),
          _widgets,
          TextButton(onPressed: actionHandler, child: const Text("Action")),
        ],
      ),
    );
  }

  void actionHandler() async {
    if (_widgetKey.currentState == null) {
      debugPrint("state is null");
    }
  }
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});
  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  void initState() {
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(widget.title),
      ),
      body: const Center(child: MyPage()),
    );
  }
}
Piinks commented 1 month ago

The keep alive should be applied to the children of the list view. Not the list itself.

github-actions[bot] commented 4 weeks ago

Without additional information, we are unfortunately not sure how to resolve this issue. We are therefore reluctantly going to close this bug for now. If you find this problem please file a new issue with the same description, what happens, logs and the output of 'flutter doctor -v'. All system setups can be slightly different so it's always better to open new issues and reference the related ones. Thanks for your contribution.

github-actions[bot] commented 2 weeks 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.