【问题标题】:Flutter - Duplicate GlobalKey detected in widget treeFlutter - 在小部件树中检测到重复的 GlobalKey
【发布时间】:2021-05-17 23:51:30
【问题描述】:

请参阅此Duplicate GlobalKey detected in widget tree,从屏幕 A 导航到屏幕 B 并再次导航到屏幕 A 时,我仍然遇到重复键的问题

登录页面

class LoginPage extends StatefulWidget {
  @override
  _LoginPageState createState() => _LoginPageState();
}

class _LoginPageState extends State<LoginPage> {
  @override
  void initState() {
    super.initState();
//UsingSharedPreference , If Already Login , GoToHomePage
    Provider.of<LoginChangeNotifier>(context, listen: false)
        .checkLogin(context); 
  }

  @override
  void dispose() {
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    var _size = Screen(MediaQuery.of(context).size);
    return Consumer<LoginChangeNotifier>(
      builder: (context, login, _) {
        return Scaffold(
          key:Provider.of<LoginChangeNotifier>(context, listen: false)
              .scaffoldLoginKey,
          backgroundColor: Colors.black,
          body: Container(
              child: SingleChildScrollView(
            child: Column(
              mainAxisSize: MainAxisSize.max,
              children: <Widget>[
                Container(
                ),
                SizedBox(height: _size.hp(2)),
                Form(
                  key: Provider.of<LoginChangeNotifier>(context, listen: false)
                      .loginKey,
                  child:Container(
                    margin: EdgeInsets.symmetric(horizontal: _size.wp(8)),
                    child: Column(
                      children: <Widget>[
                        TextFormField(
                          controller: login.controllerEmail,
                          ),
                          keyboardType: TextInputType.emailAddress,
                          autovalidate: login.autoValidate,
                          validator: (e) {
                            if (e.isEmpty) {
                              return "Cant Be Empty";
                            } else {
                              return null;
                            }
                          },
                        ),
                        SizedBox(height: 16),
                        TextFormField(
                          controller: login.controllerPassword,
                            labelText: 'Password',
                          ),
                          keyboardType: TextInputType.emailAddress,
                          obscureText: login.secureText,
                          autovalidate: login.autoValidate,
                          validator: (e) {
                            if (e.isEmpty) {
                              return "Cant Be Empty";
                            } else {
                              return null;
                            }
                          },
                        ),
RaisedButton(
                                color: myPrimaryColor,
                                  children: <Widget>[
                                    Text("Login",
                                        style: TextStyle(
                                            color: Colors.black,
                                            fontFamily: "NunitoSansBold")),
                                  ],
                                ),
                                padding:
                                    EdgeInsets.only(top: 16.0, bottom: 16.0),
                                onPressed: () {
                                  print("clicked Button Login");
//HereHandleFromProvider
                                  login.authenticationUser(context);
                                },
                              ),
                        SizedBox(height: _size.hp(3)),
                        ),
                      ],
                    ),
                  ),
                ),
              ],
            ),
          )),
        );
      },
    );
  }

Login_Provider

class LoginChangeNotifier with ChangeNotifier {
  GlobalKey<ScaffoldState> _scaffoldLoginKey = GlobalKey<ScaffoldState>(
      debugLabel: '_scaffoldLoginkey');
  GlobalKey<FormState> _loginKey = new GlobalKey<FormState>();

 GlobalKey<ScaffoldState> get scaffoldLoginKey => _scaffoldLoginKey;
  GlobalKey<FormState> get loginKey => _loginKey;

authenticationUser(BuildContext context) async {
showDialog(
              context: context,
              builder: (myContext) =>
                  AlertDialog(
                    title: new Text('Warning'),
                    content: new Text('Your Password Expired'),
                    actions: [
                      new FlatButton(
                          onPressed: () {
                            Navigator.of(context).pushReplacement(
                                MaterialPageRoute(builder: (context) =>
//MoveToScreenB
                                    ChangePasswordPage()));
                          },
                          child: new Text(
                            'Change Password',
                            style: TextStyle(color: Colors.lightBlue),
                          ))
                    ],
                    shape: RoundedRectangleBorder(
                      side: BorderSide(color: Colors.white70, width: 1),
                      borderRadius: BorderRadius.circular(10),
                    ),
                  ));
}

更改密码页面

class ChangePasswordPage extends StatelessWidget {
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Change Password", style: TextStyle(color: Colors.black)),
        centerTitle: true,
        iconTheme: IconThemeData(color: Colors.white),
      ),
      key: Provider.of<ChangePasswordNotifier>(context, listen: false)
          .scaffoldChangePasswordKey,
      body: Consumer<ChangePasswordNotifier>(
        builder: (context, changePassword, _) {
          return changePassword.changePasswordProcess
              ? Center(child: CircularProgressIndicator())
              : Padding(
                  padding: EdgeInsets.symmetric(
                  ),
                  child: Form(
                    key: Provider.of<ChangePasswordNotifier>(context,
                            listen: false)
                        .changePasswordkey,
                    child: Column(
                      children: [
                        TextFormField(),
                        SizedBox(
                            height: MediaQuery.of(context).size.height / 47),
                        TextFormField(),
                        SizedBox(
                            height: MediaQuery.of(context).size.height / 47),
                        TextFormField(),
                      ],
                    ),
                  ),
                );
        },
      ),
      bottomNavigationBar: BottomAppBar(
        elevation: 0.0,
        child: Padding(
            padding: const EdgeInsets.all(8.0),
            child: Consumer<ChangePasswordNotifier>(
              builder: (context, changePassword, _) {
                return RaisedButton(
                    onPressed: () {
                      print("Submit ChangePassword Clicked");
                      changePassword.changePasswordProcess
                          ? null
//HereHandleOnProvider
                          : changePassword.check(context);
                    },
                    child: Row(
                      children: <Widget>[
                        Text("SUBMIT",)
                      ],
                    ));
              },
            )),
      ),
    );
  }
}

更改密码提供者

class ChangePasswordNotifier with ChangeNotifier {
  GlobalKey<ScaffoldState> _scaffoldChangePasswordKey = new GlobalKey<ScaffoldState>();
  GlobalKey<FormState> _changePasswordkey =new GlobalKey<FormState>();

  GlobalKey<FormState> get changePasswordkey => _changePasswordkey;
  GlobalKey<ScaffoldState> get scaffoldChangePasswordKey =>
      _scaffoldChangePasswordKey;

      check(BuildContext context) async {
confirmationPassword(context, "Success");
        }

        confirmationPassword(BuildContext context, result)async{
           var _providerLogin =
        Provider.of<LoginChangeNotifier>(context, listen: false);
          showDialog(
        context: context,
        builder: (myContext) => AlertDialog(
              title: new Text("Confirmation"),
              content: new Text(result),
              actions: [
                new FlatButton(
                    onPressed: () {
                      print("Clicked on Confirmation Password");
                      Navigator.pushAndRemoveUntil(
                          context,
                          //Here i Want to navigate back to Screen A
                          MaterialPageRoute(builder: (context) => LoginPage()),
                              (Route<dynamic> route) => false);
                    },
                    child: new Text(
                      'OK',
                      style: TextStyle(color: Colors.lightBlue),
                    ))
              ],
              shape: RoundedRectangleBorder(
                side: BorderSide(color: Colors.white70, width: 1),
                borderRadius: BorderRadius.circular(10),
              ),
            ));
        }

}

点击导航到屏幕A时的结果

======== Exception caught by widgets library =======================================================
The following assertion was thrown building Form-[LabeledGlobalKey<FormState>#57f0a](state: FormState#c57b1):
'package:flutter/src/widgets/will_pop_scope.dart': Failed assertion: line 61 pos 12: '_route == ModalRoute.of(context)': is not true.


Either the assertion indicates an error in the framework itself, or we should provide substantially more information in this error message to help you determine and fix the underlying cause.
In either case, please report this assertion by filing a bug on GitHub:
  https://github.com/flutter/flutter/issues/new?template=BUG.md

The relevant error-causing widget was: 
  Form-[LabeledGlobalKey<FormState>#57f0a] file:///urlFile:login_page.dart:37:17
When the exception was thrown, this was the stack: 
#2      _WillPopScopeState.didUpdateWidget (package:flutter/src/widgets/will_pop_scope.dart:61:12)
#3      StatefulElement.update (package:flutter/src/widgets/framework.dart:4815:58)
#4      Element.updateChild (package:flutter/src/widgets/framework.dart:3314:15)
#5      ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:4652:16)
#6      StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:4800:11)
...
====================================================================================================

======== Exception caught by widgets library =======================================================
The following assertion was thrown while finalizing the widget tree:
Duplicate GlobalKey detected in widget tree.

The following GlobalKey was 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 key was:
- [LabeledGlobalKey<ScaffoldState>#14011]
This was determined by noticing that after the widget with the above global key was moved out of its previous parent, that previous parent never updated during this frame, meaning that it either did not update at all or updated before the widget was moved, in either case implying that it still thinks that it should have a child with that global key.
The specific parent that did not update after having one or more children forcibly removed due to GlobalKey reparenting is:
- Consumer<LoginChangeNotifier>(dependencies: [_LocalizationsScope-[GlobalKey#f6126], InheritedProvider<LoginChangeNotifier>, _InheritedTheme])
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: 
#0      BuildOwner.finalizeTree.<anonymous closure> (package:flutter/src/widgets/framework.dart:2881:15)
#1      BuildOwner.finalizeTree (package:flutter/src/widgets/framework.dart:2906:8)
#2      WidgetsBinding.drawFrame (package:flutter/src/widgets/binding.dart:915:18)
#3      RendererBinding._handlePersistentFrameCallback (package:flutter/src/rendering/binding.dart:302:5)
#4      SchedulerBinding._invokeFrameCallback (package:flutter/src/scheduler/binding.dart:1117:15)
...
====================================================================================================

=====已编辑===== 对于临时解决方案,我想使用重复的弹出上下文(1 个用于关闭警报对话框,1 个用于返回页面 A),但我想这不是最佳实践。

Navigator.pop(context); 
Navigator.pop(context);

【问题讨论】:

  • 尝试将您的全局密钥设置为最终全局密钥
  • 仍然相同的错误,顺便说一句,相关的导致错误的小部件上的错误行是:Form-[LabeledGlobalKey#06619] 参考 Form() 小部件
  • 当您调用 Navigator.pushAndRemoveUntil(..) 时,它可能会将新的 LoginPage() 小部件推送到 Navigator 堆栈,而没有处理旧的 _LoginPageState(这意味着相同的键正在重新-用过的)。你可以直接调用 Navigator.pop() 吗?
  • 不能,当从 Navigator.pushAndRemoveUntil( context, MaterialPageRoute(builder: (context) =&gt; LoginPage()), (Route&lt;dynamic&gt; route) =&gt; false); 更改为 Navigator.pop(context); 时,pop 将关闭 alertdialog 但仍在 ChangePasswordPage 上。

标签: flutter dart flutter-provider


【解决方案1】:

您可能需要考虑将 GlobalKeys 移动到您的 Pages 中,以便在不再使用时可以将其处理掉,而不是在 ChangeNotifier 中对其进行初始化。然后,您只需传递authenticationUser() 上的导航键,以便在需要时访问导航器。

final navigatorKey = GlobalKey<NavigatorState>();

要从键访问导航器,您可以使用类似的东西。

navigatorKey.currentState!.pushAndRemoveUntil(context,
    MaterialPageRoute(builder: (context) => LoginPage()),
        (Route<dynamic> route) => false);

【讨论】:

    猜你喜欢
    • 2020-04-14
    • 2021-07-30
    • 2018-08-28
    • 2019-04-28
    • 2021-06-25
    • 2021-09-01
    • 2022-08-23
    • 1970-01-01
    • 2020-12-12
    相关资源
    最近更新 更多