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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.15 19:40:57