Duplicate Global Key Error in Navigation with Flutter's Animation Package - flutter

The Background: I am intending to use Flutter's animation package to run a container transform effect between a material card and another page (a details page). To set this up, the cards are set into a list within a "search" page to which then the container is opened, the DetailsScreen is passed a data object (an object within objectList according to the card's index). See below code:
SEARCHSCREEN
...
child: ListView.builder(
controller: _listController,
itemCount: objectList.length,
itemBuilder: (BuildContext context, int index) {
return OpenContainer(
transitionDuration: Duration(milliseconds: 750),
closedBuilder: (_, openContainer) {
return AnimatedCard(
direction: AnimatedCardDirection.left,
initDelay: Duration(milliseconds: 0),
duration: Duration(milliseconds: 500),
curve: Curves.easeOutBack,
child: ObjectCard(objectList[index],
savedObjectList, openContainer),
);
},
openBuilder: (_, closeContainer) {
return DetailScreen(objectList[index], closeContainer);
},
);
},
),
DETAILSCREEN
class DetailScreen extends StatefulWidget {
const DetailScreen(this.object, this.closeContainer);
final object;
final closeContainer;
#override
_DetailScreenState createState() => _DetailScreenState();
}
class _DetailScreenState extends State<DetailScreen> {
#override
Widget build(BuildContext context) {
return Scaffold(
...
The Problem: Whenever a card is clicked upon, the following error is returned:
The widget which was currently being built when the offending call was made was:
DetailScreen-[LabeledGlobalKey<_SearchScreenState>#b16ed]
When the exception was thrown, this was the stack:
...
═════ Exception caught by animation library ═════════════════════════════════
setState() or markNeedsBuild() called during build.
════════════════════════════════════════════════════════════════════════════════
Another exception was thrown: setState() or markNeedsBuild() called during build.
Another exception was thrown: Duplicate GlobalKey detected in widget tree.
════════ Exception caught by widgets library ═══════════════════════════════════
Duplicate GlobalKey detected in widget tree.
Having tried many solutions of adding and passing keys first to the constructors of each object, then to all of them, nothing has worked. The only thing that makes the animation work is if I remove the [index] from "return DetailScreen(objectList[index], closeContainer);". The animation will fire, but obviously is not desirable as now no object has been passed and hence nothing on the DetailScreen will work. What am I missing here? I was under the impression that on each openContainer click, a DetailsScreen was created, populated with the passed object's data and the destroyed when I used the closeContainer callback. Under this assumption, this should work because only one screen will exist at a time and no duplication of "Global Keys" will occur. But this now does not seem to be the case. Is there something up that I need to include higher up the widget chain, such as within my "SearchScreen" itself, which is hosting this code? Any help or insight would be greatly appreciated!

Related

How does Text Widget get marked for rebuild on parent setState()

When setState is called in a widget's state, the corresponding element in the element tree gets marked as dirty, and the widget gets rebuilt. However, how does it handle descendents? For example, the Text widget below gets rebuilt when its ancestor SampleWidgetState gets rebuilt.
Why?
class SampleWidget extends StatefulWidget {
#override
SampleWidgetState createState() => SampleWidgetState();
}
class SampleWidgetState extends State<SampleWidget> {
String text = "text1";
#override
Widget build(BuildContext context) {
return Column(
children: [
Text(text),
ElevatedButton(
child: Text('call SetState'),
onPressed: () {
setState(() {
text = "text2";
});
},
),
],
);
}
}
from Flutter's official documentation, inside Flutter:
In response to user input (or other stimuli), an element can become dirty, for example if the developer calls setState() on the associated state object. The framework keeps a list of dirty elements and jumps directly to them during the build phase, skipping over clean elements. During the build phase, information flows unidirectionally down the element tree, which means each element is visited at most once during the build phase. Once cleaned, an element cannot become dirty again because, by induction, all its ancestor elements are also clean.
I guess this answer what Flutter does under the hood in the updating process of the widget's descendents.
SampleWidgetState is a state class, when you calling the setState() its mean build() method will reinvoke, everything inside will rebuild. thats how its works.
if you want to prevent the descendents to not rebuild, there is several ways,
use const keyword.
warp the widget you want to change its own state, example use StatefullBuilder
refactor widget to statefulwidget so its have its own state
in your case, Text widget consume SampleWidgetState : String text = "text1";, its mean Text widget is not independent, its dependent on that state.

how to use consumer correctly in flutter provider package

This is the error message
: The following NoSuchMethodError was thrown building Consumer<PageOffsetNotifier>(dirty,
: dependencies: [_InheritedProviderScope<PageOffsetNotifier>]):
: The method 'unary-' was called on null.
: Receiver: null
: Tried calling: unary-()
this is the class with consumer
class LeopardImage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Consumer<PageOffsetNotifier>(
builder: (context,notifier,child) => Positioned(
width:300, left: -notifier.offset,
child: child,
),
child: Image.asset('assets/leopard.png'),
);
}
}
here PageOffsetNotifier is the class which extends with ChangeNotifier and passed on to ChangeNotifierProvider
the notifier will detect the change in offset value while swiping one page to another and perform the desired function.....
Please tell me what I'm missing or done wrong in the consumer, as a result, I'm getting this error
To Pass the context from a page to another using provider. Then it's always wise to use its object
ChangeNotifierProvider.value(
value: object from Provider,
child: the page you want to move
)
The parent widget of this will be :
ChangeNotiferProvider<ClassName>(
oncreate: (context)=>ClassName(),
),
With this you can use consumer inside.

Flutter Mobx - setState() or markNeedsBuild() called during build

When I try to navigate from a page to another, I'm getting the error bellow. After some research i found some solutions as calling the navigation inside a SchedulerBinding.instance.addPostFrameCallback, but even if that solve in some cases, for this specific case none of them are working.
I'm using an IndexedStack with three AnimatedOpacity wrapping one different page. Besides the error, could be the way on that the IndexedStack renders its content?
Seems to be cause it renders all the pages at the same time while shows only the current index. But how i can solve this if a i need switch between these pages with the same state? I tried with PageView but the state is miss when change from one page to another.
Anyone can help me on this?
════════ Exception caught by flutter_mobx ══════════════════════════════════════
The following MobXCaughtException was thrown:
setState() or markNeedsBuild() called during build.
This Observer widget cannot be marked as needing to build because the framework is already in the process of building widgets. A widget can be marked as needing to be built during the build phase only if one of its ancestors is currently building. This exception is allowed because the framework builds parent widgets before children, which means a dirty descendant will always be built. Otherwise, the framework might not visit this widget during this build phase.
The widget on which setState() or markNeedsBuild() was called was:
Observer
The widget which was currently being built when the offending call was made was:
AnimatedOpacity
When the exception was thrown, this was the stack
#0 Element.markNeedsBuild.<anonymous closure>
package:flutter/…/widgets/framework.dart:4167
#1 Element.markNeedsBuild
package:flutter/…/widgets/framework.dart:4182
#2 ObserverElementMixin.invalidate
package:flutter_mobx/src/observer_widget_mixin.dart:70
#3 ReactionImpl._run
package:mobx/…/core/reaction.dart:119
#4 ReactiveContext._runReactionsInternal
package:mobx/…/core/context.dart:345
...
The build method:
#override
Widget build(BuildContext context) {
return IndexedStack(
index: index,
children: <Widget>[
AnimatedOpacity(
key: Key('animatedOpacity_0'),
opacity: index == 0 ? 1.0 : 0.0,
duration: Duration(milliseconds: 500),
child: Page1(),
),
AnimatedOpacity(
key: Key('animatedOpacity_1'),
opacity: index == 1 ? 1.0 : 0.0,
duration: Duration(milliseconds: 500),
child: Page2(),
),
AnimatedOpacity(
key: Key('animatedOpacity_2'),
opacity: index == 2 ? 1.0 : 0.0,
duration: Duration(milliseconds: 500),
child: Page3(),
),
],
);
}
UPDATE:
The problem occurs on the third AnimatedOpacity, and if i remove the AnimatedOpacity widgets, the problems doesn't occurs but i can't figure it out why. Someone has some explanation about that?

Adding OverlayEntry in Flutter

I am trying to insert a Container to the Overlay, but I had an error with this code.
class _MyHomePageState extends State<MyHomePage> {
#override
void didChangeDependencies() {
super.didChangeDependencies();
final entry = OverlayEntry(builder: (BuildContext overlayContext) {
return Container(
height: 50.0,
width: 50.0,
color: Colors.blue,
);
});
_addOverlay(entry);
}
void _addOverlay(OverlayEntry entry) async {
Overlay.of(context).insert(entry);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Flutter'),
),
body: Center(),
);
}
}
This is error
setState() or markNeedsBuild() called during build. This Overlay widget cannot be marked as needing to build because the framework is already in the process of building widgets. A widget can be marked as needing to be built during the build phase only if one of its ancestors is currently building. This exception is allowed because the framework builds parent widgets before children, which means a dirty descendant will always be built. Otherwise, the framework might not visit this widget during this build phase...
Thank you in advance.
Since the last update to flutter 0.8.1 I noticed this change too. I fixed this to add the overlay after a minimal delay
Timer.run(() { Overlay.of(context).insert(calendarOverlay);});
Now this works but it feels like a hack...
So in my build i use this code when the overlay should present itself..
If anyone has a better solution, I am interested ;-)
John
UPDATE: I found this code to be working too:
final overlay = Overlay.of(context);
WidgetsBinding.instance.addPostFrameCallback((_) => overlay.insert(entry));
It saves me from including timers...
Just share some of my findings. I am about to implement overlay in my app too. So found this SO question by searching.
Many people build overlay before the normal widget. For example, in your code, the overlay insert in didChangeDependencies is called before building the Scaffold. This is the cause of all the async problems. I found people do this (couple the overlay insert and corresponding normal widget in a stateful widget) is because they want to find the corresponding child widget's position, but the child widget is build after the overlay insert call, thus the overlay insert has to be in an async function.
But If you just call overlay insert after building the normal widget (make overlay insert call independent from building the base widget. Separate/decouple them), you won't need any async or Timer functions at all. In my current implementation, I separate them just to make the code safe (I feel it's safer). So no need for any async calls.

Duplicate GlobalKey detected in widget tree

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);
}
In my case, it likes a hot reload bug. Just restart debugging works for me.
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>();
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 from A to B and just Navigator.pop from B
Use pushReplacement from A to B and from B to A
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) from A to B and Navigator.pop from B
Use router.navigateTo(context, route, replace: true) from A to B the same from B to A (the key is replace: true)
Make sure that you don't have a Form parent and a Form child with the same key
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.
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,
);
}
}
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 :)
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();
I had similar issue on a StatelessWidget class, Converted it to StatefulWidget and error is gone.
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>();
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,
),
),
);
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.
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.