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,同时抛出类型错误。
修复步骤
将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(); }清理冗余判断逻辑
删除getUsers()中多余的if (json.decode(request.body) == null)判断,200状态码返回的是有效数组,无需额外判断。修正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!) ); }避免重复发起请求
在initState中初始化Future并保存,防止每次build时重新请求:late Future<List<User>> _usersFuture; @override void initState() { super.initState(); _usersFuture = getUsers(); } // 在FutureBuilder中使用 future: _usersFuture,
内容的提问来源于stack exchange,提问作者mycotoad
相关产品推荐
相关产品推荐

