Flutter:Future Builder重复调用API且DataTable不渲染问题求助
问题与解决方案:FutureBuilder+DataTable实现API数据渲染问题修复
问题描述
- FutureBuilder触发两次API调用,第一次请求正常返回响应,第二次请求未携带请求头导致报错;
- DataTable未渲染任何数据。
核心修复点
1. 解决重复API请求
直接在build方法中调用unpaid()会导致每次Widget重建时都发起新请求,需将Future实例在initState中初始化并保存为成员变量:
- 在State类中添加
late Future<List<UnpaidDetails>> _unpaidFuture; - 在
initState中赋值:_unpaidFuture = unpaid(); - FutureBuilder的
future参数改为_unpaidFuture
2. 修复DataTable渲染问题
- 完善FutureBuilder的状态处理:添加加载状态(
CircularProgressIndicator)和错误状态提示; - 确保
UnpaidDetails的fromJson方法正确映射month和price字段(需与API返回的JSON字段名一致); - 简化Dataclass为无状态Widget(无需State,因为数据是外部传入的不可变数据)
优化后的完整代码
import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; import 'package:shared_preferences/shared_preferences.dart'; // 确保UnpaidDetails类的fromJson方法正确解析字段 class UnpaidDetails { final String month; final double price; UnpaidDetails({required this.month, required this.price}); factory UnpaidDetails.fromJson(Map<String, dynamic> json) { return UnpaidDetails( month: json['month'] ?? '', // 根据实际API字段调整 price: (json['price'] ?? 0.0).toDouble(), // 根据实际API字段调整 ); } } class HomeScreen extends StatefulWidget { const HomeScreen({super.key}); @override State<HomeScreen> createState() => _HomeScreenState(); } class _HomeScreenState extends State<HomeScreen> { late Future<List<UnpaidDetails>> _unpaidFuture; @override void initState() { super.initState(); // 在initState中初始化Future,避免重复调用API _unpaidFuture = unpaid(); } Future<String?> getToken() async { final SharedPreferences prefs = await SharedPreferences.getInstance(); return prefs.getString('token'); } Future<List<UnpaidDetails>> unpaid() async { String? token = await getToken(); // 检查token是否为空,避免无效请求 if (token == null) { throw Exception('Token not found'); } final response = await http.get( Uri.parse('https://test.url/api/unpaid'), headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', 'Authorization': 'Bearer $token', }, ); if (response.statusCode == 200) { var result = jsonDecode(response.body); List jsonResponse = result["UnpaidDetails"] as List; return jsonResponse.map((e) => UnpaidDetails.fromJson(e)).toList(); } else { throw Exception('Failed to load unpaid details: ${response.statusCode}'); } } @override Widget build(BuildContext context) { return Scaffold( body: FutureBuilder<List<UnpaidDetails>>( future: _unpaidFuture, builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { // 加载状态 return const Center(child: CircularProgressIndicator()); } else if (snapshot.hasError) { // 错误状态 return Center(child: Text('Error: ${snapshot.error}')); } else if (snapshot.hasData && snapshot.data!.isNotEmpty) { // 数据加载成功 return Padding( padding: const EdgeInsets.all(5), child: Dataclass(datalist: snapshot.data!), ); } else { // 无数据状态 return const Center(child: Text('No unpaid details found')); } }, ), ); } } // 改为无状态Widget,因为数据是外部传入的不可变数据 class Dataclass extends StatelessWidget { final List<UnpaidDetails> datalist; const Dataclass({super.key, required this.datalist}); @override Widget build(BuildContext context) { return SingleChildScrollView( scrollDirection: Axis.vertical, child: FittedBox( child: DataTable( sortColumnIndex: 1, showCheckboxColumn: false, border: TableBorder.all(width: 1.0), columns: const [ DataColumn( label: Text( "Period", style: TextStyle(fontSize: 25, fontWeight: FontWeight.bold), ), ), DataColumn( label: Text( "Amount", style: TextStyle(fontSize: 25, fontWeight: FontWeight.bold), ), ), ], rows: datalist .map((data) => DataRow(cells: [ DataCell( Text(data.month, style: const TextStyle( fontSize: 26, fontWeight: FontWeight.w500)), ), DataCell( Text(data.price.toString(), style: const TextStyle( fontSize: 26, fontWeight: FontWeight.w500)), ), ])) .toList(), ), ), ); } }
额外说明
- 新增了token空值检查,避免因token缺失发起无效请求;
- 完善了各种状态的UI反馈(加载、错误、无数据);
- 将Dataclass改为StatelessWidget,符合Flutter的最佳实践,减少不必要的State管理。
内容的提问来源于stack exchange,提问作者Hari .S
相关产品推荐
相关产品推荐

