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
相关产品推荐
相关产品推荐

