Flutter中如何获取并展示FFXIVCollect API的全部法术数据?
解决FFXIV Collect法术API数据获取与ListView展示问题
问题描述
我正在使用FFXIV Collect的法术API,想在ListView.builder中展示所有法术名称,但遇到以下问题:
- 调用API返回的是包含Query、Count、Results字段的结构,现有数据模型无法直接解析
- 尝试提取Results部分后仍无法正常展示内容
- 单条数据的API(带ID)可正常获取,但通过全局变量拼接URL遍历获取多条数据的方式无效
附上当前的数据模型代码和网络请求代码,求解决如何获取并展示所有法术数据。
数据模型代码
import 'package:meta/meta.dart'; import 'dart:convert'; List<FinalApi> finalApiFromMap(String str) => List<FinalApi>.from(json.decode(str).map((x) => FinalApi.fromMap(x))); String finalApiToMap(List<FinalApi> data) => json.encode(List<dynamic>.from(data.map((x) => x.toMap()))); class FinalApi { FinalApi({ required this.id, required this.name, required this.description, required this.tooltip, required this.order, required this.rank, required this.patch, required this.owned, required this.icon, required this.type, required this.aspect, required this.sources, }); int id; String name; String description; String tooltip; int order; int rank; String patch; String owned; String icon; Aspect type; Aspect aspect; List<Source> sources; factory FinalApi.fromMap(Map<String, dynamic> json) => FinalApi( id: json["id"], name: json["name"], description: json["description"], tooltip: json["tooltip"], order: json["order"], rank: json["rank"], patch: json["patch"], owned: json["owned"], icon: json["icon"], type: Aspect.fromMap(json["type"]), aspect: Aspect.fromMap(json["aspect"]), sources: List<Source>.from(json["sources"].map((x) => Source.fromMap(x))), ); Map<String, dynamic> toMap() => { "id": id, "name": name, "description": description, "tooltip": tooltip, "order": order, "rank": rank, "patch": patch, "owned": owned, "icon": icon, "type": type.toMap(), "aspect": aspect.toMap(), "sources": List<dynamic>.from(sources.map((x) => x.toMap())), }; } class Aspect { Aspect({ required this.id, required this.name, }); int id; Name? name; factory Aspect.fromMap(Map<String, dynamic> json) => Aspect( id: json["id"], name: nameValues.map[json["name"]], ); Map<String, dynamic> toMap() => { "id": id, "name": nameValues.reverse[name], }; } enum Name { WATER, FIRE, BLUNT, PIERCING, LIGHTNING, NONE, EARTH, SLASHING, ICE, WIND, PIERCING_FIRE, BLUNT_EARTH, MAGIC, PHYSICAL } final nameValues = EnumValues({ "Blunt": Name.BLUNT, "Blunt/Earth": Name.BLUNT_EARTH, "Earth": Name.EARTH, "Fire": Name.FIRE, "Ice": Name.ICE, "Lightning": Name.LIGHTNING, "Magic": Name.MAGIC, "None": Name.NONE, "Physical": Name.PHYSICAL, "Piercing": Name.PIERCING, "Piercing/Fire": Name.PIERCING_FIRE, "Slashing": Name.SLASHING, "Water": Name.WATER, "Wind": Name.WIND }); class Source { Source({ required this.type, required this.text, required this.relatedType, required this.relatedId, }); Type? type; String text; dynamic relatedType; dynamic relatedId; factory Source.fromMap(Map<String, dynamic> json) => Source( type: typeValues.map[json["type"]], text: json["text"], relatedType: json["related_type"], relatedId: json["related_id"], ); Map<String, dynamic> toMap() => { "type": typeValues.reverse[type], "text": text, "related_type": relatedType, "related_id": relatedId, }; } enum Type { OTHER, DUNGEON } final typeValues = EnumValues({"Dungeon": Type.DUNGEON, "Other": Type.OTHER}); class EnumValues<T> { Map<String, T> map; Map<T, String>? reverseMap; EnumValues(this.map); Map<T, String> get reverse { if (reverseMap == null) { reverseMap = map.map((k, v) => new MapEntry(v, k)); } return reverseMap!; } }
网络请求代码
import 'dart:convert'; import 'dart:developer'; import 'package:apifinalfantasy/globals.dart' as globals; import 'package:apifinalfantasy/models/final_api.dart'; import 'package:flutter/cupertino.dart'; import 'package:http/http.dart' as http; class RemoteService { Future<List<FinalApi>> getPosts() async { var client = http.Client(); var uri = Uri.parse('https://ffxivcollect.com/api/spells/'); var response = await client.get(uri); try { if (response.statusCode == 200) { var json = response.body; final data = FinalApi.fromMap(jsonDecode(json)); return [data]; } } catch (e) { log(e.toString()); } return []; } }
解决方案
1. 新增顶层响应模型
API返回的是包含results数组的顶层对象,需要新增一个模型类来解析整个响应结构,保留原有FinalApi等类不变:
// 在final_api.dart中新增此类 class SpellResponse { final dynamic query; final int count; final List<FinalApi> results; SpellResponse({required this.query, required this.count, required this.results}); factory SpellResponse.fromMap(Map<String, dynamic> json) => SpellResponse( query: json["query"], count: json["count"], results: List<FinalApi>.from(json["results"].map((x) => FinalApi.fromMap(x))), ); }
2. 修正网络请求逻辑
修改RemoteService的getPosts方法,先解析顶层的SpellResponse,再取出其中的results列表返回:
class RemoteService { Future<List<FinalApi>> getPosts() async { var client = http.Client(); var uri = Uri.parse('https://ffxivcollect.com/api/spells/'); var response = await client.get(uri); try { if (response.statusCode == 200) { final jsonResponse = jsonDecode(response.body); // 解析顶层响应对象 final spellResponse = SpellResponse.fromMap(jsonResponse); // 返回法术列表 return spellResponse.results; } } catch (e) { log(e.toString()); } return []; } }
3. ListView展示实现
使用FutureBuilder配合ListView.builder来异步加载并展示数据:
class SpellListScreen extends StatelessWidget { final RemoteService remoteService = RemoteService(); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('FFXIV法术列表')), body: FutureBuilder<List<FinalApi>>( future: remoteService.getPosts(), builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { return const Center(child: CircularProgressIndicator()); } else if (snapshot.hasError) { return Center(child: Text('加载失败: ${snapshot.error}')); } else if (!snapshot.hasData || snapshot.data!.isEmpty) { return const Center(child: Text('暂无数据')); } else { return ListView.builder( itemCount: snapshot.data!.length, itemBuilder: (context, index) { final spell = snapshot.data![index]; return ListTile( title: Text(spell.name), subtitle: Text('补丁版本: ${spell.patch}'), leading: Image.network(spell.icon), // 可选:显示法术图标 ); }, ); } }, ), ); } }
内容的提问来源于stack exchange,提问作者yuuu
相关产品推荐
相关产品推荐

