https://github.com/flutter/flutter/issues/18828
https://blog.csdn.net/u011272795/article/details/83010974 <<<<<<<===============
https://medium.com/saugo360/flutter-my-futurebuilder-keeps-firing-6e774830bc2
alone!
What is the problem?
FutureBuilder widget that Flutter provides us to create widgets based on the state of some future, keeps re-firing that future every time a rebuild happens! The problem is best manifested with this simple example:
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: HomeScreen()
);
}
}
class HomeScreen extends StatefulWidget {
@override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
bool _switchValue;
@override
void initState() {
super.initState();
this._switchValue = false;
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Switch(
value: this._switchValue,
onChanged: (newValue) {
setState(() {
this._switchValue = newValue;
});
},
),
FutureBuilder(
future: this._fetchData(),
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
case ConnectionState.waiting:
return Center(
child: CircularProgressIndicator()
);
default:
return Center(
child: Text(snapshot.data)
);
}
}
),
],
),
);
}
_fetchData() async {
await Future.delayed(Duration(seconds: 2));
return 'REMOTE DATA';
}
}
- FutureBuilder that loads remote data and displays it on the screen.
- _fetchData. This function simulates a server that returns its result after two seconds.
- _switchValue, which is then fed into the switch itself, to change its actual value.
If you run this app, the following happens:
FutureBuilder goes through its whole life-cycle again! It re-fetches the future, causing unneeded traffic, and shows the loading again, causing bad user experience.
This problem manifests itself in a variety of ways. In some cases it’s not even as obvious as the example above. For example:
- Network traffic being generated from pages that are not currently on screen
- Hot reloading not working properly
- Losing the Navigator state when updating values in some Inherited Widgets
- etc…
But what is the cause of all of this? And how can we solve it?
The didUpdateWidget problem
Note: In this section I will take a closer look at how FutureBuilder works. If you’re not interested in this, you can skip to the solution.
FutureBuilder, this method looks like this:
@override
void didUpdateWidget(FutureBuilder<T> oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.future != widget.future) {
if (_activeCallbackIdentity != null) {
_unsubscribe();
_snapshot = _snapshot.inState(ConnectionState.none);
}
_subscribe();
}
}
It’s basically saying: if, when rebuilt, the new widget has a different Future instance than the old one, then repeat everything: unsubscribe, and subscribe again.
Well, that’s the key! If we provide it with the same future, then it will not refire!
instance. Our function is doing exactly the same work, but then returning a new future, different from the old one.
memoization.
Solution: Memoize the future
FutureBuilder always receives the same future instance.
AsyncMemoizer. This memoizer does exactly what we want! It takes an asynchronous function, calls it the first time it is called, and caches its result. For all subsequent calls to the function, the memoizer returns the same previously calculated future.
AsyncMemoizerin our widget:
final AsyncMemoizer _memoizer = AsyncMemoizer();
at every rebuild, which basically beats the purpose. You should instantiate it either in a StatefulWidget, or somewhere where it can persist.
_fetchData function to use that memoizer:
_fetchData() {
return this._memoizer.runOnce(() async {
await Future.delayed(Duration(seconds: 2));
return 'REMOTE DATA';
});
}
- AsyncMemoizer.runOnce which does exactly what it sounds like; it runs the function only once, and when called again, returns the cached future.
FutureBuilder now only fires the first time around:
AsyncMemoizer to pass the same future instance every time.
I hope this has been helpful! Let me know what you think!