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

Flutter 3.10 Web:FutureBuilder+PaginatedDataTable时snapshot.data为空

问题:FutureBuilder接收数据后类型不匹配导致snapshot.data为空

我正在开发一个带用户列表页的Flutter应用,通过FutureBuilder包裹PaginatedDataTable展示用户记录。PaginatedDataTable的DataTableSource依赖一个返回List<User>的GET请求,但目前请求成功获取数据后,当ConnectionState变为done时,snapshot.data仍为空,同时出现类型错误。

控制台输出

{alias: ALIAS, password: password}

AsyncSnapshot<List>(ConnectionState.waiting, null, null, null)
null
snapshot.hasData failed

200
[{"id":6,"alias":"username","name_first":"fIrStNaMe","name_last":"lAsTnAmE","email":"gmail@gmail.com","phone":"2222222222","date_birth":"2000-12-01"},{"id":1,"alias":"ALIAS","name_first":"FiRsTnAmE","name_last":"LaStNaMe","email":"email@email.com","phone":"9999999999","date_birth":"2000-01-31"}]

user_fetch: after response received
[{id: 6, alias: username, name_first: fIrStNaMe, name_last: lAsTnAmE, email: gmail@gmail.com, phone: 2222222222, date_birth: 2000-12-01}, {id: 1, alias: ALIAS, name_first: FiRsTnAmE, name_last: LaStNaMe, email: email@email.com, phone: 9999999999, date_birth: 2000-01-31}]

user_fetch, 200 response received, before return list
[{id: 6, alias: username, name_first: fIrStNaMe, name_last: lAsTnAmE, email: gmail@gmail.com, phone: 2222222222, date_birth: 2000-12-01}, {id: 1, alias: ALIAS, name_first: FiRsTnAmE, name_last: LaStNaMe, email: email@email.com, phone: 9999999999, date_birth: 2000-01-31}]

AsyncSnapshot<List>(ConnectionState.done, null, Expected a value of type 'FutureOr<List>', but got one of type 'List', )
null
snapshot.hasData failed

代码示例

import 'dart:convert';
import 'package:flutter_axum/providers/url.dart';
import 'package:flutter/material.dart';
import 'package:flutter_axum/providers/user_auth.dart';
import 'package:http/http.dart' as http;
import '../models/user.dart';
import '../providers/user_secure_storage.dart';

class HomePage extends StatefulWidget {
  const HomePage({Key? key}) : super(key: key);

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('User Paginated Data Table'),
        actions: [
          IconButton(
              icon: const Icon(Icons.exit_to_app),
              onPressed: () {
                AuthenticationProvider().logoutUser(context: context);
              }),
        ],
      ),
        body: FutureBuilder(
          future: getUsers(),
          builder: (context, snapshot) {
            print(snapshot);
            print(snapshot.data);
            if (ConnectionState.active != null && !snapshot.hasData) {
              print("snapshot.hasData failed");
              return Center(child: Text('Loading'));
            }
            if (ConnectionState.done != null && snapshot.hasError) {
              print("snapshot.hasError failed");
              return Center(child: Text(snapshot.error.toString()));
            } else {
              print("snapshot reaches PaginatedDataTable");
              return PaginatedDataTable(
                  columns: getColumns(),
                  source: UserDataTableSource(source: snapshot.data! as List<User>)
              );
            }
          }
        )
    );
  }
}

Future<List<User>> getUsers() async {

  final StorageService storageService = StorageService();
  final bearer = await storageService.readSecureData('token');
  final url = BaseUrl.baseUrl;
  String _url = "$url/user/list";

  try {
    final request = await http.get(Uri.parse(_url), headers: {'Access-Control-Allow-Origin': '*', 'Authorization': 'Bearer $bearer'});

    print(request.statusCode);
    print(request.body);

    if (request.statusCode == 200 || request.statusCode == 201){

      final list = json.decode(request.body);
      print("user_fetch: after response received");
      print(list);

      if (json.decode(request.body) == null) {
        return list;
      } else {

        print("user_fetch, 200 response received, before return list");
        print(list);

        return list;
      }
    } else {
      List<User> list = [];
      return list;
    }
  } catch (e) {
    return Future.error(e.toString());
  }
}

class UserDataTableSource extends DataTableSource {
  UserDataTableSource({required source}) : userList = source;

  final List<User> userList;

  @override
  DataRow getRow(int index) {
    final User user = userList[index];

    return DataRow.byIndex(index: index, cells: [
      DataCell(Text('${user.id}')),
      DataCell(Text(user.alias)),
      DataCell(Text(user.nameFirst)),
      DataCell(Text(user.nameLast)),
      DataCell(Text(user.email)),
      DataCell(Text(user.phone)),
      DataCell(Text(user.dateBirth)),
    ]);
  }
    @override
    bool get isRowCountApproximate => false;
    @override
    int get rowCount => userList.length;
    @override
    int get selectedRowCount => 0;
}

const String colID = 'ID';
const String colAlias = 'Alias';
const String colNameFirst = 'First Name';
const String colNameLast = 'Last Name';
const String colEmail = 'Email';
const String colPhone = 'Phone';
const String colDateBirth = 'Date of Birth';

List<DataColumn> getColumns(){
  final List<DataColumn> columns =
  <DataColumn>[
    const DataColumn(
      label: Text(colID),
      tooltip: colID,
    ),
    const DataColumn(
      label: Text(colAlias),
      tooltip: colAlias,
    ),
    const DataColumn(
      label: Text(colNameFirst),
      tooltip: colNameFirst,
    ),
    const DataColumn(
      label: Text(colNameLast),
      tooltip: colNameLast,
    ),
    const DataColumn(
      label: Text(colEmail),
      tooltip: colEmail,
    ),
    const DataColumn(
      label: Text(colPhone),
      tooltip: colPhone,
    ),
    const DataColumn(
      label: Text(colDateBirth),
      tooltip: colDateBirth,
    ),
  ];

  return columns;
}

解决思路

核心问题

getUsers()函数声明返回Future<List<User>>,但实际返回的是List<Map<String, dynamic>>(json.decode的结果),类型不匹配导致FutureBuilder无法正确解析数据,因此snapshot.hasData为false,同时抛出类型错误。

修复步骤

  1. 将JSON数组解析为User对象列表
    确保User模型包含fromJson构造方法,用于将Map转换为User实例:

    class User {
      final int id;
      final String alias;
      final String nameFirst;
      final String nameLast;
      final String email;
      final String phone;
      final String dateBirth;
    
      User({
        required this.id,
        required this.alias,
        required this.nameFirst,
        required this.nameLast,
        required this.email,
        required this.phone,
        required this.dateBirth,
      });
    
      factory User.fromJson(Map<String, dynamic> json) {
        return User(
          id: json['id'],
          alias: json['alias'],
          nameFirst: json['name_first'],
          nameLast: json['name_last'],
          email: json['email'],
          phone: json['phone'],
          dateBirth: json['date_birth'],
        );
      }
    }
    

    然后修改getUsers()的返回逻辑:

    if (request.statusCode == 200 || request.statusCode == 201){
      final List<dynamic> jsonList = json.decode(request.body);
      return jsonList.map((json) => User.fromJson(json as Map<String, dynamic>)).toList();
    }
    
  2. 清理冗余判断逻辑
    删除getUsers()中多余的if (json.decode(request.body) == null)判断,200状态码返回的是有效数组,无需额外判断。

  3. 修正FutureBuilder状态判断
    原状态判断逻辑错误,改为基于snapshot.connectionState的正确判断:

    builder: (context, snapshot) {
      print(snapshot);
      print(snapshot.data);
      if (snapshot.connectionState == ConnectionState.waiting) {
        return const Center(child: CircularProgressIndicator());
      }
      if (snapshot.hasError) {
        return Center(child: Text('Error: ${snapshot.error}'));
      }
      if (!snapshot.hasData || snapshot.data!.isEmpty) {
        return const Center(child: Text('No users found'));
      }
      return PaginatedDataTable(
        columns: getColumns(),
        source: UserDataTableSource(source: snapshot.data!)
      );
    }
    
  4. 避免重复发起请求
    在initState中初始化Future并保存,防止每次build时重新请求:

    late Future<List<User>> _usersFuture;
    
    @override
    void initState() {
      super.initState();
      _usersFuture = getUsers();
    }
    
    // 在FutureBuilder中使用
    future: _usersFuture,
    

内容的提问来源于stack exchange,提问作者mycotoad

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 13:17:00