Flutter中FutureBuilder卡在圆形进度条,API取数却无法展示
分类页面加载数据后卡在进度条的问题修复
问题原因分析
- 异步执行顺序错误:页面初始化时
token为空,fetchCategory先以空token执行,后续获取到有效token后未重新触发数据请求,FutureBuilder一直等待初始无效请求的结果。 - 模型类递归反序列化错误:
CategoryModel中brandClass字段错误地使用CategoryModel.fromJson反序列化,导致无限递归或解析失败,请求抛出异常但未被捕获。 - 缺少错误状态处理:
FutureBuilder仅判断snapshot.hasData,未处理请求失败的情况,异常时仍显示加载进度条。
修复方案
1. 修正模型类的递归反序列化问题
修改CategoryModel中的brandClass字段,替换为正确的品牌模型类(并处理空值情况):
import 'dart:convert'; class CategoryModel { final int id; final String name; final String description; final String createdAt; final String updatedAt; final bool isActive; final BrandClass? brandClass; final UserClass userClass; final dynamic vendorClass; CategoryModel({ required this.id, required this.name, required this.description, required this.createdAt, required this.updatedAt, required this.isActive, this.brandClass, required this.userClass, required this.vendorClass, }); factory CategoryModel.fromRawJson(String str) => CategoryModel.fromJson(json.decode(str)); String toRawJson() => json.encode(toJson()); factory CategoryModel.fromJson(Map<String, dynamic> json) => CategoryModel( id: json["id"], name: json["name"], description: json["description"], createdAt: json["created_at"], updatedAt: json["updated_at"], isActive: json["isActive"], brandClass: json["brandClass"] != null ? BrandClass.fromJson(json["brandClass"]) : null, userClass: UserClass.fromJson(json["userClass"]), vendorClass: json["vendorClass"], ); Map<String, dynamic> toJson() => { "id": id, "name": name, "description": description, "created_at": createdAt, "updated_at": updatedAt, "isActive": isActive, "brandClass": brandClass?.toJson(), "userClass": userClass.toJson(), "vendorClass": vendorClass, }; } // 新增品牌模型类(根据API返回结构调整字段) class BrandClass { final int id; final String name; BrandClass({required this.id, required this.name}); factory BrandClass.fromJson(Map<String, dynamic> json) => BrandClass( id: json["id"], name: json["name"], ); Map<String, dynamic> toJson() => { "id": id, "name": name, }; } class UserClass { final int id; final dynamic email; final dynamic password; final String name; final dynamic mobile; final dynamic description; final bool isActive; final dynamic createdAt; final dynamic updatedAt; final dynamic userRoleClass; final dynamic userVerificationClass; UserClass({ required this.id, required this.email, required this.password, required this.name, required this.mobile, required this.description, required this.isActive, required this.createdAt, required this.updatedAt, required this.userRoleClass, required this.userVerificationClass, }); factory UserClass.fromRawJson(String str) => UserClass.fromJson(json.decode(str)); String toRawJson() => json.encode(toJson()); factory UserClass.fromJson(Map<String, dynamic> json) => UserClass( id: json["id"], email: json["email"], password: json["password"], name: json["name"], mobile: json["mobile"], description: json["description"], isActive: json["isActive"], createdAt: json["created_at"], updatedAt: json["updated_at"], userRoleClass: json["userRoleClass"], userVerificationClass: json["userVerificationClass"], ); Map<String, dynamic> toJson() => { "id": id, "email": email, "password": password, "name": name, "mobile": mobile, "description": description, "isActive": isActive, "created_at": createdAt, "updated_at": updatedAt, "userRoleClass": userRoleClass, "userVerificationClass": userVerificationClass, }; }
2. 修正FutureBuilder的请求触发逻辑
在状态类中维护请求Future,确保获取到token后才发起有效请求,并添加错误处理:
class CategoriesList extends StatefulWidget { final int vendorId; const CategoriesList({super.key, required this.vendorId}); @override State<CategoriesList> createState() => _CategoriesListState(); } class _CategoriesListState extends State<CategoriesList> { final _storage = const FlutterSecureStorage(); String token = ""; Future<List<CategoryModel>>? _fetchFuture; @override void initState() { super.initState(); retrieveToken(); } Future<void> retrieveToken() async { String? token = await _storage.read(key: 'jwt_token'); if (token != null) { setState(() { this.token = token; _fetchFuture = fetchCategory(token); }); log(token, name: 'token from categories page'); } } Future<List<CategoryModel>> fetchCategory(String token) async { var url = 'http://example.com/api/Category/isActive?vendor_id=${widget.vendorId}'; var response = await http.get( Uri.parse(url), headers: { 'Authorization': 'Bearer $token', 'accept': 'application/json', 'Content-Type': 'application/json-patch+json', }, ); if (response.statusCode == 200) { List mapData = jsonDecode(response.body); List<CategoryModel> categories = mapData.map((category) => CategoryModel.fromJson(category)).toList(); return categories; } else { throw Exception('Failed to load category: ${response.statusCode}'); } } @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, home: Scaffold( body: SafeArea( minimum: const EdgeInsets.all(10), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const SizedBox(height: 20), const Padding( padding: EdgeInsets.only(left: 10.0), child: Text( 'Categories:', style: TextStyle( fontSize: 22, ), ), ), const SizedBox(height: 20), Flexible( child: FutureBuilder<List<CategoryModel>>( future: _fetchFuture, builder: (context, snapshot) { if (snapshot.hasData) { List<CategoryModel>? categories = snapshot.data; return ListView.builder( shrinkWrap: true, itemCount: categories?.length, itemBuilder: (BuildContext context, index) { return Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(8), color: const Color.fromARGB(255, 231, 231, 231), ), height: 75, margin: const EdgeInsets.only( top: 4, right: 8, left: 8, bottom: 8), child: ListTile( onTap: () => { // Navigator.of(context).push(MaterialPageRoute( // builder: (context) => Storefront())) //log(token.toString(), name: 'token when click'), }, leading: Image.network( "https://wallpaperaccess.com/full/2637581.jpg", width: 70, height: 70, fit: BoxFit.fitWidth, ), title: Text(categories![index].name), subtitle: Text(categories[index].description), ), ); }); } else if (snapshot.hasError) { return Center(child: Text('加载失败: ${snapshot.error}')); } else { return const Center(child: CircularProgressIndicator()); } }, ), ) ], ), ), ), ); } }
额外修复点
- 修正了
subtitle的字符串错误:原代码中Text('categories[index].description')是固定字符串,改为Text(categories[index].description)以显示真实数据。
内容的提问来源于stack exchange,提问作者Django
相关产品推荐
相关产品推荐

