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

Flutter中FutureBuilder卡在圆形进度条,API取数却无法展示

分类页面加载数据后卡在进度条的问题修复

问题原因分析

  1. 异步执行顺序错误:页面初始化时token为空,fetchCategory先以空token执行,后续获取到有效token后未重新触发数据请求,FutureBuilder一直等待初始无效请求的结果。
  2. 模型类递归反序列化错误:CategoryModel中brandClass字段错误地使用CategoryModel.fromJson反序列化,导致无限递归或解析失败,请求抛出异常但未被捕获。
  3. 缺少错误状态处理: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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.16 22:22:00