Flutter - FutureBuilder 在热重载时触发两次
Flutter - FutureBuilder fires twice on hot reload
在我的 flutter 项目中,当我在模拟器中启动项目时一切正常,future builder 只触发一次,但是当我热重载时,FutureBuilder 触发两次导致错误知道如何解决这个问题吗?
Future frameFuture() async {
var future1 = await AuthService.getUserDataFromFirestore();
var future2 = await GeoService.getPosition();
return [future1, future2];
}
@override
void initState() {
user = FirebaseAuth.instance.currentUser!;
super.initState();
}
@override
Widget build(BuildContext context) {
return FutureBuilder(
future: frameFuture(),
builder: (context, snap) {
if (snap.connectionState == ConnectionState.done && snap.hasData) return HomePage();
else return Container(
color: Colors.black,
child: Center(
child: spinKit,
),
);
}
);
}
我解决了这个问题。我将 Future
函数放在 initState
中,然后在 FutureBuilder
中使用变量。我不确定为什么它会这样工作,但这是代码:
var futures;
Future frameFuture() async {
var future1 = await AuthService.getUserDataFromFirestore();
var future2 = await GeoService.getPosition();
return [future1, future2];
}
@override
void initState() {
user = FirebaseAuth.instance.currentUser!;
super.initState();
futures = frameFuture();
}
@override
Widget build(BuildContext context) {
return FutureBuilder(
future: futures,
builder: (context, snap) {
if (snap.connectionState == ConnectionState.done && snap.hasData) return HomePage();
else return Container(
color: Colors.black,
child: Center(
child: spinKit,
),
);
}
);
}
您已经想到的解决方案是将未来的加载过程移至 StatefulWidget 的 initState,但我将解释其发生的原因:
你在构建方法中这样调用你的未来:
@override
Widget build(BuildContext context) {
return FutureBuilder(
future: frameFuture(),
问题是 Flutter 每次渲染 Widget 时都会调用构建方法,每当依赖项发生变化(InheritedWidget,setState)或 Flutter 决定重建它时。所以每次你重绘你的 UI frameFuture()
被调用时,这会让你的构建方法产生副作用(这个异步调用),这是不应该的,并且鼓励小部件没有副作用。
通过将异步计算移动到 initState
,您只需调用它一次,然后从您的状态访问缓存变量 futures
。
这里还有 FutureBuilder class
文档的摘录
“未来必须早先获得,例如在 State.initState、State.didUpdateWidget 或 State.didChangeDependencies 期间。它不能在 State.build 或 [= 期间创建33=]构造FutureBuilder时调用的方法。如果future是和FutureBuilder同时创建的,那么每次FutureBuilder的parent重建时,都会重启异步任务。"
希望这能阐明解决方案的原因。
即使从 initState
调用 Future 时也会发生这种情况。我之前使用的解决方案感觉很丑。
最干净的解决方案是使用 AsyncMemoizer
,它实际上只是检查一个函数是否在
之前是 运行
import 'package:async/async.dart';
class SampleWid extends StatefulWidget {
const SampleWid({Key? key}) : super(key: key);
final AsyncMemoizer asyncResults = AsyncMemoizer();
@override
_SampleWidState createState() => _SampleWidState();
}
class _SampleWidState extends State<SampleWid> {
@override
void initState() {
super.initState();
_getData();
}
@override
Widget build(BuildContext context) {
return FutureBuilder(
future: widget.asyncResults.future,
builder: (context, snapshot) {
if (!snapshot.hasData) return yourLoadingAnimation();
// ... Do things with the data!
});
}
// The async and await here aren't necessary.
_getData() async () {
await widget.asyncResults.runOnce(() => yourApiCall());
}
}
令人惊讶的是,没有 .reset()
方法。似乎强制重新运行的最好方法是用新的AsyncMemoizer().
覆盖它你可以像这样轻松地做到这一点
_getData() async ({bool reload = false}) {
if (reload) widget.asyncResults = AsyncMemoizer();
await widget.asyncResults.runOnce(() => yourApiCall());
}
在我的 flutter 项目中,当我在模拟器中启动项目时一切正常,future builder 只触发一次,但是当我热重载时,FutureBuilder 触发两次导致错误知道如何解决这个问题吗?
Future frameFuture() async {
var future1 = await AuthService.getUserDataFromFirestore();
var future2 = await GeoService.getPosition();
return [future1, future2];
}
@override
void initState() {
user = FirebaseAuth.instance.currentUser!;
super.initState();
}
@override
Widget build(BuildContext context) {
return FutureBuilder(
future: frameFuture(),
builder: (context, snap) {
if (snap.connectionState == ConnectionState.done && snap.hasData) return HomePage();
else return Container(
color: Colors.black,
child: Center(
child: spinKit,
),
);
}
);
}
我解决了这个问题。我将 Future
函数放在 initState
中,然后在 FutureBuilder
中使用变量。我不确定为什么它会这样工作,但这是代码:
var futures;
Future frameFuture() async {
var future1 = await AuthService.getUserDataFromFirestore();
var future2 = await GeoService.getPosition();
return [future1, future2];
}
@override
void initState() {
user = FirebaseAuth.instance.currentUser!;
super.initState();
futures = frameFuture();
}
@override
Widget build(BuildContext context) {
return FutureBuilder(
future: futures,
builder: (context, snap) {
if (snap.connectionState == ConnectionState.done && snap.hasData) return HomePage();
else return Container(
color: Colors.black,
child: Center(
child: spinKit,
),
);
}
);
}
您已经想到的解决方案是将未来的加载过程移至 StatefulWidget 的 initState,但我将解释其发生的原因: 你在构建方法中这样调用你的未来:
@override
Widget build(BuildContext context) {
return FutureBuilder(
future: frameFuture(),
问题是 Flutter 每次渲染 Widget 时都会调用构建方法,每当依赖项发生变化(InheritedWidget,setState)或 Flutter 决定重建它时。所以每次你重绘你的 UI frameFuture()
被调用时,这会让你的构建方法产生副作用(这个异步调用),这是不应该的,并且鼓励小部件没有副作用。
通过将异步计算移动到 initState
,您只需调用它一次,然后从您的状态访问缓存变量 futures
。
这里还有 FutureBuilder class
文档的摘录“未来必须早先获得,例如在 State.initState、State.didUpdateWidget 或 State.didChangeDependencies 期间。它不能在 State.build 或 [= 期间创建33=]构造FutureBuilder时调用的方法。如果future是和FutureBuilder同时创建的,那么每次FutureBuilder的parent重建时,都会重启异步任务。"
希望这能阐明解决方案的原因。
即使从 initState
调用 Future 时也会发生这种情况。我之前使用的解决方案感觉很丑。
最干净的解决方案是使用 AsyncMemoizer
,它实际上只是检查一个函数是否在
import 'package:async/async.dart';
class SampleWid extends StatefulWidget {
const SampleWid({Key? key}) : super(key: key);
final AsyncMemoizer asyncResults = AsyncMemoizer();
@override
_SampleWidState createState() => _SampleWidState();
}
class _SampleWidState extends State<SampleWid> {
@override
void initState() {
super.initState();
_getData();
}
@override
Widget build(BuildContext context) {
return FutureBuilder(
future: widget.asyncResults.future,
builder: (context, snapshot) {
if (!snapshot.hasData) return yourLoadingAnimation();
// ... Do things with the data!
});
}
// The async and await here aren't necessary.
_getData() async () {
await widget.asyncResults.runOnce(() => yourApiCall());
}
}
令人惊讶的是,没有 .reset()
方法。似乎强制重新运行的最好方法是用新的AsyncMemoizer().
覆盖它你可以像这样轻松地做到这一点
_getData() async ({bool reload = false}) {
if (reload) widget.asyncResults = AsyncMemoizer();
await widget.asyncResults.runOnce(() => yourApiCall());
}