You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter:Future Builder重复调用API且DataTable不渲染问题求助

问题与解决方案:FutureBuilder+DataTable实现API数据渲染问题修复

问题描述

  1. FutureBuilder触发两次API调用,第一次请求正常返回响应,第二次请求未携带请求头导致报错;
  2. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.05 00:20:30