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());
}