Repository navigation
Closed
Closed
Copy link
Labels
f: routesNavigator, Router, and related APIs.Navigator, Router, and related APIs.frameworkflutter/packages/flutter repository. See also f: labels.flutter/packages/flutter repository. See also f: labels.
Description
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
- Run the code below.
- Click 'open page' to navigate to the Page 2. We simulate network request with Future.delayed().
- CircularProgessIndicator is shown for a while then the content is shown.
- Long press on the FAB button. FutureBuilder rebuilds!
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.
import 'dart:async';
import 'package:flutter/material.dart';
void main() {
runApp(new MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Debugging Futurebuilder',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
routes: <String, WidgetBuilder>{
'/page2': (BuildContext context) => new Page2(),
},
home: new Page1(),
);
}
}
class Page1 extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('Page 1'),
),
body: new Center(
child: new RaisedButton(
child: new Text('Open page'),
onPressed: () => Navigator.of(context).pushNamed('/page2'),
)
),
);
}
}
class Page2 extends StatelessWidget {
@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 FutureBuilder<String>(
future: _simulateNetworkRequest(), // 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() {
return new Future.delayed(const Duration(seconds: 2), () =>
'A random text snippet from the Web');
}
}Flutter Doctor
[√] Flutter (on Microsoft Windows [Version 10.0.15063], locale en-US, channel master)
• Flutter at C:\Users\tzm\Downloads\flutter_sdk
• Framework revision 1d8705f3db (22 hours ago), 2017-07-26 11:44:50 -0700
• Engine revision 3a12bc092d
• Tools Dart version 1.25.0-dev.7.0
[√] Android toolchain - develop for Android devices (Android SDK 25.0.3)
• Android SDK at C:\Users\tzm\AppData\Local\Android\sdk
• Platform android-26, build-tools 25.0.3
• Java binary at: C:\Program Files\Android\Android Studio\jre\bin\java
• Java version OpenJDK Runtime Environment (build 1.8.0_112-release-b06)
[√] Android Studio (version 2.3)
• Android Studio at C:\Program Files\Android\Android Studio
• Java version OpenJDK Runtime Environment (build 1.8.0_112-release-b06)
[√] IntelliJ IDEA Community Edition (version 2017.1)
• Flutter plugin version 15.2
• Dart plugin version 171.4424
[√] Connected devices
• SM G7102 • 772e5ea4 • android-arm • Android 4.4.2 (API 19)
Reacted by Neil Jay Warner
Metadata
Metadata
Assignees
Labels
f: routesNavigator, Router, and related APIs.Navigator, Router, and related APIs.frameworkflutter/packages/flutter repository. See also f: labels.flutter/packages/flutter repository. See also f: labels.
Activity
[-]FutureBuilder rebuilds unnecessarily when we long press on FAB and appBar UI.[/-][+]FutureBuilder rebuilds unnecessarily when we long press on FAB button or appBar UI.[/+]cornelius-tan commentedon Jul 29, 2017
If I change Page2 FutureBuilder to its equivalent in StreamBuilder, the problem goes away.
Now press and hold the FAB button in Page2, the builder will not rebuild which is what we want. I guess the problem lies with the bundled async library because the Future fires for unknown reason. Further investigation needed.
Hixie commentedon May 8, 2018
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.
putraxor commentedon Jun 23, 2018
this issue still persist
AbdulRahmanAlHamali commentedon Aug 18, 2018
Hello,
I believe the problem here is that didUpdateWidget of the FutureBuilder state is being called every time a rebuild is issued. This function checks if the old future object is different from the new one, and if so, refires the FutureBuilder.
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
sir-boformer commentedon Aug 19, 2018
This is actually not a bug, but a feature.
The
buildmethod can be called many times per second, and there is no way to prevent that, so you should not make the assumption thatbuildis only called once. That means yourmockLoading()function is called many times, which is not your intention.The build method should be pure, e.g. not cause any side-effects (except for subscriptions to inherited widgets). In a
StatelessWidget, the output of the build method should only depend on:buildis called again when the data changes)@AbdulRahmanAlHamali provided a correct solution to the problem.
zoechi commentedon Aug 20, 2018
@sir-boformer
Is there a not missing after should?
sir-boformer commentedon Aug 20, 2018
Yep, corrected
hyochan commentedon Aug 26, 2018
@sir-boformer Do you mean we shouldn't use
FutureBuilderinsideStatefulWidget?sir-boformer commentedon Aug 26, 2018
No. I said that
buildfunctions must be pure.Actually using a
FutureBuilderusually implies that the enclosing widget is stateful, because you have to store the future you are waiting for in aStateobject.hyochan commentedon Aug 26, 2018
@sir-boformer noob question but how to ensure the build function is pure? Could you show some examples?
11 remaining items
jerryzhoujw commentedon Aug 7, 2019
Yeah, maybe better to update the demo in that video too .
From
To
bestK commentedon Sep 1, 2019
https://medium.com/saugo360/flutter-my-futurebuilder-keeps-firing-6e774830bc2
frank06 commentedon Dec 20, 2019
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
StatefulWidgetwhen you can already cache the value in itsState.Here's an example of how to fix this with Provider:
For those interested I wrote a full-blown article summarizing the problem and different approaches to solve it using
StatefulWidget,providerandflutter_hooks. Hope it helps clarify this unfortunately very common issueerperejildo commentedon Jan 10, 2020
I tried moving the function outside as you guys suggested but it doesn't work, it's getting call all the time:
erperejildo commentedon Jan 10, 2020
@frank06 I had a look to your article but could you show what do you have inside
FutureProvider? It's not clear to mefrank06 commentedon Jan 10, 2020
@erperejildo what do you mean "inside
FutureProvider? There's acallAsyncFetchsample function (for which I even provided a sample implementation), that is all. Did you see the live example I did with Streams, a bit below? That does work. Can you try doing the same with your future (instead of a stream)? It looks almost exactly the same.erperejildo commentedon Jan 10, 2020
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
Loadedfrank06 commentedon Jan 10, 2020
@erperejildo You should place the
createfurther up the widget tree. I would try doing this with aStreamBuilder/FutureBuilderand understanding how it works before usingProvidererperejildo commentedon Jan 10, 2020
Got you. I had my

StreamBuilderbefore inside theListViewso you recommendation is having this outside?Let me show you more graphically what I have:
That list contains 2 different kind of sets: one that I load from a json file previously when the app is loading (to avoid a slow Hero transition to this page) and the one I get from Firebase. Both of them are as I said on the same scroll container.
If I move the
createfurther up the widget tree, wouldn't be the default sets dependent on thisStreamProvider?frank06 commentedon Jan 10, 2020
@erperejildo Ok let me have a look later. Were you able to make it work with
StreamBuilder? ForStreamProvider, I'm pretty sure you have to use the.valueconstructor there instead ofcreate. See the docserperejildo commentedon Jan 10, 2020
with
StreamBuilderandStreamProvidermy widget works fine. The only thing is that, when I scroll or when I move between pages this will call again and I only want to trigger this function when I log in/out.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.
frank06 commentedon Jan 10, 2020
@erperejildo Sorry I can't see what you are trying to do. There's no problem in using
StreamBuilders inside aListView. Builders/build are designed to be called a million times. You can also register the provider value up in the tree and consume it further down, but it depends on what you want to do. Can you send me an e-mail with a bit more of your widget tree? And I'll help you out there, so we don't pollute this thread. E-mail is on my github profile.erperejildo commentedon Jan 13, 2020
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
result. If we leave this page and we enter again we won't load anything.We can also move the
loadResult()to an init function that we can call from the main page for example, and we could avoid the unnecessary loading when entering on our page.you shouldn't see this loading...message was one of the part confusing me. This was triggering all the time so I thought that it was being loaded again, but it's not. It's shown all the time.For example, to see this more clear we can have this:
await Future.delayed(Duration(seconds: 5));instead of this:
await http.get('https://jsonplaceholder.typicode.com/posts');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
FutureBuilderloading all the time... but not completely.I recorded this situation where I have 2
StreamBuilders. Both are the same (just returning a different layout).As you can see, when I scroll to top again, one of them (not both, I don't know why) is reloading:
https://youtu.be/ScSmp0d1BOY
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
github-actions commentedon Aug 16, 2021
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
flutter doctor -vand a minimal reproduction of the issue.