-
Notifications
You must be signed in to change notification settings - Fork 26.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
FutureBuilder rebuilds unnecessarily when we long press on FAB button or appBar UI. #11426
Comments
If I change Page2 FutureBuilder to its equivalent in StreamBuilder, the problem goes away. class Page2 extends StatefulWidget {
@override
_Page2State createState() => new _Page2State();
}
class _Page2State extends State<Page2> {
Stream<String> myStream;
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('Page 2'),
actions: <Widget>[
new IconButton(
icon: new Icon(Icons.search),
tooltip: 'Search',
onPressed: () {} // does nothing
)
],
),
body: new StreamBuilder<String>(
stream: myStream, // a Future<String> or null
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
case ConnectionState.waiting:
return new Center(child: new CircularProgressIndicator());
default:
if (snapshot.hasError)
return new Text('Error: ${snapshot.error}');
else
return new Center(child: new Text('Result: ${snapshot.data}'));
}
},
),
floatingActionButton: new FloatingActionButton(
child: new Icon(Icons.add),
tooltip: 'Add',
onPressed: () {}
),
);
}
Future<String> _simulateNetworkRequest() {
print('Request made');
return new Future.delayed(const Duration(seconds: 2), () =>
'A random text snippet from the Web');
}
@override
void initState() {
super.initState();
myStream = _simulateNetworkRequest()?.asStream();
}
} |
This might be fixed with my work on making pages build less (not sure I've landed it yet), but I'll take a look at some point. |
this issue still persist import 'dart:async';
import 'package:flutter/material.dart';
class Data {
static Future<List<String>> mockFuture() async {
await Future.delayed(Duration(milliseconds: 1500), () {});
return [
'https://swebtoon-phinf.pstatic.net/20180115_46/1515982322405V9H8X_JPEG/10_EC8DB8EB84A4EC9DBC_ipad.jpg',
'https://swebtoon-phinf.pstatic.net/20151006_293/1444125151125BQylv_JPEG/_EB93BBEB80AF_EBA38CEABCA5_E29587EABCB1_EB9384EB84B0_ipa.jpg',
'https://swebtoon-phinf.pstatic.net/20150608_183/1433732708022aySQw_JPEG/EC8DB8EB84A4EC9DBC_ipad.jpg',
'https://pm1.narvii.com/5905/aab79b9fbf033cb116e1272460d9b900692e672b_hq.jpg',
'https://pm1.narvii.com/5905/92822c13952a7c280b49b9507599aabe695b5933_hq.jpg',
'https://pm1.narvii.com/5905/30ed9dd47001fbd89cef61e58b0121611ed30b69_hq.jpg',
'https://pm1.narvii.com/5905/25c1ca23b78b9ca78aeb9d02fae841707fc4fb1c_hq.jpg',
'https://pm1.narvii.com/5905/1c04748521e3ebfaabcd8c0cd8a1bc2344156d1b_hq.jpg',
'https://pm1.narvii.com/5905/f87a64c47f11f4063e42dfb8a67db799b67f5478_hq.jpg',
];
}
}
///
///image list
///
class NetImageList1 extends StatelessWidget {
@override
Widget build(BuildContext context) {
print('NetImageList1 built!');
return Scaffold(
appBar: AppBar(title: Text('Thriller')),
backgroundColor: Colors.grey,
body: FutureBuilder(
future: Data.mockFuture(),
builder: (_, AsyncSnapshot<List<String>> snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
final rows = snapshot.data.map((it) => ImageWidget(it)).toList();
return ListView.builder(
itemCount: rows.length,
addAutomaticKeepAlives: false,
itemBuilder: (_, i) => rows[i],
);
}
return Center(child: CircularProgressIndicator());
},
),
);
}
}
///
///image widget
///
class ImageWidget extends StatelessWidget {
final String imageUrl;
const ImageWidget(this.imageUrl);
@override
Widget build(BuildContext context) {
print('Image widget built ${imageUrl.hashCode}');
return FutureBuilder(
future: mockLoading(),
builder: (_, AsyncSnapshot<String> snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
return Padding(
padding: const EdgeInsets.all(4.0),
child: InkWell(
onTap: () => gotoDetail(context, imageUrl),
child: Card(
child: AspectRatio(
aspectRatio: 16 / 9, child: Image.network(imageUrl)),
),
),
);
}
return AspectRatio(
aspectRatio: 16 / 9,
child: Container(
color: Colors.white,
child: Center(
child: CircularProgressIndicator(),
),
));
},
);
}
gotoDetail(BuildContext context, String data) {
Navigator.push(
context, MaterialPageRoute(builder: (_) => NetImageDetail(data)));
}
Future<String> mockLoading() async {
await Future.delayed(Duration(milliseconds: 250), () {});
return imageUrl;
}
}
///
///detail screen
///
class NetImageDetail extends StatelessWidget {
final String imageUrl;
NetImageDetail(this.imageUrl);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Detail')),
body: Center(child: Image.network(imageUrl)),
);
}
} |
Hello, To get past this, we can call the Future somewhere other than in the build function. For example, in the initState, and save it in a member variable, and pass this variable to the FutureBuilder. So instead of having:
We should have:
and then
|
This is actually not a bug, but a feature. The The build method should be pure, e.g. not cause any side-effects (except for subscriptions to inherited widgets). In a
@AbdulRahmanAlHamali provided a correct solution to the problem. |
Is there a not missing after should? |
Yep, corrected |
@sir-boformer Do you mean we shouldn't use |
No. I said that Actually using a |
@sir-boformer noob question but how to ensure the build function is pure? Could you show some examples? |
Bad: Color _randomColor;
Widget build(BuildContext context) {
someService.loadData(); // not pure
setState(() { // not pure
this._randomColor = getRandomColor(); // not pure
})
return Container(
color: this._randomColor,
child: StreamBuilder(
stream = this._createStream(), // not pure
...
),
);
} Good: Color _randomColor;
Stream<...> _stream;
void initState() {
super.initState();
someService.loadData();
this._randomColor = getRandomColor();
this._stream = this._createStream();
}
Widget build(BuildContext context) { // pure
return _buildStreamBuilder(); // this is ok
}
Widget _buildStreamBuilder() { // pure
// only reads variables and builds widget
return Container(
color: this._randomColor,
child: StreamBuilder(
stream: this._stream,
...
),
);
} |
@sir-boformer Wow. Thank you for being a great tutor! So, there should be nothing that changes state inside |
Not really. There is no "inside". When the Same with the Just assume that the |
This is the answer I was looking for and it really works...thanks |
I can not reproduce the initial example
Are you still seeing this? |
@zoechi This is not a bug, just a misuse of |
I see. What about https://docs.flutter.io/flutter/widgets/FutureBuilder-class.html
|
yep, it was there all along. perfect! |
Sounds like this can be closed then. |
What if the futureBuilder is in a pageView and the future depends on the position |
Yeah, maybe better to update the demo in that video too .
To
|
I think the summary of the problem is well laid out in that article, but I don't see why using a memoizer in combination with a Here's an example of how to fix this with Provider: class MyWidget extends StatelessWidget {
// Future<String> callAsyncFetch() => Future.delayed(Duration(seconds: 2), () => "hi");
@override
Widget build(BuildContext context) {
// print('building widget');
return FutureProvider<String>(
create: (_) {
// print('calling future');
return callAsyncFetch();
},
child: Consumer<String>(
builder: (_, value, __) => Text(value ?? 'Loading...'),
),
);
}
} For those interested I wrote a full-blown article summarizing the problem and different approaches to solve it using |
I tried moving the function outside as you guys suggested but it doesn't work, it's getting call all the time:
|
@frank06 I had a look to your article but could you show what do you have inside |
@erperejildo what do you mean "inside |
Sorry @frank06, I thought it was a custom method inside a provider you had created. Just checked what it's exactly (https://pub.dev/documentation/provider/latest/provider/FutureProvider-class.html)
That works fine but I still see the print every time I scroll on me list. Does it mean it is also being created every time? Edit: when I log out it still showing |
@erperejildo You should place the |
@erperejildo Ok let me have a look later. Were you able to make it work with
|
with I tried with:
but the behaviour is exactly the same: I see the print when I scroll to the widget. I'm sure this is what you mentioned, something more related with the way I put my widget on tree, but tbh not sure how should I move it without breaking the global scroll. |
@erperejildo Sorry I can't see what you are trying to do. There's no problem in using |
Thanks @frank06 but I didn't want to take more of your time so I've been investigating a little bit more creating a new fresh and simpler app just with this. I'll show what I've done in case I can help someone else. First of all I worked on the cache for my provider creating this widget:
This is using this global provider:
The provider returns a http response which is cached: first time we enter on the page it loads and it saves the value on
We can also move the
First time we'll see the 5 wait and the loading message, and second one, we'll see the message as well, but the value will be there already on the screen. This answers my question asking why is the I recorded this situation where I have 2 This is the widget showing these elements of the scroll:
I realised that the one loading again is the one on top. If I reorder them it will display the other loading message. This part is what I don't really understand |
This thread has been automatically locked since there has not been any recent activity after it was closed. If you are still experiencing a similar issue, please open a new bug, including the output of |
We have two pages - Page 1(home route) and Page 2. Page 2 fetches text content from the Web and display it through a FutureBuilder widget. When we long press on the FAB button of Page 2 (to reveal tooltip), FutureBuilder refreshes and another network request is made for the same content.
Steps to Reproduce
Now long press on the search icon on the appbar. FutureBuilder rebuilds again.
The bug only occurs when the FutureBuilder is not located on the home route. If we declare FutureBuilder on Page 1 instead of Page 2, the bug is not observed.
Flutter Doctor
The text was updated successfully, but these errors were encountered: