首页 > 解决方案 > 在任何交互回调中使用 FutureBuilder 时,它的 future 方法被调用了两次

问题描述

我有一个自定义小部件,我在其中更新了水平和垂直拖动结束回调中的数据列表。在该回调中,我使用 FutureBuilder 返回并更新了数据。但是从 FutureBuilder 的未来事件返回的方法被调用了两次。

import 'dart:math';

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        // This is the theme of your application.
        //
        // Try running your application with "flutter run". You'll see the
        // application has a blue toolbar. Then, without quitting the app, try
        // changing the primarySwatch below to Colors.green and then invoke
        // "hot reload" (press "r" in the console where you ran "flutter run",
        // or simply save your changes to "hot reload" in a Flutter IDE).
        // Notice that the counter didn't reset back to zero; the application
        // is not restarted.
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key, required this.title}) : super(key: key);

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  late List<SampleData> chartData;

  @override
  void initState() {
    getChartData();
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          // Here we take the value from the MyHomePage object that was created by
          // the App.build method, and use it to set our appbar title.
          title: Text(widget.title),
        ),
        body: Center(
          child: LoadMoreWidget(
              data: chartData, loadMoreViewBuilder: onLoadMoreWidgetCalled),
        ));
  }

  Widget onLoadMoreWidgetCalled(BuildContext context) {
    return FutureBuilder(
        future: updateData(),
        builder: (context, snapshot) {
          if (snapshot.hasData && !snapshot.hasError) {
            return Container();
          } else {
            return getProgressIndicator();
          }
        });
  }

  List<SampleData> getChartData() {
    chartData = [
      SampleData(0, 42),
      SampleData(1, 47),
      SampleData(2, 33),
      SampleData(3, 49),
      SampleData(4, 54),
      SampleData(5, 41),
      SampleData(6, 58),
      SampleData(7, 51),
      SampleData(8, 98),
      SampleData(9, 41),
      SampleData(10, 53),
      SampleData(11, 72),
      SampleData(12, 86),
      SampleData(13, 52),
      SampleData(14, 94),
      SampleData(15, 92),
      SampleData(16, 86),
      SampleData(17, 72),
      SampleData(18, 94)
    ];
    return chartData;
  }

  Widget getProgressIndicator() {
    return Align(
        alignment: Alignment.centerRight,
        child: Padding(
            padding: const EdgeInsets.only(bottom: 22),
            child: Container(
                width: 50,
                alignment: Alignment.centerRight,
                decoration: BoxDecoration(
                    gradient: LinearGradient(colors: <Color>[
                  Colors.white.withOpacity(0.0),
                  Colors.white.withOpacity(0.74)
                ], stops: const <double>[
                  0.0,
                  1
                ])),
                child: const SizedBox(
                    height: 35,
                    width: 35,
                    child: CircularProgressIndicator(
                        valueColor:
                            AlwaysStoppedAnimation<Color>(Colors.blueGrey),
                        backgroundColor: Colors.transparent,
                        strokeWidth: 3)))));
  }

  Future<List<SampleData>> updateData() async {
    for (int i = 0; i < 2; i++) {
      var newIndex = chartData[chartData.length - 1].x + 1;
      chartData.add(SampleData(newIndex, (Random().nextInt(60) + 30)));
    }
    await Future.delayed(const Duration(seconds: 1), () {});
    return chartData;
  }
}

class SampleData {
  SampleData(this.x, this.y);
  final int x;
  final num y;
}

class LoadMoreWidget extends StatefulWidget {
  const LoadMoreWidget(
      {Key? key, required this.data, required this.loadMoreViewBuilder})
      : super(key: key);

  final List<dynamic> data;

  final LoadMoreViewBuilderCallback loadMoreViewBuilder;

  @override
  State<LoadMoreWidget> createState() => _LoadMoreWidgetState();
}

class _LoadMoreWidgetState extends State<LoadMoreWidget> {
  @override
  Widget build(BuildContext context) {
    List<dynamic> data = widget.data;
    return LayoutBuilder(
        builder: (BuildContext context, BoxConstraints constraints) {
      return GestureDetector(
          onHorizontalDragEnd: (DragEndDetails details) => {
                (widget.loadMoreViewBuilder(context) as FutureBuilder)
                    .future!
                    .whenComplete(
                        () => {data = widget.data, setState(() => {})})
              },
          onVerticalDragEnd: (DragEndDetails details) => {
                (widget.loadMoreViewBuilder(context) as FutureBuilder)
                    .future!
                    .whenComplete(
                        () => {data = widget.data, setState(() => {})})
              },
          child: Stack(children: [
            ListView.builder(
              itemBuilder: (context, index) {
                return Card(
                    child: Row(
                  children: [
                    Text('x:' + data[index].x!.toString()),
                    Text('y:' + data[index].y!.toString()),
                  ],
                ));
              },
              itemCount: widget.data.length,
            ),
            (widget.loadMoreViewBuilder(context) as FutureBuilder)
                .builder(context, const AsyncSnapshot.nothing())
          ]));
    });
  }
}

typedef LoadMoreViewBuilderCallback = Widget Function(BuildContext context);

示例:https ://www.syncfusion.com/downloads/support/directtrac/general/ze/load_more_widget_example-1670437592

标签: flutterdartflutter-futurebuilder

解决方案


这是正确的使用方法FutureBuilder

return FutureBuilder<DataClass?>(
  future: updateData(), // async call that returns DataClass object
  builder: (BuildContext context, AsyncSnapshot snapshot) {
    if (snapshot.hasError) {
      return const Text("call returns error: ${snapshot.error.toString()}");
    }
    if (snapshot.connectionState != ConnectionState.done) {
      return const Center(child: CircularProgressIndicator());
    }
    if (!snapshot.hasData) {
      return const Text("call returns a null");
    }
    final data = snapshot.data as DataClass;
    // Here you create and return a Widget to display the data returned
  }
);

推荐阅读