flutter - 在任何交互回调中使用 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);
解决方案
这是正确的使用方法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
}
);
推荐阅读
- java - 如何使用 Java 在 MSSQL 中以“2016-01-01T19:33:15-05:00”格式插入日期时间?
- javascript - 如何用鼠标中键在后台打开新标签?
- python - 我正在尝试在 excel 列上设置条件,以使用 python openpyxl 从一张表获取一些数据到另一张 excel 表
- python - 连接 MySQL 时出现“KeyError: 0” (get_default_isolation_level)
- reactjs - 如果错误仍然存在,如何防止提交此表单?
- c - 如何标记文件内容?
- wordpress - 当它是顶级类别的一部分时,我想显示一条消息
- java - 在单元测试中注入不可变的配置属性
- javascript - 单击按钮时重置并重新启动动画
- node.js - npm start-react-app test 不创建描述的文件夹结构