Flutter复制Login页创建Register页时触发_dependents.isEmpty断言错误
问题描述
我使用GetX包开发Flutter登录页面,通过Form组件创建TextFormField并为其分配了GlobalKey<FormState>(var formKey = GlobalKey<FormState>(););复制登录页面代码创建注册页面后,出现如下错误:
AssertionError ('package:flutter/src/widgets/framework.dart': Failed assertion: line 5403 pos 14: '_dependents.isEmpty': is not true.)
登录页面代码
import 'package:book_store/user/sign_up.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:get/get_navigation/src/root/get_material_app.dart'; import 'package:get/get_state_manager/get_state_manager.dart'; class Login extends StatefulWidget { const Login({Key? key}) : super(key: key); @override State<Login> createState() => _LoginState(); } class _LoginState extends State<Login> { var formKey = GlobalKey<FormState>(); var emailController = TextEditingController(); var passwordController = TextEditingController(); var isObsecure = true.obs; @override Widget build(BuildContext context) { return GetMaterialApp( title: "my app", theme: ThemeData( primarySwatch: Colors.blue, ), home: FutureBuilder( builder: (context, dataSnapShot) { return Scaffold( backgroundColor: Colors.black, body: LayoutBuilder(builder: (context, cons) { return ConstrainedBox( constraints: BoxConstraints( minHeight: cons.maxHeight, ), child: SingleChildScrollView( child: Column( children: [ SizedBox( width: MediaQuery.of(context).size.width, height: 285, child: Image.asset("lib/assets/book_books_bookshelf.jpg"), ), const SizedBox( height: 20, ), Padding( padding: const EdgeInsets.all(8.0), child: Container( decoration: const BoxDecoration( color: Colors.white24, borderRadius: BorderRadius.all(Radius.circular(60)), boxShadow: [ BoxShadow( blurRadius: 8, color: Colors.black26, offset: Offset(0, -3), ), ]), child: Padding( padding: const EdgeInsets.fromLTRB(30, 20, 30, 20), child: Column( children: [ const SizedBox(height: 20), // for email Form( key: formKey, child: Column( children: [ TextFormField( controller: emailController, // if user put form empty validator: (val) => val == "" ? "please write email" : null, decoration: InputDecoration( prefixIcon: const Icon( Icons.email, color: Colors.amber, ), hintText: "abc@gmail.com", border: OutlineInputBorder( borderRadius: BorderRadius.circular(30), borderSide: const BorderSide( color: Colors.white70, )), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(30), borderSide: const BorderSide( color: Colors.white70, )), disabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(30), borderSide: const BorderSide( color: Colors.white70, ), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(30), borderSide: const BorderSide( color: Colors.white70, )), contentPadding: const EdgeInsets.symmetric( horizontal: 3.0, vertical: 8.0, ), fillColor: Colors.white38, filled: true, ), ), SizedBox( height: 30, ), //----------------password--------------------------------------------------- Obx(() => TextFormField( controller: passwordController, obscureText: isObsecure.value, // if user put form empty validator: (val) => val == "" ? "please write password" : null, decoration: InputDecoration( prefixIcon: const Icon( Icons.key, color: Colors.amber, ), suffixIcon: Obx(() => GestureDetector( onTap: () => { isObsecure.value = !isObsecure.value, }, child: Icon( isObsecure.value ? Icons .visibility_off : Icons.visibility, ), )), hintText: "password", border: OutlineInputBorder( borderRadius: BorderRadius.circular(30), borderSide: const BorderSide( color: Colors.white70, )), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(30), borderSide: const BorderSide( color: Colors.white70, )), disabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(30), borderSide: const BorderSide( color: Colors.white70, ), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(30), borderSide: const BorderSide( color: Colors.white70, )), contentPadding: const EdgeInsets.symmetric( horizontal: 3.0, vertical: 8.0, ), fillColor: Colors.white38, filled: true, ), )), SizedBox( height: 30, ), //---------------material------------------------------------------- Material( color: Colors.black, borderRadius: BorderRadius.circular(38), child: InkWell( onTap: () {}, borderRadius: BorderRadius.circular(38), child: const Padding( padding: EdgeInsets.symmetric( vertical: 10, horizontal: 10, ), child: Text( "login", style: TextStyle( color: Colors.white, fontSize: 16, ), ), ), ), ), ], ), ), const SizedBox( height: 10, ), Row( mainAxisAlignment: MainAxisAlignment.center, children: [ const Text("Don't have an Account?"), TextButton( onPressed: () { Get.to(SignUp()); }, child: const Text("Register here")) ], ), const Text( "Or", style: TextStyle( fontSize: 16, color: Colors.grey, ), ), //----------------admin text --------------------------- Row( mainAxisAlignment: MainAxisAlignment.center, children: [ const Text("Are you an admin?"), TextButton( onPressed: () {}, child: const Text("click here")) ], ), // SizedBox(height: 10,) ], ), ), ), ) ], )), ); }), ); }, ), ); } }
main.dart代码
import 'package:book_store/user/login_screen.dart'; import 'package:flutter/material.dart'; import 'package:get/get_navigation/src/root/get_material_app.dart'; void main() { WidgetsFlutterBinding.ensureInitialized(); runApp(const MyApp()); } class MyApp extends StatefulWidget { const MyApp({Key? key}) : super(key: key); @override State<MyApp> createState() => _MyAppState(); } class _MyAppState extends State<MyApp> { @override Widget build(BuildContext context) { // using get dependency so that state can be easily managed return MaterialApp( title: 'book store app', theme: ThemeData( primarySwatch: Colors.blue, ), home: FutureBuilder( builder: (context, dataSnapShot){ return Login(); }, ), ); } }
问题原因与修复方案
核心问题
- 重复嵌套根App组件:Login页面返回了
GetMaterialApp,但main.dart中已经使用了MaterialApp,导致Widget树存在多个根App上下文,引发GlobalKey依赖管理冲突。 - GlobalKey复用风险:复制代码创建注册页时,若未重新定义独立的
GlobalKey<FormState>,会导致同一个Key绑定到多个Form组件,触发断言错误。
修复步骤
1. 移除Login页面中的GetMaterialApp
Login页面应作为普通页面组件,直接返回Scaffold:
@override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.black, body: LayoutBuilder(builder: (context, cons) { // 原有body内容保持不变 }), ); }
2. 将main.dart的MaterialApp替换为GetMaterialApp
使用GetX路由必须在根组件使用GetMaterialApp:
@override Widget build(BuildContext context) { return GetMaterialApp( title: 'book store app', theme: ThemeData( primarySwatch: Colors.blue, ), home: FutureBuilder( builder: (context, dataSnapShot){ return Login(); }, ), ); }
3. 给注册页分配独立的GlobalKey
在注册页的State类中重新定义专属的formKey,禁止复用登录页的Key:
class _SignUpState extends State<SignUp> { var formKey = GlobalKey<FormState>(); var emailController = TextEditingController(); var passwordController = TextEditingController(); var confirmPasswordController = TextEditingController(); // 其他变量也需独立定义 }
4. 简化多余的Obx嵌套
登录页密码输入框的suffixIcon嵌套了两层Obx,可简化为一层,减少不必要的响应式重建:
Obx(() => TextFormField( controller: passwordController, obscureText: isObsecure.value, validator: (val) => val == "" ? "please write password" : null, decoration: InputDecoration( prefixIcon: const Icon(Icons.key, color: Colors.amber), suffixIcon: GestureDetector( onTap: () => isObsecure.value = !isObsecure.value, child: Icon( isObsecure.value ? Icons.visibility_off : Icons.visibility, ), ), // 其余装饰内容保持不变 ), )),
错误原因说明
当同一个GlobalKey被多个Widget绑定,或Widget树存在多个根App上下文导致依赖关系混乱时,Flutter框架会检测到未清理的依赖项,触发_dependents.isEmpty断言,确保GlobalKey的绑定关系唯一且可被正确管理。
内容的提问来源于stack exchange,提问作者Chinmay
相关产品推荐
相关产品推荐

