Adding OverlayEntry in Flutter - 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.

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.

When do we initialise a provider in flutter?

I just arrived on a flutter project for a web app, and all developers have a problem using flutter provider for state management.
What is the problem
When you arrive on a screen, the variables of the corresponding provider are initialised by calling a function of the provider. This function calls an api, and sets the variables in the provider.
Problem : This function is called in the build section of the widget. Each time the window is resized, the widget is rebuilt, and the function is called again.
What we want
We want to call an api when the page is first displayed, set variables with the result, and not call the api again when the widget is rebuilt.
What solution ?
We use a push from the first screen to go to the second one. We can call the function of the provider at this moment, to initialise the provider just before the second screen.
→ But a refresh on the second page will clear the provider variables, and the function to initialise them will not be called again.
We call the function to initialise the provider in the constructor of the second screen. Is it a good pattern ?
Thank you for your help in my new experience with flutter :)
I think you're mixing a couple different issues here:
How do you correctly initialize a provider
How do you call a method on initialization (only once)
For the first question:
In your main.dart file you want to do something like this:
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(create: (context) => SomeProvider()),
ChangeNotifierProvider(create: (context) => AnotherProvider()),
],
child: YourRootWidget();
);
}
Then in a widget (that probably represents a "screen" in your app), you need to do something like this to consume state changes from that provider:
#override
Widget build(BuildContext context) {
return Container(
child: Consumer<SomeProvider>(
builder: (context, provider, child) {
return Text(provider.someState);
}
),
)
}
And you need to do something like this to get access to the provider to mutate state:
#override
Widget build(BuildContext context) {
SomeProvider someProvider = Provider.of<SomeProvider>(context, listen: false);
return Container(
child: TextButton(
child: Text('Tap me'),
onPressed: () async {
await someProvider.mutateSomeState();
}
),
)
}
Regarding the second question... You can (I think) just use the initState() method on a widget to make the call only 1 time. So...
#override
void initState() {
super.initState();
AnotherProvider anotherProvider = Provider.of<AnotherProvider>(context, listen: false);
Future.microtask(() {
anotherProvider.doSomethingElse();
});
}
If I'm off on any of that, I'm sorry. That mirrors my implementation and works fine/well.
A caveat here is that I think RiverPod is likely the place you really want to go (it's maybe easier to work with and has additional features that are helpful, etc.) but I've not migrated to RiverPod yet and do not have that figured out all the way.
Anyway... Good luck!
As far as I understood, you can wrap your application with MultiProvider and call the API before going to the second screen.

How do I keep my widget state when reordering them via Draggable&DragTarget?

I'm using this reorderables package. This package works by having a list of children widgets that are each wrapped with a Draggable and put inside a DragTarget. Before that the childs key is assigned to a GlobalObjectKey.
After the dragTarget is created, it is assigned(or rebuild?) to a KeyedSubTree:
dragTarget = KeyedSubtree(key: keyIndexGlobalKey, child: dragTarget);
According to the comments in the package source code, this should preserve the child widgets state (toWrap) when being dragged:
// We pass the toWrapWithGlobalKey into the Draggable so that when a list
// item gets dragged, the accessibility framework can preserve the selected
// state of the dragging item.
final GlobalObjectKey keyIndexGlobalKey = GlobalObjectKey(toWrap.key);
The reordering itself happens not with the DragTarget accepting the Draggable dragged into it, but rather by using the DragTarget around each child to get index of the current position the Draggable is hovering over. When the Draggable is let go, a reorder function will get called, which removes the widget (that was being dragged) from the list and inserting it into the new position.
Now comes my problem: The state of the widget is not being preserved. I made a simple TestWidget to test this:
class TestWidget extends StatefulWidget{
#override
_TestWidgetState createState() => _TestWidgetState();
}
class _TestWidgetState extends State<TestWidget> {
Color boxColor;
#override
void initState() {
super.initState();
boxColor= Colors.blue;
}
#override
Widget build(BuildContext context) {
return Column(
children: [
Container(
decoration: BoxDecoration(color: boxColor),
child: Text("Test"),
),
FlatButton(
onPressed: (){
setState(() {
boxColor = Colors.red;
});
},
padding: EdgeInsets.all(8.0),
child: Text("Change to Red"),
color: Colors.grey,
)
],
);
}
}
This widget has a Container with a initial blue background (boxColor) and a button. When the button is pressed, it will change the boxColor to red. The moment the dragging on the widget is initiated, it is rebuild and defaults to the initial state (at least the Draggable feedback is). After the reordering that doesn't change and the widget is still in it's default state.
My plan here is to have a list of different custom widgets, where the User can modify their content and if they are not happy with the order, they can drag those widgets around and rearrange them.
My question is: How do I preserve the state of my widgets?
I'm thinking of creating a class for each widget with all state relevant variables and use that to build my widgets but that seems very bloated and not really in the mind of flutter. Isn't that supposed to be the role of the state of the StatefulWidget?
EDIT:
So I solved my problem by creating an additional class for my widget state with ChangeNotifier and then moving all my variables that I want to keep track of into this class. So I basically now have two lists, one for my widgets in the reorderable list and one for their states. I still think that this is kinda scuffed. If a widget in my list has additional children of its own, I would need to create separate state classes for each of them that need it and save them somewhere. This can get very messy, very quickly.

Why does the Software Keyboard cause Widget Rebuilds on Open/Close?

I have a screen, which contains a Form with a StreamBuilder. when I load initial data from StreamBuilder, TextFormField show data as expected.
When I tap inside the TextFormField, the software keyboard shows up, which causes the widgets to rebuild. The same happens again when the keyboard goes down again.
Unfortunately, the StreamBuilder is subscribed again and the text box values is replaced with the initial value.
Here is my code:
#override
Widget build(BuildContext context) {
return StreamBuilder(
stream: _bloc.inputObservable(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return TextFormField(
// ...
);
}
return const Center(
child: CircularProgressIndicator(),
);
},
);
}
How do I solve this?
Keyboard causing rebuilds
It makes total sense and is expected that the software keyboard opening causes rebuilds. Behind the scenes, the MediaQuery is updated with view insets. These MediaQueryData.viewInsets make sure that your UI knows about the keyboard obscuring it. Abstractly, the keyboard obscuring a screen causes a change to the window and most of the time to your UI, which requires changes to the UI - a rebuild.
I can make the confident guess that you are using a Scaffold in your Flutter application. Like many other framework widgets, the Scaffold widgets depends (see InheritedWidget) on the MediaQuery (that gets its data from the Window containing your app) using MediaQuery.of(context).
See MediaQueryData for more information.
It all boils down to the Scaffold having a dependency on the view insets. This allows it to resize when these view insets change. Basically, when the keyboard is opened, the view insets update, which allows the scaffold to shrink at the bottom, removing the obscured space.
Long story short, the scaffold adapting to the adjusted view insets requires the scaffold UI to rebuild. And since your widgets are necessarily children of the scaffold (likely the body), your widgets are also rebuilt when that happens.
You can disable the view insets resizing behavior using Scaffold.resizeToAvoidBottomInset. However, this will not necessarily stop the rebuilds as there might still be a dependency on the MediaQuery. I will explain how you should really think about the problem in the following.
Idempotent build methods
You should always build your Flutter widgets in a way where your build methods are idempotent.
The paradigm is that a build call could happen at any point in time, up to 60 times per second (or more if on a higher refresh rate).
What I mean by idempotent build calls is that when nothing about your widget configuration (in the case of StatelessWidgets) or nothing about your state (in the case of StatefulWidgets) changes, the resulting widget tree should be strictly the same. Thus, you do not want to handle any state in build - its only responsibility should be representing the current configuration or state.
The software keyboard opening causing rebuilds is simply a good example for why this is so. Other examples are rotating the device, resizing on web, but it can really be anything as your widget tree starts to get complex (more on that below).
StreamBuilder resubscribing on rebuild
To come back to the original question: in this case, your problem is that you are approaching the StreamBuilder incorrectly. You should not feed it a stream that is recreated each build.
The way stream builders work is by subscribing to the initial stream and then resubscribing whenever the stream is updated. This means that when the stream property of the StreamBuilder widget is different between two build calls, the stream builder will unsubscribe from the first and subscribe to the second (new) stream.
You can see this in the _StreamBuilderBaseState.didUpdateWidget implementation:
if (oldWidget.stream != widget.stream) {
if (_subscription != null) {
_unsubscribe();
_summary = widget.afterDisconnected(_summary);
}
_subscribe();
}
The obvious solution here is that you will want to supply the same stream between different build calls when you do not want to resubscribe. This goes back to idempotent build calls!
A StreamController for example will always return the same stream, which means that it is safe to use stream: streamController.stream in your StreamBuilder. Basically, all controller, behavior subject, etc. implementations should behave this way - as long as you are not recreating your stream, StreamBuilder will properly take care of it!
The faulty function in your case is therefore _bloc.inputObservable(), which creates a new stream each time instead of returning the same one.
Notes
Note that I said that build calls can happen "at any point in time". In reality, you can (technically) control exactly when every build happens in your app. However, a normal app will be so complex that you cannot possibly have control over that, hence, you will want to have idempotent build calls.
The keyboard causing rebuilds is a good example for this.
If you think about it on a high level, this is exactly what you want - the framework and its widget (or widgets that you create) take care of responding to outside changes and rebuilding whenever necessary. Your leaf widgets in the tree should not care about whether a rebuild happens - they should be fine being placed in any environment and the framework takes care of reacting to changes to that environment by rebuilding correspondently.
I hope that I was able to clear this up for you :)
I faced a similar issue in my application. What resolved my issue was to make my "widget tree clean" as suggested by one of the programmers on this forum.
Try moving the definition of your stream to init state. This will prevent your stream from disconnecting and reconnecting every time there is a rebuild.
var datastream;
#override
void initState() {
dataStream = _bloc.inputObservable();
super.initState();
}
#override
Widget build(BuildContext context) {
return StreamBuilder(
stream: dataStream,
builder: (context, snapshot) {
if (snapshot.hasData) {
return TextFormField(
// ...
);
}
return const Center(
child: CircularProgressIndicator(),
);
},
);
}
It can be resolved by creating a stateful widget like following
class StatefulWrapper extends StatefulWidget {
final Function onInit;
final Widget child;
const StatefulWrapper({#required this.onInit, #required this.child});
#override
_StatefulWrapperState createState() => _StatefulWrapperState();
}
class _StatefulWrapperState extends State<StatefulWrapper> {
#override
void initState() {
if (widget.onInit.call != null) {
widget.onInit();
}
super.initState();
}
#override
Widget build(BuildContext context) {
return widget.child;
}
}
and wrapping the stateless widget using the wrapper
Widget body;
class WidgetStateless extends StatelessWidget {
WidgetStateless();
#override
Widget build(BuildContext context) {
return StatefulWrapper(
onInit: () async {
//Create the body widget in the onInit
body = Container();
},
child : body
)
}

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.