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

Flutter中移除ListView内JSON空数据项问题求助

Model类
import 'dart:convert';

List<EmployeeResponseModel> employeeResponseModelFromJson(String str) =>
    List<EmployeeResponseModel>.from(
        json.decode(str).map((x) => EmployeeResponseModel.fromJson(x)));

String employeeResponseModelToJson(List<EmployeeResponseModel> data) =>
    json.encode(List<dynamic>.from(data.map((x) => x.toJson())));

class EmployeeResponseModel {
  EmployeeResponseModel({
    this.id,
    this.name,
    this.company,
    this.designation,
    this.companyLogo,
  });

  final int? id;
  final String? name;
  final String? company;
  final String? designation;
  final String? companyLogo;

  factory EmployeeResponseModel.fromJson(Map<String, dynamic> json) =>
      EmployeeResponseModel(
        id: json["id"] == "null" ? '' : json["id"],
        name: json["name"] == "null" ? '' : json["name"],
        company: json["company"] == "null" ? '' : json["company"],
        designation: json["designation"] == "null" ? '' : json["designation"],
        companyLogo: json["company_logo"] == "null" ? '' : json["company_logo"],
      );

  Map<String, dynamic> toJson() => {
        "id": id,
        "name": name,
        "company": company,
        "designation": designation,
        "company_logo": companyLogo,
      };
}
Provider
class EmployeeProvider extends ChangeNotifier {
  bool isAuthenticated = false;

  Future<List<EmployeeResponseModel>?> getAllEmployee() async {
    try {
      http.Response response = await http.get(
        Uri.parse(ApiBaseUrl().employeeBaseUrl),
        headers: {
          'Content-Type': 'application/json; charset=utf-8',
        },
      );

      if (response.statusCode == 200) {
        final parsed = json.decode(response.body);

        notifyListeners();
        return parsed
            .map<EmployeeResponseModel>(
                (json) => EmployeeResponseModel.fromJson(json))
            .toList();
      } else {
        throw Exception(AppStrings.failedToLoadEmployeeList);
      }
    } on SocketException {
      throw AppStrings.noInternet;
    }
  }
}
UI渲染代码
class EmployeeList extends StatefulWidget {
  const EmployeeList({super.key});

  @override
  State<EmployeeList> createState() => _EmployeeListState();
}

class _EmployeeListState extends State<EmployeeList> {
  late Future<List<EmployeeResponseModel>?> _employeeModelList;

  void initState() {
    getAllEmployee();
    super.initState();
  }

  Future<void> getAllEmployee() async {
    setState(() {
      _employeeModelList = Provider.of<EmployeeProvider>(context, listen: false)
          .getAllEmployee();
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text(AppStrings.employeeList),
        backgroundColor: Colors.blue,
      ),
      body: RefreshIndicator(
        onRefresh: getAllEmployee,
        child: SingleChildScrollView(
          physics: const ScrollPhysics(),
          child: Column(
            mainAxisSize: MainAxisSize.min,
            children: [
              FutureBuilder<List<EmployeeResponseModel>?>(
                future: _employeeModelList,
                builder: (context, snapshot) {
                  switch (snapshot.connectionState) {
                    case ConnectionState.none:
                    case ConnectionState.active:
                    case ConnectionState.waiting:
                      return ListView.builder(
                          itemCount: 10,
                          shrinkWrap: true,
                          itemBuilder: (context, index) {
                            return skeltonBuild();
                          });
                    case ConnectionState.done:
                      if (snapshot.hasData && snapshot.data!.isNotEmpty) {
                        return Flexible(
                          child: ListView.builder(
                            itemCount: snapshot.data!.length,
                            shrinkWrap: true,
                            physics: const NeverScrollableScrollPhysics(),
                            itemBuilder: (_, index) {
                              return EmployeeListWidget(
                                name: snapshot.data![index].name.toString(),
                                company:
                                    snapshot.data![index].company.toString(),
                                designation: snapshot.data![index].designation
                                    .toString(),
                                company_logo: snapshot.data![index].companyLogo
                                    .toString(),
                                onTap: () => Navigator.push(
                                  context,
                                  MaterialPageRoute(
                                      builder: (context) =>
                                          const EmployeeDetails()),
                                ),
                              );
                            },
                          ),
                        );
                      } else {
                        return Padding(
                          padding: EdgeInsets.only(
                              top: MediaQuery.of(context).size.height * 0.2),
                          child: NoInternetConnection(
                              message: snapshot.error.toString()),
                        );
                      }
                  }
                },
              ),
            ],
          ),
        ),
      ),
    );
  }
}
列表Widget代码
class EmployeeListWidget extends StatelessWidget {
  final String name;
  final String company;
  final String designation;
  final String company_logo;
  final VoidCallback? onTap;
  const EmployeeListWidget(
      {super.key,
      required this.name,
      required this.company,
      required this.designation,
      required this.company_logo,
      required this.onTap});

  @override
  Widget build(BuildContext context) {
    return InkWell(
      onTap: onTap,
      child: Padding(
        padding: const EdgeInsets.fromLTRB(4.0, 0.0, 4.0, 0.0),
        child: Card(
          elevation: 2.0,
          child: Padding(
            padding: const EdgeInsets.all(4.0),
            child: Row(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: [
                Column(
                  mainAxisSize: MainAxisSize.min,
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    if (name != "null")
                      Text(name.toString(),
                          style: const TextStyle(
                              fontSize: 16,
                              color: Colors.black87,
                              fontWeight: FontWeight.bold)),
                    const SizedBox(height: 4),
                    if (company != "null")
                      SmallGreyTextIcon(
                          text: company, icon: Icons.apartment_outlined),
                    const SizedBox(height: 4),
                    if (designation != "null")
                      SmallGreyTextIcon(
                          text: designation, icon: Icons.badge_outlined),
                  ],
                ),
                const SizedBox(
                  height: 4,
                ),
                if (company_logo != "null")
                  CachedNetworkImage(
                    imageUrl: company_logo,
                    width: 50,
                    height: 50,
                    placeholder: (context, url) =>
                        const CircularProgressIndicator(),
                    errorWidget: (context, url, error) =>
                        const Icon(Icons.error),
                  ),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

解决方案

1. 修复Model类的空值处理逻辑

原Model类存在类型错误(id为int?却可能赋值字符串)和未处理JSON原生null的问题,修改后的fromJson方法如下:

factory EmployeeResponseModel.fromJson(Map<String, dynamic> json) => EmployeeResponseModel(
      id: json["id"] == null || json["id"] == "null" ? null : int.tryParse(json["id"].toString()),
      name: json["name"] == null || json["name"] == "null" ? null : json["name"].toString(),
      company: json["company"] == null || json["company"] == "null" ? null : json["company"].toString(),
      designation: json["designation"] == null || json["designation"] == "null" ? null : json["designation"].toString(),
      companyLogo: json["company_logo"] == null || json["company_logo"] == "null" ? null : json["company_logo"].toString(),
    );

2. 在Provider中过滤空列表项

获取数据后直接过滤掉所有字段均为空的无效项,避免这些项进入列表:

Future<List<EmployeeResponseModel>?> getAllEmployee() async {
  try {
    http.Response response = await http.get(
      Uri.parse(ApiBaseUrl().employeeBaseUrl),
      headers: {
        'Content-Type': 'application/json; charset=utf-8',
      },
    );

    if (response.statusCode == 200) {
      final parsed = json.decode(response.body);
      notifyListeners();
      
      // 过滤无效项:至少一个字段不为null才保留
      return parsed
          .map<EmployeeResponseModel>((json) => EmployeeResponseModel.fromJson(json))
          .where((employee) => 
              employee.name != null || 
              employee.company != null || 
              employee.designation != null || 
              employee.companyLogo != null)
          .toList();
    } else {
      throw Exception(AppStrings.failedToLoadEmployeeList);
    }
  } on SocketException {
    throw AppStrings.noInternet;
  }
}

3. 优化UI参数传递与空值判断

  • 传递参数时避免将null转为字符串"null",使用空值合并运算符提供默认空字符串:
// 在EmployeeList的ListView.builder中修改参数传递
return EmployeeListWidget(
  name: snapshot.data![index].name ?? "",
  company: snapshot.data![index].company ?? "",
  designation: snapshot.data![index].designation ?? "",
  company_logo: snapshot.data![index].companyLogo ?? "",
  onTap: () => Navigator.push(
    context,
    MaterialPageRoute(builder: (context) => const EmployeeDetails()),
  ),
);
  • 修改Widget中的空值判断逻辑,判断字符串是否为空而非是否等于"null",同时增加全局空项判断避免渲染空Card:
class EmployeeListWidget extends StatelessWidget {
  final String name;
  final String company;
  final String designation;
  final String company_logo;
  final VoidCallback? onTap;
  const EmployeeListWidget({
    super.key,
    required this.name,
    required this.company,
    required this.designation,
    required this.company_logo,
    required this.onTap,
  });

  @override
  Widget build(BuildContext context) {
    // 所有字段均为空时,不渲染任何内容
    if (name.isEmpty && company.isEmpty && designation.isEmpty && company_logo.isEmpty) {
      return const SizedBox.shrink();
    }

    return InkWell(
      onTap: onTap,
      child: Padding(
        padding: const EdgeInsets.fromLTRB(4.0, 0.0, 4.0, 0.0),
        child: Card(
          elevation: 2.0,
          child: Padding(
            padding: const EdgeInsets.all(4.0),
            child: Row(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: [
                Column(
                  mainAxisSize: MainAxisSize.min,
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    if (name.isNotEmpty)
                      Text(
                        name,
                        style: const TextStyle(
                            fontSize: 16,
                            color: Colors.black87,
                            fontWeight: FontWeight.bold),
                      ),
                    const SizedBox(height: 4),
                    if (company.isNotEmpty)
                      SmallGreyTextIcon(
                          text: company, icon: Icons.apartment_outlined),
                    const SizedBox(height: 4),
                    if (designation.isNotEmpty)
                      SmallGreyTextIcon(
                          text: designation, icon: Icons.badge_outlined),
                  ],
                ),
                if (company_logo.isNotEmpty)
                  CachedNetworkImage(
                    imageUrl: company_logo,
                    width: 50,
                    height: 50,
                    placeholder: (context, url) =>
                        const CircularProgressIndicator(),
                    errorWidget: (context, url, error) =>
                        const Icon(Icons.error),
                  ),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.12 12:40:42