Duplicate GlobalKey detected in widget tree
FlutterFlutter Problem Overview
I am running into a globalKey
error after I navigate from Screen A
to Screen B
and click a "Cancel" button to go back to Screen A
.
It seems like the issue is that Screen B
is either
- A) Not being disposed of correctly
- B) Is not doing something that it otherwise could
And I don't actually know:
- What bad things are happening if I just remove the use of a
globalKey
? (as to get a better understanding of the fundamentals) - How can I correctly resolve this issue?
StatefulWidget documentation states:enter link description here
> A StatefulWidget keeps the same State object when moving from one > location in the tree to another if its creator used a GlobalKey for > its key. Because a widget with a GlobalKey can be used in at most one > location in the tree, a widget that uses a GlobalKey has at most one > associated element. The framework takes advantage of this property > when moving a widget with a global key from one location in the tree > to another by grafting the (unique) subtree associated with that > widget from the old location to the new location (instead of > recreating the subtree at the new location). The State objects > associated with StatefulWidget are grafted along with the rest of the > subtree, which means the State object is reused (instead of being > recreated) in the new location. However, in order to be eligible for > grafting, the widget must be inserted into the new location in the > same animation frame in which it was removed from the old location.
Console Error Output:
══╡ 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<FormFieldState<String>>#3c76d]
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:
- Column(direction: vertical, mainAxisAlignment: start, crossAxisAlignment: center, renderObject:
RenderFlex#7595c relayoutBoundary=up1 NEEDS-PAINT)
A GlobalKey can only be specified on one widget at a time in the widget tree.
So this part of the error output:
> previous parent never updated during this frame, meaning that it > either did not update at all or updated before the widget was moved
makes me think there was some opportunity for my old Stateful widget to do something (either reposition itself or release something as to be disposed correctly.
This seems to be failing in framework.dart
on assert(_children.contains(child))
:
@override
void forgetChild(Element child) {
assert(_children.contains(child));
assert(!_forgottenChildren.contains(child));
_forgottenChildren.add(child);
}
Flutter Solutions
Solution 1 - Flutter
In my case, it likes a hot reload bug. Just restart debugging works for me.
Solution 2 - Flutter
Remove the static and final type from the key variable so if
static final GlobalKey<FormState> _abcKey = GlobalKey<FormState>();
change it to
GlobalKey<FormState> _abcKey = GlobalKey<FormState>();
Solution 3 - Flutter
Thanks to Gunter's commments, I determined that this is because the Screens are not being properly disposed.
Flutter's pushReplacement makes a call to Route.dispose
which will ultimately dispose the screen.
I am still unsure as to this comes into play:
> widget must be inserted into the new location in the same animation > frame
I'm not sure what situation would benefit from such trickery. However, my problem is solved. I just need to make a call to pop or replace.
Here are the available options:
- Use
push
fromA
toB
and justNavigator.pop
fromB
- Use
pushReplacement
fromA
toB
and fromB
toA
I've recently started playing with Fluro for routing and there are a few more ways to to handle these situations (Note the optional argument replace):
-
Use
router.navigateTo(context, route, replace: false)
fromA
toB
andNavigator.pop
fromB
-
Use
router.navigateTo(context, route, replace: true)
fromA
toB
the same fromB
toA
(the key isreplace: true
)
Solution 4 - Flutter
Make sure that you don't have a Form
parent and a Form
child with the same key
Solution 5 - Flutter
Best way to solve that, which worked for me:
class _HomeScreenState extends State<HomeScreen> {
GlobalKey<FormState> _homeKey = GlobalKey<FormState>(debugLabel: '_homeScreenkey');
@override
Widget build(BuildContext context) {
return Container(
key: _homeKey,
);
}
}
Solution 6 - Flutter
I had this issue too. I had a four screen bottom tabbed application and a 'logout' method. However, that logout method was calling a pushReplacementNamed. This prevented the class that held the global keys (different from the logout function) from calling dispose.
The resolution was to change pushReplacementNamed with popAndPushNamed to get back to my 'login' screen.
Solution 7 - Flutter
In my case I wanted to use the static GlobalKey<ScaffoldState> _scaffoldKey
but when I used the same widget multiple times it gave this duplicate error.
I wanted to give it a unique string and still use this scaffold state. So I ended up using:
static GlobalObjectKey<ScaffoldState> _scaffoldKey
and in the initState
:
_scaffoldKey = new GlobalObjectKey<ScaffoldState>(id);
Edit:
Actually, silly me. I just simply removed the static
and made it GlobalKey
again :)
Solution 8 - Flutter
please take SingleChildScrollview:
and after if you use the bloc pettern then use strem with broadcast
code is here:
body: Container(
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage('assets/images/abcd.jpg'),
fit: BoxFit.cover,
),
),
child: Container(child:Form(key: _key,
child: Padding(
padding: EdgeInsets.symmetric(vertical: 100.0, horizontal: 20.0),
child: SingleChildScrollView(child:Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.all(10.0),
child: Image.asset('assets/images/logo.png', height: 80, width:80,),
),
email(),
Padding(
padding: EdgeInsets.all(5.0),
),
password(),
row(context),
],
),
),
),
),
),
),
resizeToAvoidBottomPadding: false,
);
}
and the bloc pettern code is here:
final _email = StreamController<String>.broadcast();
final _password = StreamController<String>.broadcast();
Stream<String> get email => _email.stream.transform(validateEmail);
Stream<String> get password=> _password.stream.transform(validatepassword);
Function(String) get changeEmail=> _email.sink.add;
Function(String) get changePassword => _password.sink.add;
dispose(){
_email.close();
_password.close();
}
}
final bloc=Bloc();
Solution 9 - Flutter
I also had a similar error. My answer was that after I updated Flutter some widgets no longer had child or children properties. In my case it was the CircleAvatar. The build doesn't error out initially, but when navigating back and forth through the app it will fail.
*Please review all widgets that require a child then review the updated documentation and make sure you're parameters are still correct.
Solution 10 - Flutter
I had a similar problem. in my case the problem was that I had a function in dispose method of screen B and it wouldn't execute properly. I just removed it and the problem was fixed.
so be sure your dispose method is executed properly in all screens.
Solution 11 - Flutter
This happened to me, what I did was enclosed the whole view into a navigator using an extension I made
Widget addNavigator() => Navigator(
onGenerateRoute: (_) => MaterialPageRoute(
builder: (context2) => Builder(
builder: (context) => this,
),
),
);
Solution 12 - Flutter
I also got this error. There was a static bloc object in a class and I removed the static keyword which fixed the error.
Events should be added by using the BlocProvider anyway.
Solution 13 - Flutter
I had similar issue on a StatelessWidget class, Converted it to StatefulWidget and error is gone.
Solution 14 - Flutter
If you have multiple forms with different widgets, you must use separate GlobalKey for each form. Like I have two forms, one with Company signup & one with Profile. So, I declared
GlobalKey<FormState> signUpCompanyKey = GlobalKey<FormState>();
GlobalKey<FormState> signUpProfileKey = GlobalKey<FormState>();
Solution 15 - Flutter
I am not sure why no one has mentioned this yet but, in my case, I simply changed a widget from Stateful to Stateless. To fix the error, you have to restart the application instead of doing a hot reload.