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

Flutter从服务端请求文档构建列表项时加载失败问题

Flutter请求返回200但抛出‘Failed to load album’异常排查

我有一个名为ProductDatabase的集合,存储着所有可用产品的文档。现在要通过带身份验证的请求访问本地服务器,获取所有文档快照,用来在Flutter中构建ListTile。

当前本地服务器运行正常,执行代码时命令提示符显示请求已发送且返回状态码200,但仍抛出‘Failed to load album’异常。相关代码如下:

import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:redue/pages/menu.dart';
import '../main.dart';
import 'listProducts.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:favorite_button/favorite_button.dart';
import 'map.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';

class listPage1 extends StatefulWidget {
  const listPage1({super.key});

  @override
  State<listPage1> createState() => listPage3();
}

class listPage3 extends State<listPage1> {
  late Future<Album> futureAlbum;
  Widget _buildListItem(BuildContext context, DocumentSnapshot document) {
    return Container(
        child: FutureBuilder<List<Album>>(
            future: fetchAlbum(),
            builder: (context, snapshot) {
              if (snapshot.hasData) {
                return ListView.builder(
                    itemCount: snapshot.data!.length,
                    itemBuilder: (context, index) {
                      return ListTile(
                        title: Text(snapshot.data![index].productName),
                      );
                    });
              }
              else {
                return const CircularProgressIndicator();
              }
            }));
  }


  }

  Future<List<Album>> fetchAlbum() async {
    String username = 'xxxxxx';
    String password = 'xxxxxxxx';
    String basicAuth =
        'Basic ' + base64Encode(utf8.encode('$username:$password'));
    print(basicAuth);

    final r = await http.get(
        Uri.parse(
            'http://0.0.0.0:5000/apiv1/read?collection=ProductDatabase&document=all'),
        headers: <String, String>{'authorization': basicAuth});
    print(r.body);
    if (r.statusCode == 200) {
      // If the server did return a 200 OK response,
      // then parse the JSON.
      return json.decode(r.body).cast<Album>();
    } else {
      // If the server did not return a 200 OK response,
      // then throw an exception.
      throw Exception('Failed to load album');
    }
  }
}

class Album {
  final String brand;
  final int quantity;
  final String SBD;
  final String storeName;
  final String price;
  final String productName;
  final String ID;

  const Album({
    required this.brand,
    required this.quantity,
    required this.SBD,
    required this.storeName,
    required this.price,
    required this.productName,
    required this.ID,
  });

  factory Album.fromJson(Map<String, dynamic> json) {
    return Album(
      brand: json["Data"]['Brand'],
      quantity: json["Data"]['quantity'],
      SBD: json["Data"]['SBD'],
      storeName: json["Data"]['storeName'],
      price: json["Data"]['price'],
      productName: json["Data"]['productName'],
      ID: json['ID'],
    );
  }
}

问题根源

  1. JSON解析逻辑错误:json.decode(r.body).cast<Album>()无法直接将JSON数组转换为Album对象列表,cast方法不会自动调用Album.fromJson构造器,这是抛出异常的核心原因。
  2. FutureBuilder使用不当:每次调用_buildListItem都会重新执行fetchAlbum(),导致重复请求和状态混乱;同时未处理snapshot.hasError的情况,无法直观看到具体错误。
  3. 数据结构可能不匹配:需确认服务器返回的JSON结构是否与Album.fromJson中的字段对应,比如是否存在外层Data字段、字段名大小写是否一致。

修复方案

1. 修正JSON解析逻辑

将数组遍历并逐个转换为Album对象:

Future<List<Album>> fetchAlbum() async {
  String username = 'xxxxxx';
  String password = 'xxxxxxxx';
  String basicAuth =
      'Basic ' + base64Encode(utf8.encode('$username:$password'));
  print(basicAuth);

  final r = await http.get(
      Uri.parse(
          'http://0.0.0.0:5000/apiv1/read?collection=ProductDatabase&document=all'),
      headers: <String, String>{'authorization': basicAuth});
  print(r.body);
  if (r.statusCode == 200) {
    List<dynamic> jsonList = json.decode(r.body);
    return jsonList.map((item) => Album.fromJson(item)).toList();
  } else {
    throw Exception('Failed to load album');
  }
}

2. 优化FutureBuilder使用

在State初始化时仅调用一次请求,避免重复发起:

class listPage3 extends State<listPage1> {
  late Future<List<Album>> futureAlbums;

  @override
  void initState() {
    super.initState();
    futureAlbums = fetchAlbum();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: FutureBuilder<List<Album>>(
        future: futureAlbums,
        builder: (context, snapshot) {
          if (snapshot.hasData) {
            return ListView.builder(
              itemCount: snapshot.data!.length,
              itemBuilder: (context, index) {
                return ListTile(
                  title: Text(snapshot.data![index].productName),
                );
              },
            );
          } else if (snapshot.hasError) {
            return Text('加载错误: ${snapshot.error}');
          }
          return const CircularProgressIndicator();
        },
      ),
    );
  }

  // 修正后的fetchAlbum方法放在这里
}

3. 验证并调整数据结构匹配

根据服务器返回的JSON结构修改Album.fromJson,比如如果返回的是直接的产品对象数组(无外层Data),则调整为:

factory Album.fromJson(Map<String, dynamic> json) {
  return Album(
    brand: json['Brand'],
    quantity: json['quantity'],
    SBD: json['SBD'],
    storeName: json['storeName'],
    price: json['price'],
    productName: json['productName'],
    ID: json['ID'],
  );
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.01 17:05:21