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

Flutter无法获取团队数据,报错Failed to fetch team data(附代码)

Flutter获取团队数据失败问题修复

问题现象

从后端接口获取团队数据时抛出异常:

ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: Exception: Failed to fetch team data

现有代码

TeamPage

import 'dart:convert';
import 'package:crm/models/base_url.dart';
import 'package:crm/models/team.dart';
import 'package:crm/profile/profil.dart';
import 'package:crm/screens/team/widgets/team_widget.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;

import '../home/home_widgets/navigation_drawer_widget.dart';

class TeamPage extends StatefulWidget {
  @override
  _TeamPageState createState() => _TeamPageState();
}

class _TeamPageState extends State<TeamPage> {
  GlobalKey<ScaffoldState> _scaffoldKEY = GlobalKey<ScaffoldState>();
  late Future<List<Team>> _teamData;

  @override
  void initState() {
    super.initState();
    _teamData = fetchTeams();
  }

  Future<List<Team>> fetchTeams() async {
    final response = await http.get(Uri.parse('${BaseUrl.BASE_URL}/flutter-api/database'));

    if (response.statusCode == 200) {
      final List<dynamic> data = jsonDecode(response.body);
      return data.map((json) => Team.fromJson(json)).toList();
    } else {
      throw Exception('Failed to fetch team data');
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      key: _scaffoldKEY,
      drawer: NavigationDrawerWidget(),
      body: Container(
        decoration: BoxDecoration(
          image: DecorationImage(
            image: AssetImage("assets/images/bg.png"),
            fit: BoxFit.cover,
          ),
        ),
        child: SingleChildScrollView(
          child: Column(
            children: [
              Padding(
                padding: EdgeInsets.all(10.0),
                child: Row(
                  children: [
                    SizedBox(
                      height: 90,
                    ),
                    IconButton(
                      icon: Icon(
                        Icons.menu_outlined,
                        color: Colors.white,
                      ),
                      onPressed: () =>
                          _scaffoldKEY.currentState?.openDrawer(),
                    ),
                    Text(
                      "Team",
                      style: TextStyle(color: Colors.white, fontSize: 20),
                    ),
                    Spacer(),
                    Icon(
                      Icons.add_alert_outlined,
                      color: Colors.transparent,
                    ),
                    SizedBox(
                      width: MediaQuery.of(context).size.width * 0.05,
                    ),
                    InkWell(
                      onTap: () {
                        Navigator.of(context).push(
                          MaterialPageRoute(
                            builder: (context) => PorfilePage(),
                          ),
                        );
                      },
                      child: Icon(
                        Icons.person,
                        color: Colors.white,
                      ),
                    ),
                    SizedBox(
                      width: MediaQuery.of(context).size.width * 0.05,
                    ),
                  ],
                ),
              ),
              SizedBox(
                height: MediaQuery.of(context).size.height * 0.04,
              ),
              Container(
                height: MediaQuery.of(context).size.height * 0.045,
                width: MediaQuery.of(context).size.width * 0.87,
                child: Row(
                  children: [
                    SizedBox(
                      width: MediaQuery.of(context).size.width * 0.04,
                    ),
                    Icon(Icons.search_outlined, color: Colors.white),
                  ],
                ),
                decoration: BoxDecoration(
                  color: Colors.teal.shade200,
                  borderRadius: BorderRadius.circular(15),
                  boxShadow: [
                    BoxShadow(
                      blurRadius: 10,
                      spreadRadius: 5,
                      offset: Offset(1, 1),
                      color: Colors.grey.withOpacity(0.1),
                    ),
                  ],
                ),
              ),
              SizedBox(
                height: MediaQuery.of(context).size.height * 0.04,
              ),
              FutureBuilder<List<Team>>(
                future: fetchTeams(),
                builder: (context, snapshot) {
                  if (snapshot.hasData) {
                    final List<Team> teams = snapshot.data!;

                    return TeamWidget(team: teams.first);
                  } else if (snapshot.hasError) {
                    return Text('Error: ${snapshot.error}');
                  }

                  return CircularProgressIndicator();
                },
              ),
            ]
          )
        )
      )
    );
  }
}

TeamWidget

import 'package:crm/models/team.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:percent_indicator/linear_percent_indicator.dart';

import '../../../../route/route_helper.dart';

class TeamWidget extends StatelessWidget {
  final Team team;

  TeamWidget({required this.team});

  @override
  Widget build(BuildContext context) {
    return SingleChildScrollView(
      child: Container(
        height: MediaQuery.of(context).size.height*0.8,
        width: MediaQuery.of(context).size.width,
        child: ListView.builder(
          itemCount:9,
          itemBuilder: (BuildContext context, int index){
            return Padding(
              padding: EdgeInsets.all(6.0),
              child: ListTile(
                title: Container(
                  height:MediaQuery.of(context).size.height*0.15,
                  width:MediaQuery.of(context).size.width*0.8 ,
                  child: Row(
                    children: [
                      Column( 
                        children: [
                          SizedBox(height:MediaQuery.of(context).size.height*0.06,),  
                          Row(
                            children: [
                              SizedBox( width:MediaQuery.of(context).size.width*0.02 ,),
                              CircleAvatar(
                                radius: 25,
                                backgroundImage: AssetImage('assets/images/1.png'),
                              ),
                            ],
                          ),
                        ],
                      ),
                      SizedBox( width:MediaQuery.of(context).size.width*0.03 ,),
                      VerticalDivider(
                        color: Colors.black26,
                        width: 1,
                        thickness: 1,
                        indent: 8,
                        endIndent: 8,
                      ),
                      SizedBox( width:MediaQuery.of(context).size.width*0.03 ,),
                      Column(
                        children: [
                          SizedBox(width:MediaQuery.of(context).size.width*0.03 ,),
                          SizedBox(height:MediaQuery.of(context).size.height*0.05,),
                          Text('Name: ${team.name}',style: TextStyle(fontSize: 18),),
                          SizedBox( width:MediaQuery.of(context).size.width*0.03 ,),
                          Text('Email: ${team.email}',style: TextStyle(fontSize: 12,color: Colors.grey.shade400,),),
                          SizedBox( width:MediaQuery.of(context).size.width*0.03 ,),
                          Text('Phone : ${team.phone }',style: TextStyle(color:Colors.black54,fontSize: 13 ),)
                        ],
                      ),
                      Spacer(),
                      Column(
                        children: [
                          SizedBox(height:MediaQuery.of(context).size.height*0.055,),  
                          Icon(Icons.remove_circle,color: Colors.red,)
                        ],
                      ),
                      SizedBox( width:MediaQuery.of(context).size.width*0.03 ,),
                    ],
                  ),
                  decoration: BoxDecoration(
                    color:Colors.white,
                    borderRadius: BorderRadius.circular(15),
                    boxShadow: [
                      BoxShadow(
                        blurRadius: 10,
                        spreadRadius: 5,
                        offset: Offset(1, 1),
                        color:Colors.grey.withOpacity(0.1)
                      )
                    ]
                  ),
                ),
              ),
            );
          }
        ),
        decoration: BoxDecoration(
          color:Colors.grey.shade100,
          borderRadius: BorderRadius.only(topRight: Radius.circular(20),topLeft: Radius.circular(20)),
        ),
      ),
    );
  }
}

Team类

class Team {
  late final int id;
  late final String name;
  late final String email;
  late final double phone;

  Team({
    required this.id,
    required this.name,
    required this.email,
    required this.phone,
  });

  factory Team.fromJson(Map<String, dynamic> json) {
    return Team(
      id: json['id'],
      name: json['name'],
      email: json['email'],
      phone: json['phone'],
    );
  }
}

BaseUrl

// ignore_for_file: non_constant_identifier_names

class BaseUrl {
  static String BASE_URL = "http://mermez.site/flutter-api";
}

修复步骤

1. 修正API请求地址

BaseUrl已包含/flutter-api,原代码重复拼接路径导致请求地址错误,修改fetchTeams方法:

Future<List<Team>> fetchTeams() async {
  final response = await http.get(Uri.parse('${BaseUrl.BASE_URL}/database'));

  if (response.statusCode == 200) {
    final List<dynamic> data = jsonDecode(response.body);
    return data.map((json) => Team.fromJson(json)).toList();
  } else {
    // 补充状态码信息,便于排查
    throw Exception('Failed to fetch team data. Status code: ${response.statusCode}');
  }
}

2. 优化FutureBuilder的Future参数

避免每次build重复发起请求,使用initState中初始化的_teamData:

FutureBuilder<List<Team>>(
  future: _teamData,
  builder: (context, snapshot) {
    if (snapshot.hasData) {
      final List<Team> teams = snapshot.data!;
      return TeamWidget(teams: teams);
    } else if (snapshot.hasError) {
      return Text('Error: ${snapshot.error}');
    }
    return CircularProgressIndicator();
  },
),

3. 修正Team类的phone字段类型

手机号通常为字符串(可能包含特殊前缀),将double改为String:

class Team {
  late final int id;
  late final String name;
  late final String email;
  late final String phone;

  Team({
    required this.id,
    required this.name,
    required this.email,
    required this.phone,
  });

  factory Team.fromJson(Map<String, dynamic> json) {
    return Team(
      id: json['id'],
      name: json['name'],
      email: json['email'],
      phone: json['phone'].toString(),
    );
  }
}

4. 修复TeamWidget的列表展示逻辑

修改TeamWidget接收团队列表,根据实际数据长度渲染:

class TeamWidget extends StatelessWidget {
  final List<Team> teams;

  TeamWidget({required this.teams});

  @override
  Widget build(BuildContext context) {
    return Container(
      height: MediaQuery.of(context).size.height*0.8,
      width: MediaQuery.of(context).size.width,
      child: ListView.builder(
        itemCount: teams.length,
        itemBuilder: (BuildContext context, int index){
          final team = teams[index];
          return Padding(
            padding: EdgeInsets.all(6.0),
            child: ListTile(
              title: Container(
                height:MediaQuery.of(context).size.height*0.15,
                width:MediaQuery.of(context).size.width*0.8 ,
                child: Row(
                  children: [
                    Column( 
                      children: [
                        SizedBox(height:MediaQuery.of(context).size.height*0.06,),  
                        Row(
                          children: [
                            SizedBox( width:MediaQuery.of(context).size.width*0.02 ,),
                            CircleAvatar(
                              radius: 25,
                              backgroundImage: AssetImage('assets/images/1.png'),
                            ),
                          ],
                        ),
                      ],
                    ),
                    SizedBox( width:MediaQuery.of(context).size.width*0.03 ,),
                    VerticalDivider(
                      color: Colors.black26,
                      width: 1,
                      thickness: 1,
                      indent: 8,
                      endIndent: 8,
                    ),
                    SizedBox( width:MediaQuery.of(context).size.width*0.03 ,),
                    Column(
                      children: [
                        SizedBox(width:MediaQuery.of(context).size.width*0.03 ,),
                        SizedBox(height:MediaQuery.of(context).size.height*0.05,),
                        Text('Name: ${team.name}',style: TextStyle(fontSize: 18),),
                        SizedBox( width:MediaQuery.of(context).size.width*0.03 ,),
                        Text('Email: ${team.email}',style: TextStyle(fontSize: 12,color: Colors.grey.shade400,),),
                        SizedBox( width:MediaQuery.of(context).size.width*0.03 ,),
                        Text('Phone : ${team.phone }',style: TextStyle(color:Colors.black54,fontSize: 13 ),)
                      ],
                    ),
                    Spacer(),
                    Column(
                      children: [
                        SizedBox(height:MediaQuery.of(context).size.height*0.055,),  
                        Icon(Icons.remove_circle,color: Colors.red,)
                      ],
                    ),
                    SizedBox( width:MediaQuery.of(context).size.width*0.03 ,),
                  ],
                ),
                decoration: BoxDecoration(
                  color:Colors.white,
                  borderRadius: BorderRadius.circular(15),
                  boxShadow: [
                    BoxShadow(
                      blurRadius: 10,
                      spreadRadius: 5,
                      offset: Offset(1, 1),
                      color:Colors.grey.withOpacity(0.1)
                    )
                  ]
                ),
              ),
            ),
          );
        }
      ),
      decoration: BoxDecoration(
        color:Colors.grey.shade100,
        borderRadius: BorderRadius.only(topRight: Radius.circular(20),topLeft: Radius.circular(20)),
      ),
    );
  }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 03:47:05